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.
A ModeDefinition may declare access: the widest sandbox access shell
commands run under while the mode holds, on the SANDBOX_MODES ladder.
The bash seam gains the resolution point to hang it on: BashExecutor.
resolveMode(session) folds override ?? default and dispatches the new
bash/resolve-mode waterfall; dsh-tool-bash consults it at both the
stamping site and the escalation baseline; dsh-mode's clamp listener
takes the ladder minimum per call. Two independent log folds compose at
read time — the mode never writes the sandbox knob, so the two switch
in any order and the knob re-emerges intact on exit.
The built-in plan definition ships access: read-only with the bash trio
allowlisted CONDITIONALLY: both policy layers admit bash/bash_output/
bash_kill only while a confining executor is mounted (an unconfinable
shell cannot honor the cap), and a bash call carrying sandbox_permissions
under a cap is denied at the gate — no widening mid-mode; the widened
step belongs in the plan.
examples/plan-acp-agent swaps bash-local for sandbox-local +
bash-sandbox (workspace-write default, clamped read-only inside plan)
plus the approval seam; the re-recorded plan-mode arc runs a real cat
inside plan under the clamped sandbox, and modes-advertise now pins the
sandbox-mode and approval config options. RFC amended to the landed
shape (access cap section, orthogonality FAQ, deferred item resolved
into effects self-declaration).
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.
Rewrite the agent-scope RFC with executable examples and an explicit security non-goal. Harden subagent scalar and depth validation, and pin live tool-filter semantics across code, tests, and generated docs.
Queue worker results before settlement cleanup, claim terminal and death boundaries before provider callbacks, and close late-message admission.
Make child cancellation and disposal reentrancy-safe across the workflow bridge and generic subagent wrapper, with adversarial regression coverage and RFC documentation.
Make Cordis construction and teardown ownership reentrancy-safe, then carry caller and provider ownership through reservation, setup, publication, quiescence, and sentinel retirement.
Stabilize registry carriers and factory/workflow boundaries, add adversarial lifecycle regressions, and align the rewritten RFC plus generated contracts with the enforced behavior.
Drain idle injection flushes before agent teardown, snapshot approval and subagent provider inputs, and gate subagent lifecycle events on real child readiness. Align the RFCs and generated contracts with the hardened behavior.
Review finding, valid — the previous SDK fix covered only the
non-default branch: in the default mode under Code Mode the wire filter
dropped exit_plan_mode but the registry-rendered tools:sdk section
still advertised its binding, offering default-mode agents a call that
can only error and breaking the byte-identical claim (a no-dsh-mode
deployment's registry never saw the tool, so its SDK never listed it).
The SDK re-render extracts to one helper both branches share: the
non-default branch passes the mode's visibility rule, the default
branch hides exactly the exit binding. The pinning test now compares
the default-mode SDK byte-for-byte against a bare deployment without
dsh-mode — the strongest form of the invariant the RFC states.
Review follow-up on the residual the previous commit accepted — and the
acceptance was wrong, because the fix is clean: in Code Mode the SDK
section IS the soft surface (the wire carries only run_code), section
text resolves in assemble's base, and renderToolsSdk is an exported
pure renderer. The outermost wrapper therefore re-renders tools:sdk
from the same visibility predicate the wire filter applies (allowlist,
exit-IFF-plan, minus run_code mirroring the registry's own exclusion):
a plan-mode program is documented exactly the callable bindings — read
and the exit, never the denied write. The default mode leaves the
section untouched (absence of policy), both pinned by tests.
The soft layer's promise — the model is never encouraged toward a tool
the gate denies — now holds in Code Mode too; the only remaining
prompt-honesty residual is a prepend-after-load assemble listener,
where the gate still covers execution.
Review finding, valid: under the registry's Code Mode the assembly's
only wire tool is run_code, which the plan allowlist filtered out —
leaving the model with NO tools at all, the exit review included. The
composition exists today (the acp-agent example ships a code-mode
overlay), so plan mode bricked it outright.
run_code is a transport, not a capability: every bridged sub-call is
serialized back through ToolRegistry.execute() carrying the same agent,
so tools/pre-execute judges each capability individually — exactly like
native calls. Both layers now exempt it by name: the filter keeps it
visible (tests pin plan-mode Code Mode assembly = ['run_code']) and the
gate passes the wrapper while the same run's write sub-call still
denies with the plan-mode reason.
Documented residual, same class as the prepend-after-load one: the SDK
section renders from the registry's store, so a plan-mode program may
be offered bindings whose dispatch the gate then denies — nothing runs
that a native call could not.
Review finding at the seam of two surfaces this branch added to the
same stdin: with an ask_user_question (or plan-review) prompt active,
the line handler dispatched every line as the answer first, so
'/mode plan' typed mid-question was recorded as free-text feedback —
model-visible in the tool result — and the mode never changed. Command
handling now runs before answer dispatch: the command executes, the
question stays pending and still owns the next non-command line. A
literal '/mode…' free-text answer is the trade-off deliberately spent —
a swallowed command that becomes review feedback costs far more than
that contrived answer shape.