Conflict resolution beyond line merges: the ACP bridge's mode/set
re-notify moved inside master's new containment finally (a throwing
presenter must not desync the picker), and session/new keeps the modes
advertisement beside master's plain object return. dsh-mode's boundary
flush moved off the session/event feed onto the loop's interception
seams (agent/prompt-submit / agent/turn-continuation): master's scoped
dispatch made post-commit session observers observe-only — an append
from one re-enters the publishing append — while the two waterfalls
fire outside any log emit and bracket the same boundaries; mode.spec's
boundary helper now dispatches those seams via agentEvents, and the
containment tests stub only the mode/set append. Catalogs, doc graphs,
and the module graph regenerated.
A ModeDefinition is now exactly { section, access? }; unknown keys (a
tools list included) fail loud at load. What plan mode still does: the
guidance section, the exit_plan_mode visibility rule (plan only, both
soft surfaces), the access cap's bash/resolve-mode clamp, and the two
cap-derived pre-execute guards (the bash trio is withheld when no
confining executor can honor the cap; sandbox escalation is denied
while it holds). The general deny-by-default gate and the assemble
allowlist filter are gone: which tools a mode admits is an effects
question, and a hand-maintained name list mislabels it — it must track
every composed tool and rots silently as tools arrive. The dimension
returns as a consumer of effects self-declaration on tool definitions
(MCP ToolAnnotations as the template) — rationale and restart trigger
archived in the RFC's Alternatives/Deferred; the interim guidance-only
non-shell restraint is priced in Consequences.
Exiting plan is now a pure removal (the exit tool + section), which the
delta encoding CAN express: the re-recorded plan-mode fixture pins one
plan-shaped initial header snapshot plus one header-delta instead of
two snapshots.
The stack rebases onto a moved master through its base branch. Beyond
mechanical unions (both branches' demo scripts, example rows, service
roles, tool lists, acp deps, doc budgets — each side fit alone, the
union needs the higher ceilings), three semantic reconciliations:
- The ACP bridge now carries BOTH per-session surfaces: the sandbox
stack's config options + approval answerer and this branch's session
modes; session/new and session/load advertise modes AND configOptions
side by side.
- The feature matrix supersedes the sandbox stance per the RFC's
second-lander rule: session/set_mode and current_mode_update flip to
shipped-by-dsh-mode, config-option rows stay as #169 wrote them, and
§6 records both landed features under the picker-to-modes /
knobs-to-config-options division.
- The snapshot pin grammar (#254: one header snapshot + declared deltas
+ a Markdown prompt golden) gains a symmetric declaration for what a
delta cannot express: expectedHeaderSnapshots — a plan-mode flip
resorts the canonical tool list, so its widening lands as a second
full snapshot, now its own Markdown section. The pin-less-class and
model-turn-only-pin amendments carry over; new fixtures cover the
extended writer paths, and the plan-acp-agent scenarios re-recorded
under the merged composition (the app now bundles the skill tool)
with the suite's refresh mode wired through.
Every packages/*/* README now carries a canonical '## Known Limitations and
Deferred Work' section: condensed, evidence-backed bullets for consumer-visible
gaps (unimplemented features, platform caveats, MVP cuts) and consciously
postponed work (TODO/FIXME/XXX markers, RFC deferrals still open). The ten
pre-existing ad-hoc variants ('What is NOT here (TODO)', 'Deferred',
'Limitations (MVP)', 'Known limitations (tracked TODOs)', ...) are normalized
into the canonical heading.
A new doc-sync gate, scripts/verify-readme-limitations.ts, enforces the shape:
exactly one limitations-like heading per package README, byte-equal to the
canonical h2, with at least one bullet; near-miss headings fail so variants
cannot creep back. Packages with genuinely nothing to declare (dsh-brand,
dsh-timeout, dsh-subagent-mock, dsh-app-boot) are whitelisted in the script and
must NOT carry the section; whitelist entries are validated against the scanned
package set so a rename fails loud.
Wired into the doc-sync chain (package.json) and the run-gates doc-sync leaf
set; the standing rule lands in packages/AGENTS.md and the adding-a-package
cookbook; decision record in
docs/rfc/implemented/process/2026-07-10-readme-known-limitations-gate.md
(RFC index regenerated).
Also fixes two stale '(deferred)' markers claiming dsh-compact-basic is
unimplemented (the dsh-compact seam README's package table and the seam's
module doc comment).
The three-entry cordis.yml (dsh-sandbox-local + dsh-bash-sandbox at a
read-only default + dsh-approval) served over ACP: the first live approval
composition. Recorded snapshot scenarios pin the wire end to end —
config-options advertisement, the mode-switching arc as the suite pinned
header (both switches, the prompt-section delta, one changed-by-the-user
notice per knob, a confined write landing under the switched mode), and
both escalation branches over scripted permissionAnswers (a grant runs
confined under workspace-write; a rejection executes nothing and pins the
fail-closed text). The with-key escalation e2e drives a real model +
real runner + the real bridge answerer, world-verified; ci.yml snapshot
lane and e2e.yml install bubblewrap so the confined replays actually
execute.
Both RFCs move to implemented/ (Decision/Consequences form, deferred
phases tracked in their own sections), with every cross-reference flipped.
The proposal survives contact with the code with three amendments, per
the RFCs-are-proposals rule. (1) A mode transition logs a
request/header-delta only when expressible: adding exit_plan_mode
resorts the canonical tool list, and a pure reordering has no delta
form, so entering plan mode logs the full fallback snapshot — the
attributability claim holds either way. (2) The proposed/ skeleton
converts to the implemented grammar: Proposal → Decision, the roadmap's
staging (now history) drops to the standing Deferred list, and
Acceptance criteria + Risks fold into Consequences (what holds, then the
accepted costs, including the ACP v2 mode-removal migration). (3) The
two recorded scenarios stay pending a with-key session, recorded in
Deferred.
The docs tail completes: the cookbook's plan-mode row upgrades from
sketch to the shipped package, architecture.md gains the ctx.modes
capability row (ceiling 1640 → 1650: a new capability service's table
row does not fit the old budget), and every reference repoints to
implemented/.
Adopts #211 (Code Mode tools: run_code + the code/both-mode snapshot
scenarios). Tool-catalog expectations take the union (run_code +
task_*); the two new pinsHeader fixtures (code-mode-turn,
both-mode-turn) were recorded on master without the task runtime, so
they are re-pinned KEYLESSLY by replaying their recorded chunks against
the merged tree (same procedure as text-turn) — the fixture diff is
exactly the header delta: task tool schemas, the tool:tasks prompt
section, and the bash background wording.
start({ kind, label, owner, run }) preflights everything that can fail
(the attachSurface fence, validation, the owner-cleanup attach) BEFORE
invoking the producer's run() starter, then commits atomically —
'work started but never got a collectable id' is now structurally
impossible instead of a producer try/catch rollback obligation (the
P1 review fix, rebuilt on #185's declare/execute split). Producers
lose their catch-wraps; the leak tests now pin the stronger property
that a failed preflight never spawns anything. TaskRegistration splits
into TaskStart (identity + run) and TaskHooks (cancel/done/readOutput);
docs, type-equiv manifest, catalogs, and both RFCs move with it.
One shared ctx.tasks registry (branded <kind>-N ids, owner-fenced
read/kill/wait/list, attachSurface misconfiguration fence, reported-flag
notice dedup, atomic register) + dsh-tool-tasks (task_output/task_list/
task_kill, completion-notice injection, background prompt habit).
Producers opt in via their own enableRunInBackground config: bash
(stream kind; seam slimmed to resolve/run/start returning a BashProcess
handle, bash_output/bash_kill deleted) and subagent (final-output kind;
done settles after run.dispose()). Owner disposal drains tasks through
the new awaited ctx.agents.onCleanup seam in the loop's disposal chain.
Both RFCs moved to implemented/; docs, catalogs, snapshots re-pinned.
The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.
run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).
The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
The reconstructability RFC is the principle's home: model-visible ⟺
logged in both forms, the mechanism (boundary derivation + header
fold), the enforcement (write-time round-trip guard, the dev
invariant), the corollaries ranked (prefix-cache stability first), the
MiniCode lineage with the provenance arrow inverted, and the
alternatives it beat — including the stateful transmission client
whose three-design archaeology lives in PR #162.
Placements per the one-home-per-fact taxonomy: a standing-order line in
root AGENTS.md (with displacement trims to stay inside the 1,575-word
ceiling), the principle statement in architecture.md § Session Log and
its Turn Flow lines (condensed to the ratcheted 1,630 ceiling), the
request-envelope section in core-data-structures/core.md with the
LlmCallConfig paste, both review-requested FIXMEs
(FIXME(call-config-shape) beside the type, FIXME(catalog-verbs) at the
catalog's drift-gate note), cookbook rows redirected off agent/request
(tool filtering → system-prompt/assemble, plan-mode prompt → sections/
inject()), and the llm/stream JSDoc stating the frozen-request
contract. RFC index and all generated catalogs regenerated.
Review comment on the verify step: dumping every open PR under
--limit 1000 wastes agent context and re-truncates past 1000. Ask the
exact question server-side instead: gh pr list --state open
--base <branch> --json number --jq length, whose output is a single
count that is exact at any number of open PRs. Skill, checklist, and
the cookbook dependent-check line all use the per-branch form.
Codex review caught three procedure defects: the dependent check used
bare 'gh pr list', whose default pagination can hide an open dependent
and make branch deletion look safe (now --state open --limit 1000); the
refresh step merged local master, which 'gh pr merge' leaves stale (now
fetch and merge origin/master); and the quick checklist omitted pushing
the dependent after the local merge.
Commit the stacked-PR landing procedure as a repo skill so every
collaborator's agent discovers it: merge bottom-up, retarget and refresh
each dependent, and delete no branch until the whole stack has landed
(deleting a base branch auto-closes the open PR that bases on it).
The responding-to-pr-review-on-a-stack cookbook guide's dependent-check
step links the skill as the full landing procedure.
GenerateOptions.prefill had no production setter and both adapters
rejected it with LlmError('UNSUPPORTED') — its entire observable
behavior was two throws, each pinned by one adapter test. DeepSeek's
chat-prefix completion is a Beta feature on a base URL neither adapter
targets. ToolSchema.strict was threaded through defineTool, the
registry's schemas() allowlist, the deepseek wire mapping, a per-tool
payload-patching pass in the pi-ai adapter, and a tool-catalog render
row, yet no shipped tool set it and the internal endpoint story for
strict mode was never built.
Remove both fields end-to-end: the vocabulary in dsh-llm, the adapter
guards and wire branches, the dsh-tools threading, the tool-catalog
Strict row, the pinning tests, the core.md pastes, the adapter README
rows, and the cookbook line that used prefill as the UNSUPPORTED
example (now stated generically). The pi-ai payload fixup keeps the
half with a job: pi-ai stamps strict:false on every serialized tool,
so the fixup scrubs it unconditionally for wire parity with the
hand-rolled twin (per-tool set/delete machinery gone). temperature/
stop/maxTokens are untouched — honored end-to-end by both adapters.
Each knob returns with its first real producer: prefill with an
adapter that implements chat-prefix completion, strict with a tool
that wants it and a beta-endpoint story.
RFC: docs/rfc/implemented/simplification/2026-07-04-drop-inert-request-knobs.md
(moved from proposed/, amended to shipped reality); the content-block
vocabulary RFC's consequence line now records prefill as producer-gated.
- session.md § TurnEndReasonMap now carries the full precedence rule
the old architecture.md stated and the loop implements (loop.ts's
stepFinishReason carry-forward): max-tokens wins over completed only;
disposed/aborted/error take precedence. The architecture.md pointer
was otherwise citing an incomplete home.
- The cookbook feature-map compaction row no longer reads as if a
model-facing /compact tool ships: a manual trigger invokes the same
ctx.compact routine; the consumer tool is deferred per the
compaction RFC.
The definitive sweep (audit every AGENTS.md mention in packages/, docs/,
examples/, scripts/) found thirteen more citations of relocated policy
and two citations of rules that never existed as quoted:
- with-key policy comments (web deepseek/perplexity e2e headers) ->
docs/testing.md; real-impl-over-mock comments (acp harness, load,
stream-update specs) -> docs/testing.md; defensive-pattern quotes
(acp index.ts x3, stream-update) -> docs/defensive-patterns.md.
- md-tier repoints: real-api-e2e RFC, tool-schema-catalog RFC,
postmortem 0001 guardrail row, adding-a-package cookbook,
drop-bash-output-spill-files RFC, acp-subagent-backend RFC phrasing.
- Two false attributions dropped in favor of self-contained reasoning:
tool-todo's 'don't validate scenarios that can't happen' and the
bash-stdin-env RFC's 'Don't add features beyond what the task
requires' (neither rule ever existed under those names).
- Citations of the two 'not golden truth' doctrines stay: those bullets
survive verbatim in the root conventions.
Note: packages/support/ui-stdio readline TTY spec flakes under full
coverage on a heavily loaded box (passes standalone and passed the
same tree's coverage run minutes earlier); untouched by this stack.
architecture.md is the behavior map: layering, service map, seam
pattern, and the loop — everything else defers to its owning tier.
- Seam narrations compress to two-to-four sentences plus links to the
RFC and type-catalog homes that carry the detail (turn-end variant
semantics -> session.md, derivation mapping -> session.md, StreamChunk
conventions -> llm-streaming.md + source).
- The MVP feature-to-mechanism checklist moves de-statused into the
extension cookbook as 'The feature -> mechanism map' — mechanisms
only, no implementation-status bolding to rot; the microkernel RFC's
proof-obligation pointer follows it.
- The layering diagram describes layers by family instead of
enumerating packages (the stale 'future plugins: hooks, compaction'
row is gone); the dependency rule defers to packages/README.md.
- The loop pseudocode, the three externally-cited anchors (the
vocabulary, event taxonomy, waterfall semantics), and the filename
are unchanged.
- Budget ratchet: docs/architecture.md 3897 -> 1800 (now 1,797 words);
the doc-tiers RFC's deferred list prunes the item this ships.
Applies the documentation standard to its biggest offender. Every rule
survives as one to three lines plus a link to its durable home; the
stories, duplicate statements, and re-narrations go:
- Situational clusters evict to new homes: docs/testing.md (tiers,
with-key policy, real-over-mock, world-verification, real-entry-path
guards), docs/defensive-patterns.md (the bug-class rules), and
docs/cookbook/responding-to-pr-review-on-a-stack.md (the stacked-PR
review procedure).
- Doc-authoring rules consolidate into docs/AGENTS.md § Writing rules
(current-state-never-history, md-wrap, ts-block compilation, @mode,
catalog same-change, pair same-change).
- packages/README.md drops to the group table + the extension-vs-bundle
dependency rule; the hand ASCII graph yields to the generated
module-graph.md; group READMEs are the canonical per-package map.
- packages/AGENTS.md keeps only its packages-specific rules (export
shape, ctx.get, real-Loader coverage); examples/AGENTS.md repoints
its with-key-policy link; rfc/README.md loses a narrated-history
aside; dsh-code-review / dsh-find-simplifications / verify-md-wrap
references follow the moved content.
- Budget manifest ratchets: AGENTS.md 8200 -> 1500 (now 1,495 words),
packages/README.md 1900 -> 600, packages/AGENTS.md 600 -> 450; the
two new eviction docs join the budget set (testing 800, defensive
550); docs/AGENTS.md raises 1000 -> 1250 for the absorbed writing
rules (the one justified increase). The doc-tiers RFC's deferred list
prunes the two items this change ships.
The write-diff-card fix made write's presentResult return an args-derived
whole-file diff (oldText:null) for a create / unchanged overwrite, but the
DiffResultView contract and its mirrored docs still said `diffs` is ALWAYS
the applied contextual hunks computed from before/after. Correct the type
JSDoc, the write execute-side comment, and the four mirrored surfaces
(tools.md, tools README, acp-feature-support, adding-a-tool cookbook) to
say: typically the applied hunks, or a whole-file diff when there is no
before-image (a create) — and that a mutation returns the diff result even
when it duplicates the call-time card, since a tool_call_update.content
replace would otherwise clobber the diff with the model-facing text.
Regenerate the cordis catalog (source line shift).
Address the applied-hunk-diffs review:
- CRLF write overwrite emitted bogus every-line-changed hunks: write's
`before` was LF-normalized but `after` kept the raw model content, so a
CRLF rewrite of an LF file diffed every line. Normalize write's `after`
to LF so both sides share the diff basis (edit already did). Regression
test proves it fails on the raw-after path.
- The tool-private `meta` payload is now typed `unknown` (opaque) at every
seam instead of `JsonValue`. This drops the `dsh-tools -> dsh-session`
package edge that existed only to name the type, and removes the
`FileDiff` index signature that had been widening the type solely for
JsonValue-assignability. Serializability is still enforced at runtime by
`Session.append`'s isJsonValue check, which was always the real guard.
- Sync the docs the new result/meta surface left stale: ToolResultView's
diff card + ToolExecutionResult.meta in tools.md/session.md type-equiv
blocks, the acp/tools READMEs, and the adding-a-tool cookbook; regenerate
the cordis catalog and module graph.
Replace the "bag of optional fields" tool-presentation types
(ToolCallPresentation / ToolResultPresentation / ToolTerminal) with a
card-tagged discriminated union — the standing FIXME(tool-presentation).
A tool declares one render intent per call/result and the ACP bridge
switches on `card`:
ToolCallView = generic | terminal | diff
ToolResultView = generic | terminal
The `diff` card is new: fs write/edit now emit an ACP {type:'diff'}
content block (an editor's inline diff), which the old shapes could not
express. The bridge also relativizes a file card's title against the
session cwd (mirroring claude-agent-acp's toDisplayPath) while keeping
locations/diff paths raw, and derives the no-capability fenced console
fallback from a terminal result's output. read gains the window-in-title
(`Read foo.txt (5 - 8)`) and an always-set location line, matching the
reference adapter field-for-field.
Migrates all three producer families (tool-fs, tool-bash, tool-todo) and
the sole consumer (the ACP bridge) together — the source does not compile
piecewise. Adds snapshot coverage for the terminal _meta path (a new
capability-advertising scenario) and re-records the fs goldens to show the
diff cards. Applied-hunk (result-time, context-line) diffs need a new
result/event shape and are a follow-up.
RFC: docs/rfc/implemented/architecture/2026-07-02-tool-render-intent-union.md
The loop recorded every model token delta as a durable `assistant/chunk`
session event AND emitted an identical live `agent/stream-chunk` Cordis event
one line later. Same StreamChunk, same turn/step; the emit added only the live
Agent handle, which the sole consumer discarded. This is the boundary-mirror
duplication the event-domain work removed for turn/step boundaries, applied to
the token stream — a follow-up the boundary RFC explicitly deferred.
The premise is settled: chunk persistence is authoritative (the proposal to
stop persisting chunks was rejected — replay/snapshots depend on it), so
`assistant/chunk` on `session/event` is the load-bearing token stream and
`agent/stream-chunk` is pure redundancy.
- Remove the `agent/stream-chunk` declaration + emit; drop the now-unused
StreamChunk import from dsh-agent's types.
- Migrate `dsh-ui-stdio` (the only live consumer; ACP already reads
assistant/chunk off session/event) to render assistant/chunk in its existing
session/event listener. Consolidating to one listener also makes the
inReasoning dim-SGR flag deterministic across chunk/boundary events (they no
longer race across two listeners).
- Repoint the agent-loop tests (cancel/loop) and ui-stdio tests to the
session/event assistant/chunk feed.
- New RFC (implemented/simplification/2026-07-02-remove-stream-chunk-mirror);
amend the boundary RFC's retained-list entry to cross-link; update
architecture, cookbook, event-domain-semantics, the ACP proposal, and the
regenerated cordis catalog.
Snapshot goldens unchanged (ACP never used the mirror), confirming no
editor-facing transcript change.
Bring the interception-seams branch onto current master (via A→B). The
substantive reconciliation is master's compaction `agent/pre-step` serial seam
meeting C's interception seams:
- types.ts: keep BOTH master's `agent/pre-step` AND C's new interception events
(`agent/prompt-submit`, `agent/session-start`, `agent/turn-continuation`→
`ContinuationDecision`); drop the turn-mirror declarations (removed on A).
- loop.ts: the merged per-turn order is `turn/start` → per queued msg
`agent/prompt-submit` (rewrite/inject/block) → (fully-blocked ⇒ zero-step
`rejected`) → per step: drain steering → assemble system prompt →
`agent/pre-step` (compaction, OUTSIDE the step) → `step/start` → single
`deriveMessages()` → model → tools/pre-execute·dispatch·post-execute. No
turn-mirror emits; `closeTurn()` is the A-simplified single-call form.
- Docs (architecture, core.md, agent/agent-loop READMEs, catalog) reconciled to
show C's interception seams alongside `agent/pre-step`, no turn/step mirrors.
- rfc/README: dropped the stale `proposed/` compaction row (master moved that RFC
to implemented/); kept C's new `pre-tool-input-rewrite` proposed row.
- interception.spec.ts: migrated its two `agent/turn-end` reason collectors to
the `turn/end` session event, and ADDED a cross-test proving a
`prompt-submit` rewrite + additionalContext is VISIBLE to an `agent/pre-step`
listener on the same turn — pinning the merged seam ordering (compaction sees
the post-prompt-submit surface, not stale history).
Complete the boundary-mirror removal begun with the step mirrors: drop
`agent/turn-start` and `agent/turn-end` from the agent event taxonomy. Turn and
step boundaries are now read exclusively off the durable `session/event` feed
(`turn/start`/`turn/end`/`step/start`/`step/end`) — there is no `agent/*` mirror
for any boundary.
- loop.ts: delete both turn emits; `closeTurn` loses its `emit` parameter and
its now-unreachable idempotency guard (it is called exactly once per turn, on
mutually exclusive normal/catch paths); `failTurn` loses the dead post-close
branch that only a throwing turn-end LISTENER could reach.
- ui-stdio: render turn boundaries from `session/event`, recovering the short
agent label from an `agent/created`→id map (the `turn/start` event carries only
the turn number, and the session id is not reliably the agent id). ui-stdio is
a disposable test REPL, so this migration retires the sole justification the
event-domain-semantics RFC gave for KEEPING the turn mirrors.
- Tests: reason/turn-number collectors and the boundary-ordering test now read
`session/event`; the throwing-turn-boundary-LISTENER tests are deleted (that
code path no longer exists). A new test covers the outer-catch disposed branch
via a pre-step listener that disposes-then-throws (the surviving real path).
- Docs: promote the "remove agent boundary mirror events" RFC to implemented
(amended/narrowed — `agent/steering` is RETAINED, not a boundary mirror);
update the event-domain-semantics + turn-enclosure RFCs, architecture.md, the
cookbook, the ACP/agent/ui-stdio prose, and regenerate the cordis catalog.
`agent/steering` and `agent/stream-chunk` are explicitly out of scope (not
durable-boundary mirrors). ACP is unaffected — it already settles from the log's
`turn/end` + `agent/status`; snapshot goldens are byte-unchanged.
Codex's PR-C review found two (A) blockers:
- tools/post-execute could corrupt the protected outcome. postExecute passed the
mutable `result` to listeners and then read result.callId / spread result on the
return paths, so a listener mutating the reference (flipping isError, rewriting
callId, injecting an error) escaped the decision channel. Now the authoritative
callId/isError/error are SNAPSHOT before the waterfall and the return value is
rebuilt from the snapshot + the typed PostToolDecision — the decision is the only
sanctioned way to change the outcome, and callId is always exec.callId. Added a
regression test that mutates the result reference and asserts it has no effect;
proven to fail red on the unfixed code.
- Public docs/JSDoc still advertised the removed `tools/execute` waterfall after the
split. Swept every current-state reference to tools/pre-execute + tools/post-execute:
the ToolRegistry class JSDoc (and the regenerated catalog), loop.ts's ASCII flow
(also added the prompt-submit/session-start steps it was missing), the package-map
READMEs (packages, core, agent-core), core-data-structures core.md/tools.md, the
bash + acp + invariants src/READMEs (the deferred permission gate is the
tools/pre-execute deny/ask seam now), the cookbook, and the implemented RFCs whose
factual seam catalog drifted. codec.ts's totality prose now lists `rejected`.
Proposed-RFC references are left as-is (frozen proposals, validated when built).