Plan mode's stage 2 (RFC 2026-07-07-plan-mode). The exit tool: one required plan argument (the durable log artifact), execute re-checks the folded mode, then conducts the review over the user-interaction seam — one single-select question (Approve / Keep planning) with free text open — so an approval appends mode/set back to default in-turn and every other outcome (keep-planning feedback verbatim, aborted, no provider) returns the corrective isError with the mode unchanged. presentCall is a generic card titled by the plan's first heading carrying the plan markdown; over ACP the review rides the ask_user elicitation flow, in the terminal the stdio prompt queue — no approval-seam dependency. The ACP bridge maps the picker 1:1 onto ctx.modes (opportunistic, a type-only peer edge): session/new + session/load advertise availableModes/currentModeId, session/set_mode validates through set() and echoes an optimistic current_mode_update (the pending mode IS the selection; the logged mode/set lands at the boundary and, matching, is not re-sent), and a session/event listener re-notifies on each logged flip that differs from the last sent — the tool-driven exit updates the picker. The feature matrix rows move from 'not modeled' to the picker-to-modes / knobs-to-config-options division, with the ACP v2 removal direction recorded as a mechanical-migration risk. The snapshot harness gains the setMode/setModeExpectError ops and a scripted elicitationAnswers FIFO (cancel on exhaustion; a stray choice string reaches the agent verbatim as a non-consenting custom answer, so a scenario bug fails safe). The suite factory's header-pin requirement now applies only to model-turn scenarios — a protocol-only suite has no header content to anchor. examples/plan-acp-agent is the live composition; its keyless modes-advertise scenario pins the wire surface (advertisement, both set_mode round-trips, unknown-id rejection). The recorded plan-mode approve/reject arc awaits a with-key recording session; its texts are pinned at the unit tier meanwhile. examples/AGENTS.md ceiling 653 → 680: the new example's required smoke row does not fit the old budget.
ui/ — editor/client integration surfaces
Integrations that expose the agent to an external editor or client. These are product packages: a real surface a user drives the harness through.
| Package | Role | ctx key |
|---|---|---|
acp/ |
Agent Client Protocol bridge: serves the agent to an ACP editor (Zed) over JSON-RPC stdio | (drives ctx.agents/ctx.sessions) |
user-interaction/ |
Abstract human question/answer seam used by UI-backed confirmation tools | ctx.userInteraction |
tool-ask-user/ |
Model-facing ask_user_question tool over ctx.userInteraction |
(registers on ctx.tools) |
stdio-agent/ |
Terminal stdio chat APP: the agent-core spine + console logger + readline UI + a pre-created main agent, with a bin |
(composition + bin) |
acp-agent/ |
ACP server APP: the agent-core spine + JSONL persistence + the acp bridge (no stdout logger), with a bin |
(composition + bin) |
app-boot/ |
Shared boot glue for the two app bins: .env loading, fail-loud Loader guards, snapshot-aware config resolution, the settle-the-tree boot sequence |
(library for the bins) |
A UI integration is a client-driver plugin, not a loop change and not a capability seam: it consumes the existing agent/* event taxonomy and the dsh-agent factory. The readline UI is the unstructured analogue of the acp bridge and lives INSIDE the stdio app (the stdio-chat module of stdio-agent/): it is scaffolding for that one front door, not an independently swappable integration, so it carries no package boundary of its own.
user-interaction and tool-ask-user live here because asking a human is a UI-backed product affordance, not part of the providerless core spine. The seam remains provider-neutral (ctx.userInteraction), while the tool is the model-facing consumer and the app/bridge packages provide concrete providers.
stdio-agent and acp-agent are the two app packages: each composes the core/agent-core spine with its coupled front-door cluster (and owns the boot bin), so a leaf cordis.yml is the swappable backends plus one app entry plus any optional product tools. They live in ui/ because each IS a user-facing front door; the stdout-purity coupling (logger vs. no logger) becomes a property of the artifact rather than a leaf convention.