- Strict steer additionally requires an OPEN STEP: between steps the
loop may be awaiting its continuation/turn-stop checkpoints, where
pending steering was already folded and a terminal stop discards a
later arrival. A message accepted during an open step is drained and
recorded at that step's settlement before any terminal decision, so
the acknowledged-then-discarded window is closed. New keyless test
holds agent/turn-stop open and pins the rejection.
- tool-subagent-control README: distinguish synchronous not-delivered
errors from started-Task failures (unknown/foreign/descriptor-less
ids settle the started Task as failed), and drop the claim that the
completion notice carries the child's response.
- Wire the control service and send_message tool into every shipped
composition with a resumable provider and background enabled
(headless-agent, tui-agent, and the SDK helper's subagent feature
base resources); jsonrpc-agent disables background and is unchanged.
- Resolve the send_message availability check in the CALLER's tool
scope so a restriction that removes the follow-up tool from one
agent also blocks that agent's continuable start.
- Control-service disposal now cancels live activations and awaits
producer settlement instead of stranding them: TaskService keeps
producer Tasks across a reload, so the disposing service aborts each
activation-owned controller, resolves its terminal gate (the
effect-scoped onTaskDone listener is already gone), and awaits done.
A new test kills a mid-start activation through HMR disposal.
- Strict steer now rejects the two windows where an acknowledged
message would be silently dropped: the closed-turn durability-flush
window (status still running, loop strands drained steering) and a
committed structured capture (terminal turn-stop discards late
steering). Seam JSDoc, catalog doc, README, and the Agent Note
bilingual pair state the tightened contract; new keyless tests pin
both rejections.
- Continuable background delegation now fails loud when the advertised
send_message tool is not registered, instead of starting a durable
child the model cannot continue. The acp-agent example already loads
the control tool; the tool-catalog boot recipe is unaffected because
capability wording is harvested at mount.
macOS realpaths temp dirs into /private. The normalizer only stripped
the /private prefix for TMPDIR under /var; a TMPDIR under /tmp (any
explicitly relocated temp root) left one side canonicalized and the
comparison failing. Accept both shapes.
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.
The shipped-roster change made @deepseek-ai/dsh-tool-session-query a
default row of the shared base.cordis.yml, so the TUI and Web surfaces
put the five session-search tools in front of the model. That
contradicts the recorded opt-in stance for the model-facing session
query consumer; the ACP example remains the mounted reference.
Remove the row from the shared base, the now-dangling disabled patch in
the opt-in core-web profile, and the workspace dependency. The
ctx.sessionQuery index stays: the TUI's /resume and the Web content
search consume it directly. Both shipped-composition tests now pin the
20-tool catalog.
goalChangeRef returned the full GoalSnapshot for every snapshot operation,
so foldGoal(...).lastRef and the goal/changed notification ref carried
objective, phase, and maxGoalRounds fields instead of the declared
GoalRef { id, revision }. Only the clear tombstone was bare. Emit an exact
{ id, revision } ref for snapshot changes and pin the contract with a
regression test covering create/edit/block notifications and the fold.
CI caught a startup race the PTY smoke stages naturally: a TUI /exit typed
while watchPersonalPatches is still opening its watcher disposes the tree,
and the HMR effect registration rejects with INACTIVE_EFFECT, crashing an
app that exited exactly as asked. Return a no-op disposer for that exact
code; every other registration failure still propagates (covered both ways).
ds-review-bot round 1 on the DSH-home integration:
- generated wrappers now inject the services their manifest needs (skills/
tools beside loader), and loadPreparedRepository rejects a wrapper fiber
that settles anything but ACTIVE — a composition missing a required
service fails the repository transaction instead of committing an ACTIVE
row over a silently PENDING child (critical finding)
- the github: source ref segment excludes '#', so 'a#b' refs fail at the
config parser with the promised syntax instead of inside pnpm
- watchPersonalPatches re-reads the include's non-patch options per refresh
instead of a registration-time snapshot
- the TUI smoke's cache-seeded wrapper is produced by the real
prepareDshPlugin (cache LAYOUT stays a deliberate external pin)
- new Loader integration test drives a live repositories update through
entry.update: generation swap, old skills removed, failed candidate
rolled back to the previous generation
ds-review-bot round 1 on the repository-plugin runtime:
- a manifest-declared skill root absent or non-directory in the installed
package now fails the plugin load (skill-local treats a missing root as
legitimately empty, which silently mounted a skill-less plugin)
- includeDefaultRoots: false no longer inherits $DSH_BUNDLED_SKILL_DIR, so
isolated repository providers see only their explicit roots
- prepared wrapper baseUrl schema requires the file: scheme, failing hostile
URLs at the declared validation boundary
- preparedPath reuses format.ts's isOutside; SERVER_NAME_PATTERN is exported
and pinned equal to dsh-mcp-client's, with the restatement justified (the
prepare bin keeps a zod-only module graph); the unexplained `as never`
cast now carries its schemastery rationale
- the import-free wrapper assertion also rejects dynamic import(
- the headless fixture wrapper is regenerated by the real prepareDshPlugin
and a drift test pins fixture == generator output
- prepareDshPlugin JSDoc states the non-atomic publish repair contract
ds-review-bot round 1: boot() now throws `host preparation failed` when
prepare() rejects before any config-tree entry mounts (the plugin-tree label
overstated), and the new hygiene gate verify-vendored-links pins the
linkWorkspacePackages fix — every vendored package name in pnpm-lock.yaml
must resolve to a workspace link with no registry copy alongside.