commit - a74847ebc13a55884bc2f3a8b311c83548d55908
commit + 98843d2184110b08bc0ab8759ba7aef81f32b600
blob - ba088d2ecc6c709a83f441056a1a7bfab618a34f
blob + 526917744516c4cc3b90e94fbe98a5f519ed063a
--- CONTEXT.md
+++ CONTEXT.md
_Avoid_: main chat, primary conversation
**Turn**:
-One user submission through to the assistant's completed response,
-including all tool calls in between.
+One prompt submission through to a terminal outcome, including every
+Generation and Tool call in between.
_Avoid_: round, exchange
+**Generation**:
+One Provider request and its streamed outcome within a Turn; Tool results
+may extend the Projection and cause another Generation before the Turn ends.
+_Avoid_: model turn, provider turn
+
**Projection**:
The conversation state as shaped for and sent to a provider's API.
_Avoid_: history, context (alone)
fugu-api for model keys, fugu-web for the search token.
**Coordinator**:
-`bin/fugu` — spawns workers, owns conversation state, turn loop,
-journal, and the authority map; parses no raw hostile bytes. The
-only setgid `_fugu` binary; sheds the group after reading config.
+`bin/fugu` — spawns workers, owns the Lead's Projection and Turn
+orchestration, the Session Journal, and the authority map; parses no raw
+hostile bytes. The only setgid `_fugu` binary; sheds the group after
+reading config.
**Control fd**:
The fixed descriptor (`FUGU_CTL_FD`, 3) each worker's coordinator
A wire protocol (anthropic or openai dialect) plus endpoint and key;
named provider blocks coexist with the implicit default.
+**Search adapter**:
+The provider-specific request and response translation used by brokered
+web search; it owns no transport, credential, SSRF guard, or access policy.
+_Avoid_: search provider (when naming the adapter seam)
+
**Effective window**:
The context-size denominator for `/context` and the status gauge:
`context_limit`, else provider-reported, else compiled table, else
- The **Coordinator** alone talks to every worker; **Workers** never
talk to each other (authority map, I10).
-- A **Turn** belongs to a **Session**; only successful (or
- iteration-capped) turns survive in the **Journal**'s replay.
-- A **Subagent** runs its turns through the same **fugu-api**
+- A **Turn** driven by the **Lead** belongs to a **Session**; only successful
+ (or iteration-capped) Lead Turns survive in the **Journal**'s replay. A
+ Turn run by a **Subagent** is ephemeral and never enters the Journal.
+- A **Subagent** runs each Turn through the same **fugu-api**
custodian but executes tools only in the read-only **fugu-tool**
role. Correlated model connections make progress concurrently inside
that one custodian; the Coordinator queues brokered web calls and
blob - 364d411841b75e2dd4c89f93378708b6c5973d99
blob + c43906d6ddbf0e54a107b625cd3db44f786c9a02
--- docs/adr/0001-process-topology.md
+++ docs/adr/0001-process-topology.md
separations but leave process count and topology free. We chose a
vmd-style hub: `bin/fugu` is the coordinator — it parses the
configuration (shedding the `_fugu` group immediately after), spawns
-every worker by fork+exec, owns conversation state, the turn loop, the
-session journal, and the authority map — and never parses raw hostile
-byte formats. Workers under `libexec/fugu/`: **fugu-tty** (curses and
-line front end; inherits the terminal fds; no secrets), **fugu-api**
+every worker by fork+exec, owns the Lead's Projection and Turn
+orchestration, the Session Journal, and the authority map — and never
+parses raw hostile byte formats. Workers under `libexec/fugu/`:
+**fugu-tty** (curses and line front end; inherits the terminal fds; no
+secrets), **fugu-api**
(model-credential custodian; TLS, SSE/JSON codecs; no filesystem or
exec after the CA bundle loads), **fugu-web** (search-credential
custodian; fetch, HTML-to-text, SSRF guard), **fugu-tool** (tool
The coordinator retains `proc exec` at steady state to spawn subagent
executors and the editor room; this is conforming because it parses no
raw untrusted input (vmd-parent precedent).
+Sharing the canonical Turn Implementation with the read-only Subagent Role
+does not move either runtime or add a channel; ADR-0005 records that
+refinement.
blob - /dev/null
blob + ff4e8bf42c4b85e205bc6cdbf7705a740e073a87 (mode 644)
--- /dev/null
+++ docs/adr/0004-search-adapter-boundary.md
+# Keep web search behind a transport-free adapter
+
+Although Kagi is the only implementation today, brokered web search will keep
+provider-specific request and response translation behind a **Search adapter**
+boundary. The adapter builds the search request shape and interprets success
+and error responses; `fugu-web` retains TLS, deadlines, credential custody and
+attachment, SSRF and access policy, and output caps. This accepts the small
+cost of a single-implementation seam so that adding another search service does
+not require reopening the security-sensitive transport path.
blob - /dev/null
blob + 50238fa013fd60d244a8ed388c77c94e926af4df (mode 644)
--- /dev/null
+++ docs/adr/0005-canonical-turn-mechanics.md
+# Share canonical Turn mechanics without centralizing a Role
+
+The Lead and Subagent have separate provider-and-tool loops with duplicated
+request construction, Generation collection, message pairing, bounds, and
+stop handling. They have already drifted on malformed tool arguments and
+empty Generations. We will replace those loops with one provider-neutral
+Turn Implementation linked into both the Coordinator and the read-only
+`fugu-tool -r` Subagent Role. This shares mechanics, not runtime authority:
+the Coordinator retains the Lead and its Journal, while `fugu-tool -r`
+retains each ephemeral, kernel-confined Subagent Turn. No Role, credential,
+channel, or worker-to-worker communication is added; this refines rather than
+changes ADR-0001.
+
+## Interface and ownership
+
+The canonical Turn Module owns Generation assembly, provider-neutral request
+and message construction, complete-object validation for tool arguments,
+tool-use/tool-result pairing, shared byte and call limits, empty-Generation
+validity, and the request-tool-result iteration rules. Provider codec Modules
+retain wire translation. The tool Interface is batch-shaped so the Lead can
+preserve concurrent Subagent waves and stable result ordering. An absent
+no-argument tool input becomes `{}`; a non-object input or an empty normal
+Generation fails before a canonical assistant message is accepted. Provider
+length stops and iteration caps follow their explicit policy rather than being
+confused with those invalid Generations.
+
+Two real Adapters supply what legitimately varies. The Lead Adapter supplies
+persistence, cancellation checkpoints, steering, mutable and brokered tool
+execution, and user-facing observation. The Subagent Adapter supplies its
+ephemeral Projection, process cancellation, read-only tool surface, brokered
+web access, and bounded final report. Their deliberately different
+50-versus-25 iteration behavior, Subagent conclusion Generation, tool
+authority, and reporting remain explicit policy; they are not duplicated
+mechanics disguised as policy.
+
+The Lead Adapter uses a deep Turn transaction Interface as the only owner of
+pending Projection messages, matching Journal records, background-job
+`BEGIN`/`COMMIT`/`ABORT` participation, and the prior-request cache cursor.
+Every accepted message crosses that Interface once, active Generations see
+committed plus pending messages, and commit or abandon are the only terminal
+operations. The Coordinator still chooses cancellation under its atomic
+signal gate; the transaction performs the chosen terminal operation exactly
+once. A cache cursor advances when a Projection is sent, never merely because
+the resulting Turn commits.
+
+Commit durably records the Journal Turn end before committing background jobs,
+then makes the pending Projection state final. Abandon first stops a live
+synchronous executor when necessary, aborts pending jobs, records the abandoned
+Turn, discards every pending message, and restarts provider caching cold.
+`/clear`, `/compact`, `/model`, and resume reset Projection and cache state only
+through the same owner. Background-job participation is a private
+remote-but-owned seam: production uses the existing imsg Adapter and
+transaction tests use an in-memory Adapter without exposing that port through
+the Turn transaction Interface.
+
+## Consequences
+
+The existing behavior matrix is characterized before either loop is deleted,
+so accidental drift is separated from intentional policy. Extraction starts
+with a provider-neutral Generation collector over the existing length-carrying
+events, then lifts the complete Turn engine after the protocol work in ADR-0006
+and the Turn transaction are stable. Tests exercise both Adapters through the
+same Interface, while adapter-specific tests pin the intentional policy
+differences and the Lead's commit/cancellation ordering.
+
+Presentation remains Lead Adapter policy during this migration. A standalone
+presentation Module would currently only move branches already divided among
+the Coordinator, `output.c`, and `fugu-tty`. TTY, plain, JSON, and NDJSON may
+become Adapters only after the canonical Turn Module exposes a stable tagged
+Turn-event Interface that lets them own filtering, ordering, streaming, and
+final aggregation.
blob - /dev/null
blob + 1d4cf3462691dd6aa23db4d88835ac20c31b1846 (mode 644)
--- /dev/null
+++ docs/adr/0006-typed-imsg-exchanges.md
+# Deepen imsgev and keep logical exchanges typed
+
+The existing `imsgev` Module is the transport seam, but normal callers can
+ignore composition failures and many Roles duplicate payload extraction,
+chunking, terminal handling, and receive-state validation. A failed middle
+chunk can therefore be accepted as truncated complete data, while a failed
+terminal frame can leave a receiver waiting indefinitely. We will deepen
+`imsgev` rather than layer a second universal protocol Module beside it.
+
+`imsgev` owns checked normal sends, explicitly best-effort teardown sends,
+length-carrying payload views that distinguish an empty payload from extraction
+failure, and bounded checked chunk emission. A normal send failure cannot be
+silently discarded or followed by a successful terminal frame for the same
+logical exchange. Variable-length logical payloads are either bounded to one
+checked frame or sent incrementally.
+
+Correlation, ordering, accumulation caps, and terminal validation remain in
+narrow typed exchange Modules for the protocol families that actually share
+those rules. Provider requests, tools, web requests, editor composition, and
+UI streams do not acquire one parameterized state machine merely because they
+all use imsg. `proto.h` remains the single auditable message taxonomy and
+authority map, and receivers retain fail-closed, length-carrying Role
+validation under I10 and I13.
+
+## Considered options
+
+- *Transplant fugu1's arbitrary-length logical-message reassembly.* Rejected:
+ fugu2 deliberately processes several bounded payloads incrementally and has
+ a different hub topology. The useful lesson is centralized checked framing
+ and sequence ownership, not the old wire format.
+- *One Module aware of every `FUGU_IMSG` type.* Rejected: its Interface would
+ mirror all exchange policy, obscure the authority map, and become shallower
+ as protocols diverged.
+- *Fix ignored return values call by call.* Rejected: framing, send failure,
+ and payload-view rules would remain non-local and drift again.
+
+## Consequences
+
+Protocol hardening precedes the Turn refactor in ADR-0005 because it is bounded
+and stabilizes the events that the canonical Generation collector consumes.
+The protocol test surface injects failure at every chunk and terminal position
+and traces wrong sizes, out-of-order data, duplicate or missing terminals,
+wrong correlation, overruns, and data after termination. This decision does
+not change ADR-0001's process topology, ADR-0002's libevent dispatch, or
+ADR-0003's bespoke hub-and-spoke privsep Module.