Address ds-review-bot v5/v6 review round 6:
- renderType tracks the active ancestor schemas by object identity (the frame
stack is the DFS path). A stateful getter can mutate the graph after
validation so a child returns an ancestor at render time; without this the
walk pushed frames forever instead of degrading. A repeated ancestor now
degrades to Any, honoring the never-throw contract; distinct nodes in a
legitimately deep chain are different objects, so it stays O(1) per push and
O(depth) memory.
- The multiline allocateClassName JSDoc was still attached to the
MAX_CLASS_NAME_BASE constant (a self-referential @link, and the function had
no doc). Move the doc onto the function and give the constant its own
one-liner.
- Tests cover the post-validation cycle and a non-object render-time child;
py-types.ts stays at 100% per-file coverage.
Address ds-review-bot v5/v6 review round 5:
- allocateClassName: keep a per-base collision counter (state.nextClassCounter)
so a deep single-field chain sharing one capped base allocates in amortized
O(1) instead of rescanning from 2 each time (Theta(depth^2) time); remove the
stale one-line JSDoc left above the multiline one and attach the doc to the
function, not the constant.
- renderType's catch rolls back the typing symbols the discarded subtree added
(not just the classes) so the import line still lists exactly the symbols the
surviving output uses; the comment now names that the same path also degrades
this module's internal-invariant throws to Any, the trade for never throwing.
- README (both languages) no longer describes an installable
dsh-code-runtime-python package: the Python renderer is built in and drives
any runtime reporting language: 'python'; the first-party backend ships
separately.
- Tests: assert the render-phase degrade on the first call, assert the import
line after rollback, and cover the collision-skip loop; py-types.ts stays at
100% per-file coverage.
Address ds-review-bot v5/v6 review round 4:
- renderType now holds the no-throw contract across the whole walk, not
just root validation: a stateful getter that passes validation and then
throws in the render phase degrades the node to Any, rolling back any
classes the call had begun emitting, instead of escaping.
- allocateClassName caps the accumulated base name. Child class names
derive from their parent's, so an unbounded single-field object chain
grew the sum of names to Theta(depth^2) (a 5000-deep schema produced a
~25MB SDK); the cap keeps total emitted text linear, the collision
counter still makes truncated bases unique.
- The language-dispatch note's Consequences first sentence and the zh
guard paragraph are corrected: two table entries (not one), and
full-width Chinese punctuation per translation-rules.md.
Address ds-review-bot v5/v6 review round 3:
- Config.mode JSDoc and the regenerated config-catalog no longer claim Code
Mode requires a TypeScript runtime; both now say a language with a
registered SDK renderer.
- The active 2026-06-15-code-mode base note (both languages) follows shipped
reality: the SDK renders the loaded runtime's language, dsh-tools accepts
any language with a renderer and run_code flavor, and it cross-links the
language-dispatch note.
- The language-dispatch note distinguishes the two Object.hasOwn guards'
reachability and documents the peekRuntime no-runtime degrade vs the
rejected silent fallback.
- SDK_RENDERERS comment: adding a language is two table entries, not one.
- py-types: document the deliberate PEP 586 deviation for float Literals;
add oneOf-object-branch tests (named union classes and context-free
degrade), keeping py-types.ts at 100% per-file coverage.
Implements the durable-subagent-catalog RFC: SubagentControlService.listChildren()
enumerates a parent's direct continuable children from one sessionQuery trace,
validates each child's sole subagent/descriptor event (now carrying the durable
creation label), and returns one ordered SubagentListEntry[] with per-child
corrupt/unsupported/unavailable diagnostics. The list_agents tool ships as a
separately loadable plugin of dsh-tool-subagent-control requiring sessionQuery
at load; send_message stays usable without it.
Implement the continuable background subagents RFC: a durable child
session with a series of Task-backed activations, each disposing its
run before the Task settles.
- dsh-subagent: rename SubagentRun.sendMessage to strict steer, drop
run-level resume, add SubagentProvider.resume dispatch via
SubagentService.resume, the continuation start field, and the
versioned model-hidden subagent/descriptor session event.
- dsh-subagent-inprocess/-spawn/-fork: publish the control-allocated
child id, append the descriptor inside the initial turn, implement
cold resume from the child's own transcript under the live parent
scope, and strict running-only steer.
- dsh-subagent-control (new): SubagentControlService owning stable
child ids, descriptor snapshot/fold/authorization, Task-backed
activation with settle-then-dispose ordering, the process-local
active-run association, and steer-or-resume sendMessage routing.
- dsh-tool-subagent: background route branches on the provider's
resume capability (continuable via the control service; one-shot
task for ACP), returning both child and task ids.
- dsh-tool-subagent-control (new): the globally named send_message
tool rendering steered/started routes.
Keyless coverage spans Task ownership and disposal ordering, running
delivery, cold follow-up, descriptor rejection and rollback, known-id
reconstruction, kill during lookup, admission races, and a new
subagent-continuable ACP snapshot scenario.
Address ds-review-bot v5/v6 review on the Python SDK renderer:
- resolveFlavor now takes a peekRuntime() reader: undefined (no runtime,
the doc-catalog harvest) degrades to the TS flavor, but a mounted
runtime whose language is absent from RUN_CODE_FLAVORS fails loud. This
removes the try/catch that silently swallowed the invalid-language path
and drops the /* v8 ignore */ that hid the flavor guard from coverage;
wireSchemas validates the runtime before projecting schemas so the
renderer-table rejection stays the canonical assembly error.
- py-types RESERVED drops the soft keywords match/case: they are legal as
TypedDict fields and methods, so keeping them needlessly degraded
common search/regex arg objects to dict[str, Any].
- py-types treats an object with omitted properties as {} like the unified
validator and TS renderer do, so a closed empty object declares an empty
TypedDict instead of a permissive dict[str, Any].
- README: symmetric jsonSchemaToPy->Any note; a stale zh SDK bullet and
limitation corrected; link the service-wide-language limitation to its
Agent Note.
Code Mode generated only a TypeScript SDK and rejected any runtime whose
language was not "typescript". Add py-types.ts (jsonSchemaToPy /
renderToolsSdkPy) and select the SDK-section renderer and the run_code
schema flavor by ctx.codeRuntime.language through two parallel tables
(SDK_RENDERERS, RUN_CODE_FLAVORS), read with Object.hasOwn and failing
loud on a language with no renderer. The tool layer depends only on the
code-runtime seam's language field, so it lands independently of the
Python protocol and backend.
The staged-promotion machinery (concludingParents keyed by parent token
plus a pendingParentConclusions staging map) spread one fact — this call
concluded the turn — across three registry-side collections with manual
cleanup. Align it with how additionalContexts already crosses the same
boundary: concludeTurn() marks only its own execution, the marker rides
that execution's successful result (ToolExecutionFailure types
concludesTurn as never, so a policy-converted failure sheds it with the
type), and the composite that owns the nested dispatch forwards it —
Code Mode's binding does so beside its existing context forwarding.
The registry loses both parent-keyed collections and the promotion
block; the propagation decision moves to the owning boundary; the
structured-output consumer's own two-phase commit is untouched.
agent-loop lifecycle: dispose drains machine.done to true quiescence.
cancel()'s own running-to-idle transition can legitimately re-enter
through an automation listener (goal-session's idle drive runs
synchronously to its first await) and replace done with a fresh
admission after the single capture; teardown now re-cancels and
re-awaits until the slot stabilizes, so the scope never unwinds under a
live run.
tools: a nested concludeTurn() stages on its own execution and promotes
to the enclosing composite only on the call's authoritative successful
verdict. A post-execute policy that converts the nested success into an
error no longer lets a recovering composite stop the turn on a failed
terminal operation (the Code Mode structured-output shape).
goal-session: the driver owns its round durability barrier again. The
loop's persistence is eager write-behind with no turn-end flush, so the
old post-turn agent/error signal for flush failures never fires; a
settled round now sets needsCheckpoint and re-enters drive, flushing
before the next reservation and disarming on failure instead of queueing
an autonomous round on state that was never persisted.
Conflict resolution: drainDispatches composes both sides — the reworked
single-lane driver's drive() (ordered stages, mid-flight commit drain)
followed by this branch's logWork drain, so every shaped settle append
still lands inside the open run_code turn.
Responding to ds-review-bot round 2 on #658 (three critical findings, one
warning — all rooted in the pump/commit split racing ordered stages):
- ONE driver lane now owns every ordered stage: the start append, prepare
(pre-execute/guards), and the head-of-line commit (post-execute, context
deferral, settle append). start() is awaited before the next entry can
start, so concurrent submissions can no longer run pre-execute pipelines
concurrently; only the around-dispatch/body stage overlaps, matching the
native loop's fillPool sequencing.
- An exclusive call's barrier now holds through its COMMIT: later starts
wait for the exclusive pipeline (post-execute included) to finish, the
native exclusive-group semantics.
- drainDispatches() awaits the driver run itself, so a commit already
mid-flight when the program returns is drained before run_code closes
the turn — the settle event and deferred contexts land inside it.
- maxParallelSubCalls is resolved and validated at construction (positive
integer), so direct construction can no longer wedge the pool with 0.
New tests: overlapping-submission ordered-prepare, barrier-through-commit,
drain-mid-commit, cap rejection. 96 keyless snapshots replay unchanged;
Agent Note updated (both languages).
CI's full-tree coverage flagged three untaken paths this PR introduced:
- shapeDispatchLog's catch (a throwing tools/code-dispatch-log listener
must be contained — the settle event logs the unshaped content);
- the spill listener's flatten-decline arm (non-text sub-result content
passes through unchanged);
- the generated scope-key extractor row for tools/code-dispatch-log
(registered in the scope invariant matrix like the other tools events).
The coverage gate flagged three untaken paths in the bridge scheduler:
- the exclusive-head inFlight re-check was dead (the shared guard above
already returns for an exclusive head with any in-flight sibling) — removed;
- the commit-cursor undefined-dispatched break was structurally unreachable
once entries join commitQueue only after start() ran synchronously —
reordered the pump so the invariant holds by construction, annotated;
- the finish (final-result) commit arm and the pump re-entry guard gain a
covering test (throwing tools/pre-execute listener) and a defensive
annotation respectively; mid-run unregistration test renamed to match its
actual post-result settlement path.
Also covers the direct-construction maxParallelSubCalls default (index.ts)
and commits the regenerated persistence catalog for the new dispatch pair.
ds-review-bot findings on the parallel bridge: sub-dispatches now run
through the registry's staged scheduler view (the loop's own seam) —
ordered prepare (pre-execute/guards) at submission-ordered start time,
only the dispatch/body stage overlapping, and a head-of-line commit
cursor running post-execute, context deferral, and the settle event in
submission order (new spec pins post order + context order under
out-of-order completion). Queued dispatches reclassify via
executionMode() immediately before starting, so a registry mutation
while queued flips them exclusive (native lazy-reclassification
semantics). Config and tool catalogs regenerated; the tool-catalog
metadata now names the start/settle pair and the scheduling contract.
The bridge replaces its serialization queue with a pool that reuses the
native concurrency contract: submissions classify through
registry.executionMode (fail-closed isConcurrencySafe), start strictly in
submission order, overlap up to the validated maxParallelSubCalls config
(default 10; 1 restores serial), and exclusive calls drain the pool, run
alone, and bar later calls. Each started sub-call logs a
tool/code-dispatch-start event at pool entry; the existing
tool/code-dispatch settles the pair (started ⇔ settles exactly once;
abandoned queued calls log neither). SDK prompt guidance now states the
true Promise.all contract — re-recorded across every code/both-mode
snapshot (plus the stale cordis-dynamic-toolchain fixture gaining the
required description arg).
Client: CodeSubCall widens to RunningToolCall | ToolResultNode — starts
land the running shape (rows wear the native running ring), settles
replace in place preserving start order, callTime pairs to the start
time. Fixture emits start/settle pairs; jsdom pins the running sub-row;
runtime specs pin in-place settlement and out-of-order completion.
run_code gains a required bash-style description parameter: presentCall
titles the card with it and moves the program to rawInput, so every
surface gets a readable label. tool/code-dispatch now logs each
sub-call's complete content/isError (the tool/result vocabulary),
replacing the bounded resultSummary and deleting the summarize/cwd
machinery — a UI renders sub-calls through the identical path as native
results. The dsh config tree mounts the worker code runtime and reads
DSH_TOOLS_MODE (temporary seam until per-session mode selection lands).
Session format stays v0 (pre-release churn). Code-mode ACP/TUI fixtures
re-recorded; TUI presenter pin refreshed; catalogs regenerated. Keyless
web smoke pins the code-mode wire contract (tools=[run_code] + SDK
prompt section).