Skip to content

Architecture

Trurlic is a single Rust crate. Visibility enforces the boundaries — everything is pub(crate) except cli and store. There is no LLM code anywhere in the binary: all reasoning runs on the coding agent’s own model, over MCP.

cli
│ parses args, dispatches
commands ───────────────┐
│ │ │ │
┌──────────┘ │ └──────────┐ │
▼ ▼ ▼ ▼
mcp map workflow │
│ │ │ │
└──────┬───────┴──────────────┴─────┘
store (foundation — no internal deps)
ModuleDepends onRole
storeThe decision graph: TOML files, edge index, validation, atomic writes, file locking. Never imports another module.
workflowstoreStep deduction, concern tracking, prompt generation. Pure functions — no I/O, no side effects.
mcpstore, workflowThe MCP server: JSON-RPC over stdio, tool dispatch, context assembly, file watcher.
mapstoreInteractive graph visualization: WebSocket live sync, REST API, frontend embedded at compile time.
commandsstore, workflow, mcp, mapCLI command handlers, including install for IDE MCP config and serve.
clicommandsArgument parsing (clap) and dispatch — the entry point.

Boundary rules:

  • store is the foundation. Every write goes through a Store method that requires a StoreLock proof parameter.
  • workflow is pure computation. advance() is a deterministic function of graph state plus inputs — same inputs, same output.
  • mcp never writes the graph directly; it calls Store write methods. Prompts come from workflow::steps.
  • map depends only on store and embeds its frontend via rust-embed.

Every mutation — from the CLI, MCP, or map — takes the same atomic path:

Store::write_*(&StoreLock)
├─ validate the full graph (fail-closed — refuse invalid states)
├─ serialize to TOML
├─ write to a temp file
├─ verify round-trip parse
├─ rename node file into place
└─ rename graph.toml last ← the commit point

If the process crashes mid-write, the incomplete write is orphaned and reconciled on the next trurlic check. Because graph.toml is renamed last, the graph is never observed half-updated. See Integrity Model for the full guarantees.

agent calls a tool → mcp::dispatch
├─ read tool: acquire RwLock read lock → query in-memory graph
└─ write tool: acquire RwLock write lock → file lock → validate → commit

Read tools hold the lock for microseconds — they query in-memory data structures. Write tools take an exclusive lock, then a file lock, before validating and committing.

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

  • Tool calls — read tools take the read lock (concurrent); write tools take the write lock (exclusive), then the file lock.
  • File watcher — a notify-based watcher detects external changes (CLI commands, git checkout, manual edits) and reloads state under the write lock. The swap takes microseconds.
  • Map server — takes the read lock for API responses and WebSocket diffs.

The file lock (fs2 flock) serializes mutations across the CLI, MCP, and map processes, so they can run against the same .trurlic/ safely.

Every dependency is justified; no proc macros run at runtime (serde derive and thiserror are compile-time only).

DependencyPurpose
clapCLI argument parsing
serde + toml + serde_json + serde_yaml_ngSerialization: TOML graph files, JSON MCP protocol, JSON/YAML IDE configs
thiserrorError types (compile-time derive)
chronoUTC timestamps (RFC 3339)
blake3Content hashing — pure Rust, no C/OpenSSL
rayonParallel node-file reads in load_state
fs2Cross-platform file locking (flock)
notifyFilesystem watcher for live reload
tokio + axum + tower-httpAsync runtime and HTTP/WebSocket server for trurlic map
rust-embedMap frontend compiled into the binary
openerCross-platform browser launch for trurlic map
randMap authentication token generation
  • unsafe_code = "deny" at the crate level; unwrap()/expect() denied outside #[cfg(test)].
  • Every graph mutation validates the full graph before touching disk — invalid writes are refused, never silently committed.
  • Atomic writes: serialize → temp → round-trip verify → rename, with graph.toml renamed last.
  • workflow::advance is a pure function — no I/O, no side effects.

For the module-by-module description straight from source, see the trurlic repo’s CLAUDE.md.