One Host · many Clients · JSON-RPC codepapr-host/1

There is one product backend: the workspace Host

Desktop, TUI, CLI, tests, and a future WebUI are JSON-RPC clients of the same Host. They differ only in transport and initialize.capabilities. Tauri is the desktop shell, not the Agent execution plane. How a request is stacked: context layering.

Three planes: control · state · execution

When debugging, ask first: is this scheduling, authoritative state, or the tool/model loop?

CONTROL

Control plane

Session lifecycle, SessionTurnLock, cancel, permission / question / MCP confirm, client registry. Desktop / TUI: agent/startTurn. CLI: blocking agent/chat (cancelOnDisconnect: true). /goal runs GoalRunner without holding the turn lock for the whole loop. Live-session LRU targets 16 and never evicts a busy session; agent/cancel stops that session's turn and bash jobs only.

STATE

State plane

Authority is workspace .CodePapr/project.sqlite (node:sqlite first): messages Archive, context_surfaces, memory_entries, todo. VFS overlay lets terminal read see unsaved desktop buffers. HostEventBus is monotonically sequenced.

EXECUTION

Execution plane

agentRunner builds requests, runs tools, subagents via runSubagentSession, compact, Recall. bash (macOS SBPL sandbox), browser (system Chrome CDP), MCP (stdio / SSE / Streamable-HTTP), LSP all live on Host. workspace/list / read have entry, time, and byte budgets; LSP / MCP frames are capped.

Desktop / TUI / CLI / tests / future WebUI │ JSON-RPC 2.0 Unix socket / named pipe ▼ Workspace Host (Source of Truth) Control: startTurn / chat / startGoal / cancel / compact State: sqlite + EventBus(sequence) + overlay Execute: agentRunner → LLM + tools + GoalRunner

CQE: command, query, event

Method names are frozen in protocol.ts. Do not invent aliases in the UI. Reconnect recovery only trusts sequence.

Command

agent/chat agent/startTurn agent/startGoal agent/cancel agent/compact agent/runVerifier agent/app_run + permission/question/MCP decisions. Mutate state.

Query

agent/getEvents (replay with afterSequence, plus hasMore / oldestSequence / replayUnavailable) agent/turnStatus agent/goalStatus agent/pendingInteractions workspace/read. Read facts.

Event

agent/stream: text / reasoning / tool-start / tool-end / turn/* / goal-progress / goal-finished / context-compacted / context-pruned / permission, question, MCP. Each event has sequence.

01

Turn

One user utterance. Desktop/TUI startTurn, CLI chat. Wait for turn/finished.

02

Goal

User /goal. Host runs plan → worker chat → Verifier. Wait for goal-finished; inner turn/finished is not the Goal end.

03

Compact

checkpoint writes Surface (started→completed). prune only trims live-log tool noise. Archive user text is untouched.

04

Reconnect

A dead Client is not a dead Session. After lease reconnect, getEvents(afterSequence). Drop already-seen sequences so optimistic UI does not flash twice.

Three clients, one Host

Capabilities live in initialize.capabilities. A client without a capability must not try to paint permission dialogs or .papr panels.

DESKTOP

Tauri shell + HostBackedAgent

Chat uses startTurn; /goal calls startGoal. Reverse RPC: permission, question, MCP confirm, tools.app. Monaco overlay syncs to Host. Worker path is rollback only.

window.permission · question · mcpConfirm · tools.app
TUI

Ink + startTurn

Same startTurn. /goal upgrades to Goal and waits for goal-finished. Has permission/question, no .papr. Transcript in .CodePapr/tui/; authority is still sqlite.

window.permission · question
CLI

One-shot agent/chat

Blocks until the turn (or the whole Goal) ends. cancelOnDisconnect: true: closing the terminal cancels. Default session cli-<pid>.

agent.stream · no interactive windows

Authority is sqlite, not the UI

The UI may paint an optimistic user message. Replay, compact, memory, and checkpoints take the Host database as truth.

messages

Archive originals. Tool results fold into assistant tool_invocations and hydrate back into synthetic tool messages. role=tool rows are not stored.

context_surfaces

The selection authority for the model history. Compaction rows go started → completed; leftover started becomes interrupted on Host boot.

memory_entries

The only memory ledger. Short instructions enter Bootstrap; procedures are recalled per turn; citation / untrusted never auto-recall. Legacy memory.json is migrated once and never written back.

EventBus

Ring buffer + monotonic sequence. Desktop deliverHostAgentStream drops sequence ≤ last.

How to extend

Change the owner of the authority, not the nearest React file.

New RPC / stream type

Freeze it in packages/@codepapr/host/src/protocol.ts, then wire inProcessHost.ts. UI only maps names; it does not invent them.

New tools

Host agentTools. MCP uses Host transports (stdio / SSE / HTTP). Do not add a second Agent tool stack in Rust.

Bytes sent to the model

core ImmutablePrefix + Host agentRunner. Desktop sends raw user text, mode, images, Recall insertions.

Goal behavior

hostGoal.ts + hostVerifier.ts. TUI/CLI must not expand /goal into a one-shot prompt.

Sandbox

macOS: Host SBPL on by default. Linux / Windows: no kernel sandbox; static reject of ~ / $VAR / .. / unauthorized absolute paths. CODEPAPR_HOST_SANDBOX=0 turns off SBPL and uses the same static guard.

How to optimize (highest leverage first)

1. Prefix cache

01 Prefix holds stable system + sorted tool defs + sampling. Skills / memory / character live in 02 Bootstrap. Adding or removing tools splits the cache. Prefix hits can only be measured against a real model.

2. When to compact

Near the soft budget, checkpoint (summary + tail). Tool noise uses prune. Do not compact every turn. On Surface failure, keep the last completed generation.

3. Goal and depth

The Goal outer loop does not hold the lock; each worker turn does. Root task must use currentDepth: 0 or exploreMaxDepth: 1 blocks explore. Recall drops untrusted; checkpoint bodies are weighted into the corpus.

4. Bounded I/O

workspace/list uses Dirent with a 4000-entry / 250ms budget. workspace/read honors maxBytes, streams line ranges, and rejects NUL. LSP / stdio MCP reassemble chunked frames (8MB / 16MB / 64 pending); overflow kills the child.

Intentionally unmerged: Host headless browser uses system Chrome CDP; desktop still has Rust headless_chrome. synthetic: true exists only in the desktop optimistic layer, not the Host Archive. Do not treat these as missing P0s.

Code: inProcessHost.ts, agentRunner.ts, hostGoal.ts, hostProjectStore.ts, hostRecall.ts, hostSandbox.ts, protocol.ts. Protocol: docs/HOST.en.md. System design: docs/ARCHITECTURE.en.md.