Architecture and runtime
One Bun engine, two clients, durable effects.
One engine, two clients
Section titled “One engine, two clients”The Electron desktop and the CLI both present state and submit versioned commands to the same Bun Engine Host. The Engine Host composes the runtime; packages own reusable behavior. A client does not gain execution authority by displaying a tool, model, or approval.
In the desktop, the React renderer talks to the Electron main process over IPC. Main owns windows, native integration and process supervision; through a small native guardian it runs the Bun support host and engines as supervised children. Electron’s embedded Node never runs the engine. The Swift app is gone.
flowchart TD Renderer[Desktop renderer] --> Main[Electron main process] Main --> Support[Desktop support host · Bun] CLI[CLI] --> Protocol[Versioned host protocol] Support --> Protocol Protocol --> Host[Engine Host composition] Host --> Turn[Agent turn state machine] Turn --> Context[Trusted context planning] Turn --> Routing[Provider routing and adapters] Turn --> Tools[Tool runtime and policy] Host --> Workflows[Tasks and registered-workflow queue] Tools --> Execution[Durable attempts and receipts] Routing --> Execution Workflows --> Execution Execution --> SQLite[(SQLite execution authority)] Execution --> Transcript[(Canonical transcript)]The arrows show logical ownership/data flow, not permitted package imports. The exact import rules are in dependency rules. The generated composition map and request traces locate concrete source owners.
Engine processes
Section titled “Engine processes”| Process | Serves |
|---|---|
| Chat engine | All of a workspace’s chats and their read-only subthreads, one per workspace root, Profile, permission mode and extra folders |
| Worktree engine | A writing subthread, which works in its own Git worktree |
| Agora engine | The browser chat, with its browser-only toolset |
| Utility engine | Helper turns such as chat titles: bare, no tools, memory or MCP |
| Reporting and inspection helpers | History inspection and read-only usage reporting |
Each turn carries its own model route (turn.submit and turn.continue take
route), so chats on different models share one engine and no chat inherits
another’s model. Approvals and questions belong to their own session. When a
shared engine stops, every chat it served moves to saved-state review; turns
are durable, so committed work is kept. Each engine keeps one long-lived
MemPalace worker for its memory index, stopped after two quiet minutes.
A normal turn
Section titled “A normal turn”- The host validates and admits a request against the current session and scope.
- It freezes the turn’s model route and builds context from trusted history and permitted project inputs.
- A provider adapter normalizes its stream into model events. Provider wire objects never become transcript or frontend protocol objects.
- Tool requests enter the common validation, policy, approval, and execution path.
- Durable results and verified payloads become ordered conversation events.
- Clients project the result, usage, changes, and any recovery state.
The context window
Section titled “The context window”Standing context budgets (instructions, catalog, history) follow the model’s
window.
A request keeps the provider’s cached prefix: later instructions travel as
host-marked user text instead of edits to the system block, OpenAI-compatible
hosts receive a prompt_cache_key, and estimator calibration is persisted so
a restarted engine does not misjudge a half-empty window. Automatic
compaction is judged on the whole chat before any reduction; a summary given
up before dispatch is recorded on the receipt and shown in the thread rail’s
Context card.
Capacity summaries chain until the request fits, and the
presentation tail sits at the start of the turn’s tool activity.
Why there are two storage authorities
Section titled “Why there are two storage authorities”| Data | Authority | Recovery implication |
|---|---|---|
| Conversation meaning | Fingerprint-chained canonical transcript | Replay only validated complete records |
| Whether an effect may run or resume | SQLite execution rows, attempts, receipts, leases | Reconcile exact durable facts before doing more work |
| Conversation query views | Rebuildable SQLite projections | Repair only from valid canonical history |
| Artifacts and attachments | Referenced payloads with ownership and integrity checks | Missing or mismatched bytes cannot be treated as valid output |
Current versions and supported migration inputs are listed once in project status.
Failure and recovery
Section titled “Failure and recovery”flowchart TD Interrupted[Interrupted work] --> Inspect[Inspect durable attempt and receipt] Inspect --> Committed{Committed result exists?} Committed -->|Yes| Deliver[Deliver existing result] Committed -->|No| Safe{Safe continuation authorized?} Safe -->|Yes| Resume[Resume through execution owner] Safe -->|No| Review[Require explicit recovery decision] Review --> Close[Reconcile or cancel interrupted work] Close --> Idle[Confirm session can accept new work]No receipt means no invented success. A timeout does not authorize repeating an external effect. Recovery UI is explained in operations; the precise contract is session recovery.
Constraints that must survive refactoring
Section titled “Constraints that must survive refactoring”- One effect path: policy, durable attempt, handler, receipt, semantic delivery.
- One runnable workflow queue for registered workflows; clients do not create private schedulers.
- Provider-specific behavior stays inside adapters.
- Scope, policy ceilings, and the turn’s model route are frozen at admission.
- Unknown versions, corrupt authority, unsafe paths, and stale revisions fail closed.
- Processes and native resources have an owner that drains and reaps them.
See durable effects, core contracts, and storage/execution before changing these boundaries.
Related
Section titled “Related”Decisions behind this page: 0147, 0189, 0223, 0229, 0233, 0252, 0264, 0273, 0274 — see the decisions index.