Four-Crate Architecture
Enki is organized into four crates with strict dependency direction:cli → tui, acp, core. No cycles.
core
Pure sync state machine. Orchestrator, DAG scheduler, SQLite persistence, CoW copy manager, git merge refinery, roles, hashlines. Zero async, zero tokio.
acp
Async ACP client. Spawns agent subprocesses, manages sessions, routes streaming updates. All internal state is
Rc<RefCell<...>> — !Send, must run on a LocalSet.tui
Sync terminal UI library over raw
crossterm (not ratatui). Chat framework via Handler<M> trait. Optional markdown feature for termimad+syntect rendering.cli
The
enki binary. No args → TUI. enki mcp → JSON-RPC stdio server. Houses the coordinator loop (tokio::select! on a dedicated OS thread with current_thread runtime + LocalSet).Core Design Principles
DAG is the single source of truth
The in-memory DAG (viaScheduler) is the authoritative state for what’s running, ready, blocked, paused, or cancelled. The SQLite database is write-behind persistence — state is written to DB for crash recovery and external visibility, but runtime decisions read from the DAG.
Synchronous state machines in core
TheOrchestrator, Scheduler, MonitorState, and Dag are all pure synchronous types. No async, no tokio, no ACP dependency in core. Every method is fn handle(&mut self, cmd) -> Vec<Event> — trivially testable.
Coordinator is a thin async adapter
The CLI’scoordinator.rs owns the tokio select loop, ACP sessions, and TUI channels. It translates async events (worker completions, TUI messages, merge results, timer ticks) into Orchestrator::Commands, and executes the resulting Events (spawn workers, kill sessions, queue merges).
Signal file protocol for cross-process communication
The MCP server runs as a separate process. It writes to the DB and drops JSON signal files in.enki/events/. The coordinator’s tick loop picks these up via Command::CheckSignals and reacts accordingly.
Orchestrator: Command/Event API
The orchestrator follows a pure command/event pattern. Commands come in, events go out.The orchestrator’s
handle() method takes a Command and returns Vec<Event>. The coordinator executes those events in a cascade loop — spawning workers can fail and produce new events, so it drains in a while !events.is_empty() loop.Data Flow
Here’s how a typical workflow moves through Enki’s architecture:Key Patterns
Hashlines:read_text_file tags each line with {line_num:>width}:{xxh3_hash}|{content}. write_text_file verifies anchors to detect stale edits. Implemented in core/src/hashline.rs.
Two-phase worker completion: Worker finishes (WorkerDone, frees tier slot) → merge runs → MergeDone advances DAG. The scheduler tracks both phases separately for concurrency accounting.
Signal file IPC: MCP server writes .enki/events/sig-*.json. Coordinator polls and deletes on each tick. No fsnotify.
infra_broken flag: If cp fails during worker spawn, coordinator auto-fails all subsequent spawns rather than retrying.
Merger agent flow: On merge conflict, MergeNeedsResolution spawns a separate ACP session with minimal tools working in a shared temp clone. CleanupGuard + std::mem::forget keeps the temp dir alive during resolution.
Database Schema
SQLite (WAL mode) with auto-migration. DAG stored as JSON blob in executions table.Auto-migration runs on every DB open:
auto_migrate() parses the schema const and ALTER TABLE ADD COLUMN for anything missing. No version files needed.MCP Server
JSON-RPC 2.0 over stdio. Role-based tool filtering (planner gets all tools, worker gets status + list only). Available tools:enki_status— task counts by statusenki_task_create— create standalone task (writes DB + signal file)enki_task_list— list all tasksenki_execution_create— create multi-step execution with dependenciesenki_task_retry— retry a failed taskenki_pause— pause an execution or stepenki_cancel— cancel an execution or stepenki_stop_all— stop all running workers
Environment Variables
| Variable | Purpose |
|---|---|
ENKI_BIN | Path to own binary, injected into all subprocesses |
ENKI_DIR | Project .enki/ directory for DB + signal files |
ENKI_SESSION_ID | Scopes MCP tool results to current session |
CLAUDECODE | Cleared on agent spawn to prevent nested-session refusal |
Next Steps
DAG Execution
Learn how the DAG scheduler evaluates dependencies and manages concurrency
Worker Isolation
Understand copy-on-write cloning and filesystem isolation
