Skip to content

Architecture

Trurlic is a single crate with eight modules. Visibility enforces boundaries — pub(crate) on everything except cli and store.

┌──────────────────────────────────────────────────────────────────┐
│ CLI / COMMANDS │
│ commands → store, session, config │
│ CLI command handlers — the entry point for all user actions │
└──────────────┬───────────────────────┬───────────────────────────┘
│ │
▼ ▼
┌─────────────────────────┐ ┌─────────────────────────────────────┐
│ SESSION │ │ MCP SERVER │
│ session → store, │ │ mcp → store, workflow │
│ workflow, provider │ │ │
│ │ │ JSON-RPC stdio, tool dispatch, │
│ CLI design sessions, │ │ context assembly, file watcher │
│ bootstrap driver, │ │ │
│ LLM extraction │ │ Arc<RwLock<ProjectState>> │
└──────────┬───────────────┘ └──────────┬────────────────┬────────┘
│ │ │
▼ ▼ │
┌─────────────────────────┐ ┌─────────────────────────┐ │
│ PROVIDER │ │ WORKFLOW │ │
│ provider → (leaf) │ │ workflow → store │ │
│ │ │ │ │
│ Anthropic, OpenAI, │ │ Step deduction, │ │
│ OpenRouter, Gemini, │ │ concern tracking, │ │
│ Custom, Ollama │ │ prompt generation │ │
│ clients + SSE streaming │ │ Pure functions, no I/O │ │
└──────────────────────────┘ │ │ │
└──────────┬───────────────┘ │
┌─────────────────────────┐ │ │
│ CONFIG │ ▼ ▼
│ config → (leaf) │ ┌────────────────────────────────────┐
│ │ │ STORE │
│ Provider resolution, │ │ store → (no internal deps) │
│ API key handling │ │ │
└──────────────────────────┘ │ Decision graph: TOML files, │
│ graph index, validation, │
┌─────────────────────────┐ │ atomic writes, file locking │
│ MAP │ │ │
│ map → store │ │ The foundation. Never imports │
│ │ │ from any other module. │
│ Interactive graph viz, │ └────────────────────────────────────┘
│ WebSocket live sync, │
│ REST API, embedded HTML │
└──────────────────────────┘

The provider module contains four client implementations:

  • anthropic.rs — native Anthropic Messages API client.
  • openai.rs — OpenAI-compatible client, also used for OpenRouter (via openrouter.ai/api/v1), Custom (user-specified base URL), and Ollama (local, no API key).
  • gemini.rs — native Google Gemini API client.
  • sse.rs — shared SSE streaming parser used by all clients.
  • store is the foundation. It never imports from any other module. Every write goes through Store methods with StoreLock proof parameters.
  • workflow is pure computation. It never calls an LLM, never touches the filesystem, never allocates beyond the response JSON. advance() is a deterministic function of graph state plus inputs.
  • mcp never writes to the graph directly. It calls Store write methods. It never runs LLM calls. Prompt generation comes from workflow::steps.
  • session is the only module that calls LLM APIs. It owns the CLI dialogue loop and the bootstrap driver.
  • provider and config are leaf modules. They do not import from any other internal module.
  • map depends only on store. It embeds its frontend at compile time via rust-embed.
CLI / MCP / Map
Store::write_*(&StoreLock)
├─ Validate full graph
├─ Serialize to TOML
├─ Write to temp file
├─ Verify round-trip parse
├─ Rename into place
└─ Rename graph.toml last (commit point)

Every write is atomic. If the process crashes between any two steps, the incomplete write is cleaned up on next startup. graph.toml is renamed last — it is the commit point. If it was not renamed, the node file is orphaned and reconciled on trurlic check.

Agent calls MCP tool
mcp::dispatch
├─ Read tools: acquire RwLock read lock
│ └─ Query ProjectState (in-memory graph)
└─ Write tools: acquire RwLock write lock
├─ Acquire file lock (StoreLock)
├─ Validate + commit
└─ Release locks

Read tools hold the read lock for microseconds — they query in-memory data structures. Write tools acquire an exclusive write lock, then a file lock, validate, and commit. The file lock prevents concurrent mutations from CLI, MCP, and map.

The MCP server holds Arc<RwLock<ProjectState>>. Three concurrent actors access it:

MCP tool calls — the primary consumer. Read tools acquire the read lock (concurrent). Write tools acquire the write lock (exclusive), then the file lock.

File watcher thread — a notify-based filesystem watcher detects external changes (CLI commands, git checkout, manual edits). On change, it reloads state from disk under the write lock. The swap takes microseconds.

Map WebSocket — the map server reads state for API responses and pushes diffs over WebSocket. It acquires the read lock for queries.

The write lock is never held across LLM calls. Session module (which calls LLMs) operates independently — it acquires the file lock only for the atomic write at the end.

DependencyPurposeJustification
clapCLI argument parsingStandard, derive-based
serde + toml + serde_jsonSerializationCore data format (TOML files, JSON MCP protocol)
thiserrorError typesCompile-time derive, no runtime proc macro
chronoTimestampsDecision created field (UTC, RFC 3339)
blake3Content hashingPure Rust, no C/OpenSSL dependency
rayonParallel file I/Oload_state reads node files concurrently
fs2File lockingCross-platform flock
notifyFile system watcherLive reload for MCP server and map
reqwestHTTP client for LLM APIsrustls-tls feature — pure Rust TLS, no OpenSSL
tokioAsync runtimeMCP server, map server, LLM streaming
axumHTTP server for mapWebSocket support via ws feature
tower-httpHTTP middlewareCORS and security headers for map
rust-embedEmbedded assetsMap frontend compiled into binary
openerBrowser launchtrurlic map opens browser cross-platform
randToken generationMap authentication token
zeroizeSecret erasureAPI keys zeroed from memory on drop

Every dependency is justified. No proc macros at runtime — serde derive and thiserror are compile-time only.

ProfileSettingsPurpose
devopt-level = 0, debug = true, deps at opt-level = 2Fast compile, debuggable
releasestrip, lto = "fat", codegen-units = 1, overflow-checks = true, panic = "abort"Minimal binary, maximum performance
benchInherits release, strip = false, debug = 2Profiling with symbols

unsafe_code = "deny" is enforced at the crate level. unwrap() and expect() are denied outside #[cfg(test)].

For the integrity guarantees on every write, see Integrity Model. For the advance loop and workflow engine, see Core Concepts. For the full module-by-module description, see CLAUDE.md.