Master's verify-export-jsdoc landed mid-stack; complete the six missing
@param/@returns on the workflow trio's public surface (and the services
catalog they regenerate into).
Three Codex findings on json-schema.ts, one discipline:
- required-declared and every value check now use Object.hasOwn — 'in' let
inherited names (toString) satisfy required, dodge additionalProperties:
false, and validate a declared property against the value's prototype
member instead of a carried one
- isObjectLike now means PLAIN JSON object (proto chain of at most one link,
realm-agnostic): a Date annotation or a Map-as-properties no longer passes
structurally and serializes lossily — they fail loud as subset violations
- startInProcessRun asserts BEFORE the defensive structuredClone, so a
hostile schema fails as OutputSchemaError, never a raw DataCloneError
Also the type-equiv catalog gap: tools.md gains the structured-output subset
vocabulary (4 blocks) with matching manifest entries. The driver index also
drops the runtime internals from its public re-export (runs acquire it
internally; no external consumer remains — see the following commit).
Review follow-up (#196): a listed name with no registered tool was silently
ignored; misconfiguration must block work instead. The check lives in the
assembly — the earliest moment the registered tool set exists (tool plugins
register after the service constructs) and the only universal one (cordis has
no "all plugins loaded" event; registrations change at any time). assemble()
is now async so the throw surfaces as a rejection rather than a synchronous
escape from a Promise-returning method.
Blast radius, pinned by a loop-level test: the rejection reaches the turn's
outer catch — the turn closes balanced with an `error` reason, agent/error
mirrors it, no step opens, no request/header is logged, no request reaches
the adapter, and the agent returns to idle; every turn fails identically
until the config is fixed. A boot-time validation pass was considered and
rejected (recorded in the RFC). The general principle — misconfiguration
fails loud, never a silent skip — is added to AGENTS.md.
A three-dot rest entry reads as elision in a cordis.yml; the spelled-out
sentinel says what lands there. The literal now appears once in code (the
constant) and once in the value-pinning test; every other reference — the
forwarding tests included — imports TOOL_ORDER_REST, which adds the
dsh-system-prompt devDependency to the two app packages.
Review follow-up on #196.
- run-gates.ts docSyncLeafGates() gains verify-export-jsdoc — CI lanes
and the pre-push hook execute this leaf list, not the doc-sync npm
script, so the gate was previously unenforced there (proven by
SessionForkErrorCode landing undocumented via a master merge while
checks stayed green; now documented). Same wiring gap fixed for
master's verify-config-catalog, which was also missing from the list.
- The heritage exemption now recovers the base's return surface: a void
base return carried no @returns duty, so an override returning a
concrete result documents it itself (annotated overrides run the
standard check; unannotated ones are classified by the checker so
faithful void overrides need no boilerplate annotation). Three new
negative-path tests pin it; RFC and module doc updated.
- AGENTS.md states each principle inline instead of citing RFCs (eight
citations removed; high-level doc links kept) and the editing section
now carries the self-containedness rule.
- Generated catalogs/graphs regenerated for the shifted line pointers.
A new waterfall near request construction lets plugins contribute
request-ONLY messages framing the derived history: RequestMessages
{ before, after } with a frozen empty seed, fired inside the open step
after the agent/request config waterfall, so the step/start boundary
snapshot and its same-sync-frame invariant are untouched. The request
becomes messagePrefix + boundary snapshot + messageSuffix.
Contributions never enter session history — deriveMessages() is
unchanged — so the request header is their durable record:
EpochHeader gains messagePrefix/messageSuffix (canonical absence for
empty arrays), request/header-delta replaces either array whole with
an empty array encoding the transition back to absence, and the
dev-mode reconstruction cross-check now expects the folded header's
framing around the boundary derivation.
This is the seam for per-request advisory context that must be
model-visible now without becoming durable history (a skills catalog,
an environment reminder), keeping the base system prompt
workspace-independent and provider prefix caches stable. The docs
carry the channel cost model: session-frozen content belongs in
before, low-frequency change notices belong in durable history via
inject() (paid once, prefix-cached thereafter), and after is reserved
for small frequently-refreshed state snapshots re-paid on every
request they ride. No shipped producer yet, so ACP snapshot fixtures
are byte-identical.
Out-of-process delegation to external coding agents as two new subagent
seam backends, exposed as subagent_claude_code / subagent_codex tools.
Verified against @anthropic-ai/claude-agent-sdk 0.3.202 and codex CLI
0.142.5 via keyless spikes; includes the dsh-subagent-process extraction
plan, isolation/permission stances, and tiered test coverage.
A folded mode name the current config no longer defines behaves as default
plus one boundary notice naming the dropped definition — set()'s loud
validation covers only the write path.
The #192 review discussion, preserved where design rationale lives: a
schema-object library (zod/schemastery) cannot sit at a wire-data boundary;
ajv replaces only the value walker while the subset gate — the module's
point — stays hand-written; provider JSON mode guarantees valid JSON, not
schema-conforming JSON, and would trade away mid-run tools and in-turn
validation retry for it. Strict tool schemas are named as the accepted
upgrade path when the provider ships them.
The deliverable is plan mode — retitle and reorder the proposal opener so
the feature leads and the logged session-mode state is its implementation
property. Fix the Codex citation in the boolean-planMode alternative: Codex
has plan mode (/plan); its ACP mode list carries its approval presets — two
different surfaces. Durable vocabulary (mode/set, ctx.modes, dsh-mode) stays
string-shaped so a second mode never renames logged event types.
The zh.md counterpart (section-for-section mirror per the i18n contract:
identical heading structure, byte-identical text fences, same link targets)
plus the recorded i18n.yaml and the language-switcher lines on both sides —
matching the approval / sandbox / env-state RFC practice.
Re-audited against origin: master unmoved; approval seam still on
feat/sandbox-support; three relevant branches appeared since drafting.
- env-state-visibility RFC (fold-from-log + boundary application for env
facts): adopt its boundary-narration principle for user-driven mode flips
(one coalesced notice when the flushed mode differs from the last request
header's fold; net-zero silent; tool exits self-narrate via result), and
resolve the one real collision — its config-phase sketch routes ACP
session/set_mode to env facts while this RFC claims the picker. Proposed
division: picker-to-modes / knobs-to-config-options; a mode definition may
later bundle env facts; second lander amends. Also note the source-field
contrast (env drift has no log-adjacent cause; every mode/set does).
- sandbox-escalation: first live approval composition — named as the
precedent our recorded scenarios follow.
- ask-user-question / user-interaction seam: the stdio approval answerer
rides its one-prompt-owns-stdin queue where mounted.
- Config: pin the allowlist as the degenerate form of a per-tool
allow|deny|ask map so execution-phase ask policies arrive without a
config migration (deferred on approval's allow_always).
- Dedup the ACP mapping (one home: Protocol and UI surfaces).
Adds the Detailed design block: the mode/set vocabulary (no provenance
field — cause is log-adjacent; bare-string mode names), ModeConfig with an
explicit resolve step, the cursor-cached fold + ctx.modes service with
turn/start|step/end pending flush, the computed mode:policy section (order
50) + post-next() assembly filter (exit_plan_mode visible IFF plan mode —
what keeps default-mode assemblies byte-identical), the fold-only gate with
its ask on exit_plan_mode (no dsh-approval dependency — the registry
routes), the exit tool contract and render intent, dependency edges and the
stdio/ACP surfaces, the input.json setMode op + plan-mode/plan-mode-reject
scenarios (mode set before turn 1 → the initial header snapshot is already
in plan shape; the widening delta appears at exit), and the mechanical tail.
Consistency fixes: the plan allowlist names real tools (no grep tool
exists); the re-widen risk now states the post-next() mechanism instead of
a listener-order convention.
A session mode is a named, logged, per-agent policy state: mode/set as a
log-only SessionEventMap member (todo/write shape), a foldMode over the log,
soft enforcement at system-prompt/assemble (filtered schemas + mode section,
auto-logged as request/header-delta), hard enforcement at tools/pre-execute
(deny-by-default against the mode allowlist), and a thin ctx.modes service
with turn-boundary pending-intent flush for user flips. One new product
package dsh-mode (packages/mode/mode, the approval-group shape); plan is the
only shipped definition.
Lands as ONE feature — a plan mode without a model-driven, approvable exit is
not a smaller version of the feature. The roadmap's two stages are build-and-
review order for a single stacked landing: the mode core, then exit_plan_mode
(ask-gated through the approval seam; the plan text rides the tool args as
the reviewable log artifact), the stdio readline answerer, and the ACP
session-mode surface (session/set_mode, current_mode_update, available
modes). Hard prerequisite: the approval seam lands first; the stack bases on
feat/sandbox-support meanwhile.
Master's generated config catalog (#188, flattened paths #191) now records
plugin Configs; the nudge removal dropped structuredNudgeRetries from both
backends, so the regenerated catalog loses those rows.
Ship @deepseek-ai/dsh-retention under packages/util/: pure ItemRetainer /
TextRetainer plus neutral notice helpers, so tools that cap model-facing
output share one "what did we keep, what did we omit, may we stop reading"
mechanic while keeping grouping, exit codes, provider errors, and recovery
prose tool-owned. The two retainers are separate names because they differ in
resource model: item-head can stop the upstream on the first over-cap probe
(shouldStop), while text tail/head-tail must read to the end. The library
documents glob/grep/bash/web_fetch/web_search mappings but migrates no tool
yet — glob/grep don't exist, and migration is deliberately separate work.
Flips the RFC to implemented/ and rewrites its skeleton to shipped reality.
The parent branch dropped structuredNudgeRetries; the two workflow-side test
setups stop passing it, and the RFC's foundation paragraph now describes the
current design (final-ASSEMBLY enforcement logged via request/header, the
post-capture pre-execute deny, the start() schema snapshot, no re-prompt)
instead of the retired agent/request + nudge shape.