A dismissed review now returns a successful exit_plan_mode result, so
the plan policy must not treat any success as leaving plan mode. Also
update the user-facing description of a dismissed review.
Group built-in settings before optional and installed plugins, and open the full experimental catalog on a separate page.
Preserve detail navigation and unsaved configuration when installation finishes, and use the visible More label as the accessible name.
Choosing "Request changes" on a plan review rejected the question, and
exit_plan_mode turned that into a failed tool result asking the model to
stop. The loop then spent one more model request only to have the model
wait. A dismissed review now returns { approved: false } and concludes the
turn, so the user's next message starts the following request.
Silero cannot back-date the first segment of a cold detector, so it
reports a start index several frames after speech actually began. Every
recording therefore lost its opening audio: measured across 50
utterances the median loss was 102 ms and the worst 230 ms — a full
syllable, which surfaced as dropped leading words ("麻烦把发票…" →
"把发票…", "用 Python…" → "写一个 Python…").
Only the first segment is affected; later segments aligned within 3 ms
because the detector retains history by then. Tuning cannot fix it:
lowering minSpeechSeconds from 0.25 s to 0.05 s left the median loss at
110 ms, and lowering the threshold to 0.2 still lost up to 174 ms while
risking false triggers.
Restore the dropped onset from the recording itself, bounded by the new
vadOnsetPaddingSeconds (default 0.3 s, zero disables). Measured on the
same corpus with identical DSH settings, this recovers the full no-VAD
accuracy and in the technical domain exceeds it, since segmentation also
trims silence:
语料 条件 无VAD VAD丢起音 VAD+回填 改善
audio clean 12.50% 16.33% 12.50% 23.5%
audio noisy10 17.74% 19.15% 17.94% 6.3%
audio_tech clean 10.34% 15.65% 9.81% 37.3%
audio_tech noisy10 25.73% 30.50% 26.26% 13.9%
Inference cost is unchanged (2.6 s vs 2.6 s on both clean corpora).
Use the package's own `SubprocessTerminalActivity['state']` instead of a derived
alias, label the samples as `state:revision` so a reader cannot mistake the
suffix for a count, name the summariser's bound as a group count, and state only
the two observables the prompt count supports.
Refs #5755
An activity poll that times out reports only "unknown" or "Matcher did not
succeed in time". The Linux standby has failed that way twice, at different
polls, and the log shows nothing about what `inspect()` read, which returns
`unknown` for several distinct conditions.
Every activity poll now records the state and revision it samples and counts the
prompts printed while it waits, so a timeout names both. Repeated samples
collapse and the printed list is bounded. Timeouts, intervals and expectations
are unchanged, and where the poll budget equals the test budget the test timer
can still preempt this message.
Refs #5755
An unexpected fork exit can close the worker's channel under a send, which
Vitest reports as a runner-level worker error instead of the clean exit. It
then tears the worker down before the ChildProcess exit event, and
DiagnosticForkWorker treated that teardown as an orderly shutdown: it cleared
the observer and marked the exit expected, so the coverage log lost the pid,
file, code and signal it exists to report.
A worker error observed before a stop now keeps the observer, and a stop
requested after that error no longer marks the exit as expected.
Vitest reports one such exit either as the clean exit path or as a
runner-level worker error, so the spec accepts both forms and compares the
record against whichever appeared.
Refs #5813
pi-ai sends Anthropic tool changes natively only when the initial tool list
has an active declaration. When every projected declaration is deferred,
prepend a non-deferred DeferredToolPlaceholder instead of failing the request.
Document the placeholder, the catalog-owned update capabilities, and the
image role limit as separate README entries.
Master removed `backgroundMode` and the `run_in_background` argument, so
every delegation starts a managed activation. Mount the working-directory
owner SubagentRuntime injects and the JSONL persistence root the spawn
provider requires, and synchronize on the child's cancellation, which the
parent teardown or the manager drain raises instead of a parent-only cause.
The Typert workspace pass scans the analyzer's symbol table, and every
symbol asked `registrationForFile`/`packageNameForFile` for its owning
package. Both looked up the real path and then scanned every registration
with `isWithin`, and `isWithin` re-ran `path.resolve` through `realPath`
for both arguments on each call. A CPU profile of the host pass put
17.0 s of the 30.9 s sampled in that scan, almost all of it in
`realPath`/`resolve`/`existsSync`.
The registration set and the analyzer's face are fixed for the analyzer's
lifetime, so each source path's answer cannot change: memoize both
lookups per analyzer. `resolve` depends only on the requested path and the
process working directory, so memoize the normalization step too; the
existing canonical-path cache keeps its "existing paths only" rule.
tsdown host pass, interleaved A/B: 39.89/37.18 s -> 25.70/25.06 s.
Isolated generator phase: discover 8.69/8.59 s -> 7.33/7.44 s,
generate 17.19/16.96 s -> 4.21/4.21 s. Every emitted `typert.*` file
hashes identically before and after each run.
waitForTitleAfterTurnEnd and waitForEventAfterTurnEnd now name the session,
expected record, and deadline when the deadline passes before the first log
harvest settles, and keep the vi.waitFor failure as cause. Previously they
surfaced Vitest's generic 'Timed out in waitFor!', which failed the title
diagnostic spec on the macOS Sandbox lane at master 090348e4.
The title diagnostic spec uses isolateDiagnosticTimeout like its siblings,
replacing the Windows-only 5s timeout.
Refs #5631
The upload warns instead of silently ignoring missing files, and the spec
pins the upload after the benchmark step. benchmarks/AGENTS.md and the
report Agent Note now name the artifact CI publishes.
Refs #5840
The node-24-bench job uploads benchmarks/.dsh-report after the benchmark
step, including when a budget assertion fails, so the report the Vitest
globalSetup renders survives the runner. #5838 deferred this step because
the authoring token had no workflow scope.
Refs #5838
Extension failures, unreadable package identities, a blocked session-log
upload, and degraded Messages replay recur on every request. Log the first
occurrence per field, route, module, or blocking event instead.
Co-authored-by: turtle1999 <[email protected]>
Request-extension fields are auxiliary. A contributor whose preparation
throws is now logged and omitted while the other fields are still sent,
and a post-2xx acceptance failure is logged without failing the request.
Package inventory is best effort: a named manifest without a version, or
a bare package whose manifest is missing or unreadable, reports version
"unknown" instead of failing preparation.
A failing request-extension contributor surfaced only
'DeepSeek request extension preparation failed'; the cause stayed on
LlmError.cause, which the turn failure record and retry notice drop.
Append the cause chain to preparation and acceptance messages.
Every recorded case states what it measures and which user scenario it
affects, shown on hover in the report. Endpoints whose workers report
process CPU time default to the measured non-CPU share and busy
parallelism; the Session corpus worker now reports CPU time per
endpoint. Machine profiles carry logical CPU counts, and the page
shows each default share with its source and can reset overrides.
The General font-size row gains a More font settings disclosure that expands
a font field for each of text, code, and the sidebar terminal, plus code
(10-16 px) and terminal (10-20 px) size steppers. Font lists are normalized,
stored in the ui-theme namespace, embedded by the boot script, and placed
ahead of each role's built-in stack; the code size moves the code typography
tokens; the terminal applies font and size live and refits without replacing
the shell. ThemeRuntime adopts Host sections only after its own writes settle.
Refs #5217
Two fixture helpers in surface.spec.ts returned ': SessionEvent', a
declaration-merge-extensible mapped union. Spreading such a value (the
rejection cases build '{ ...event, data: ... }') materialises an anonymous
union per fixture, and the following 'as SessionEvent' casts then compare
union against union, which dominates the root type-check program.
Return the single variant each helper actually builds, spelled as the
canonical indexed access SessionEvent<'system/message'> /
SessionEvent<'user/message'>, drop the casts that only existed because data
was a union, and trim the comment of the member count that drifts with every
declaration merge.
Measured on the host build: check 110.9 s -> 84.6 s (median, -23.7 %),
wall -17 % to -24 %; fixture types alone (clean buildinfo, root program):
check 53.4/45.8 s (Extract alias) vs 45.3/49.1 s (indexed access).
Two declaration forms relied on a checker binding a name the permissive way:
- cordis-host-runner declared 'export function CordisDynamicPluginId'
alongside 'import type { CordisDynamicPluginId }' of the branded type, so
the call sites resolved through the type-only import. Export the four
constructors under separate local names; the public names, JSDoc and the
type names in signatures are unchanged, and the typert signature metadata
keeps the original type names.
- views-type-chain.client.spec.tsx spread one slots.register call over three
lines, which lets a checker anchor the rejected overload on the argument
line instead of the call line, outside the @ts-expect-error directive.
Keeping the call on one line keeps the negative assertion effective.
Let a title provider replace one that is still draining, so switching the
titles bundle during an in-flight request no longer leaves no provider.
Omit group container rows from bundle inventories, since they have no
plugin entry to switch. Drop the obsolete preset-target product-use case,
fix the Claude Code row id example, and refresh stale Agent Note text.
Session restore and projection run after the file closes, so they use
no storage share; the page keeps one share per endpoint. Record the
repeat projection-list budget, drop the post-GC page heap from the
memory view, remove the unused profile group, and add the owning
Agent Note.
Remove the `{ preset, insert }` / `{ preset, id, ...override }` profile patch
operation added in #5773, together with the app-boot compiler and reload drain,
config-editor filtering, plugin-manager preset row inventory and retained-module
removal checks, the read-only preset rows on the Plugins page, and the
agent-preset-registry row identities that served them.
Session search, Ralph, persistent terminals, Codex, and Claude Code bundles now
insert ordinary Host rows whose tools register in the global tool layer, like the
earlier optional bundles. Every preset, including minimal, sees them, and toggling
a bundle updates live Agents without new composition generations.
Record budgeted benchmark endpoints and peak memory, and render a
self-contained interactive report that rescales measured times to
reviewed laptop and cloud profiles by single-thread CPU rating and
default-disk latency, and compares peaks with 4 GB and 8 GB of
available memory.
The packed rehearsal now names each stage on the child's stdout and bounds the
manager's removal, so an operation that never settles reports the profile lock
and the still-live pnpm process instead of only a test timeout. That evidence
located pnpm 11.7.0 leaving its worker pool alive after a short-circuit return
(pnpm issue 12297, fixed in 11.23.0 and carried by this stack's base).
Three attempts to keep pnpm off that path — a private pnpm store, installing
both bundles before removing either, and pre-declaring the profile's age
exemptions — did not change the outcome, so none of them remain.
- The summary lists only the catalog roots as installable specs; the other 29
archives stay in the run manifest, where the installer resolves them itself.
- Only a pnpm entrypoint receives pnpm's arguments, a catalog root counts as a
required edge for platform admission, and a signal that stops packing reports
the interrupted exit instead of an exception.
- The packed rehearsal names the Agent SDK's musl artifact on musl hosts again,
and its comments match what the fixture does.
- The cache test registers its pnpm children for termination, and a new case
keeps one profile and its real lockfile: pnpm refuses a new run's URL with
ERR_PNPM_TARBALL_URL_MISMATCH until the stale resolution is cleared with
pnpm's own `pnpm clean --lockfile` remedy, which the desktop README now
documents for same-version iteration.
The teardown asserted the Web process's SIGTERM exit inside `finally`, so a
body failure was reported as a shutdown timeout instead of itself. The
assertions now run only when the body passed, the earlier failure is rethrown
after teardown, and the listener readiness became a named step that no longer
clones the release pack coordinator's listen idiom.
Both are failure paths the registry owns: a signal that stops packing must
report as a cancellation rather than as a pack failure, and a client that
disconnects mid-tarball must not stop later requests. execa reports the first
either as a resolved canceled result or as a thrown error, so the packing step
now names both.
A packaged application installs an Official on-demand bundle as
`name@<running DSH version>`, and the plugin manager asks a custom registry
alone, so the unsigned test DMG cannot install a locally built provider before
its release reaches npm: each packed manifest still names the rest of the
repository by version, and ordinary bundle tarballs therefore resolve against
a registry that does not have them yet.
`pnpm run dev:bundle-registry` validates the Official catalog and the workspace
inventory, builds the official artifacts, packs the catalog's complete local
dependency closure with the release families' own payload and manifest checks,
records the run in `dist/test-bundles/<run-id>/bundle-registry.json` as its
completion marker, and only then serves it from 127.0.0.1 on an allocated port
under a fresh namespace. Repository-owned names are answered from this run or
refused locally; third-party packages keep npm's real metadata and bytes
through a fixed redirect. The run publishes nothing, changes no global
npm/pnpm configuration, and leaves DMG packaging untouched.
The packed installer rehearsal now installs that closure into an isolated
profile with no `file:` override, checkout link, ignore list, or scoped
registry standing in for a package the registry should answer, and it asserts a
transitive repository package served only by this run.
The fixture workspaces declare packageManager, so corepack resolves that
version when pack-release runs pnpm pack inside them; leaving 11.7.0 there
would exercise the version this change replaces.
pnpm 11.28.5 runs verifyDepsBeforeRun before a script, so `pnpm run install`
inside the installed node-pty directory first installed node-pty's own
dependencies into the package and ran its `prepare` build. Both reached the
packaged payload: the SEA blob then carried a node-gyp tree and tripled pkg's
file records, and postject aborted while injecting it. The step now runs the
package's install script alone, as it did with pnpm 11.7.0.
pnpm writes allowBuilds placeholders for blocked dependency scripts only when
it can prompt, so a managed run that always captures output left
pendingBuilds empty and the approval flow could not offer anything. The failed
run's own report is the surviving record: the profile policy now records those
names, which keeps approval, persistence, and the wildcard and decision guards
unchanged.
pnpm's legacy deploy links the deployed project into the target's node_modules
when the target sits inside that project. Materializing that link copies the
payload into itself and fails with ERR_FS_CP_EINVAL, which broke the
release-shaped Python runtime matrix on Linux and Windows. The staged payload
never carries the project source, so the staging tree's owner drops the entry
and leaves the deploy root's files alone.
pnpm 11.7.0 can finish its work, print `Done in Xs`, and then never exit
(pnpm/pnpm#12297, pnpm/pnpm#13617): finishWorkers() clears the worker pool
reference, and a later worker API call recreates a pool that is never
finished, so idle worker threads keep the event loop alive. The upstream fix
(pnpm/pnpm#13226) shipped in 11.23.0, and 11.25.0 fixed the hoisted-linker
rename race as well.
The hang surfaces in this repository as a packed-profile rehearsal that runs
`pnpm remove` through the plugin manager and then waits for the test deadline,
holding the profile write lock for the whole time.
Bump packageManager and devDependencies.pnpm in the root, the pinned pnpm in
apps/desktop, and the native/system packageManager; relock; exempt the fresh
package manager from the release-age window; follow the development guide
pair.
Review found the same Host profile write plus inventory reload chain still
uncovered in worktree-opt-in.e2e.ts, and still written as 10_000 literals in
plugin-manager.e2e.ts. Both now use PLUGIN_TOGGLE_SETTLE_MS, the README keeps
only the convention, and the constant's JSDoc names the store's load() as the
reader that republishes the switch.
A plugin-card switch republishes `aria-checked` only after the Host writes
the profile and the panel re-reads its inventory. That chain measured
0.5-0.8 s on an idle host and 2.0-4.8 s across 32 samples in a 16-worker
lane, so Vitest's 1-second `expect.poll` default truncated it and failed
the switch assertions of the CoT translation scenarios in CI.
Every poll that follows a plugin-card switch click now passes the shared
PLUGIN_TOGGLE_SETTLE_MS budget; assertions, retries, and product code are
unchanged.
A reasoning route always declares at least one level, so the adapter's
"no floor" path is defensive; the resolver rejects an empty list before any
consumer sees it. Mark that branch with the repo's v8-ignore convention and
state why.
`purpose` is a label for replay and transport metadata, so no adapter should
branch on one caller's value. Exact-model metadata now reports
`reasoning.floorEffort`, the least effort a route accepts, and the title
service selects it for its request. `llm-pi-ai` and `llm-deepseek` dispatch
the effort they receive and no longer recognize `session-title`; the
`minimizeReasoning` field is gone. A route with no registered adapter, such
as the recorded replay lanes serve, reports no floor, so the title request
keeps the adapter default instead of failing before dispatch.
Replaces the `minimizeReasoning` field from the previous commit.
`purpose` is a provider-neutral label for replay and transport metadata, so
an adapter must not branch on one caller's value. `GenerateOptions` gains
`minimizeReasoning`, which any auxiliary caller sets when reasoning would
consume the output budget it reserved for a short visible result; both
shipped adapters dispatch such a request at the least reasoning the model
supports, and `session-title-llm` sets it instead of relying on the
adapter recognizing `'session-title'`. The pasted type in the subsystem
page and the generated Cordis API catalog follow.
Prompted by review: llm-pi-ai must not know about session-title.
A request body also carries the session log, whose recorded title event
embeds the framed prompt, so a body-wide marker search matches the main
request too; both mocks now read the request's own messages. The headless
scenarios also assert the dispatched cap, which is the base-bundle ceiling
on the 256000-token route and the route override's 1024 in the pi-ai
compatibility scenario. The extraction cases cover asterisks that are not
emphasis.
The lowest-level dispatch keeps reasoning off the title's output ceiling,
not a small cap's visible text, and it deliberately skips the
requested-effort check. The README pair records that the dispatched cap
is the smaller of the configured ceiling and the latest folded
request/header, including the tighter-main-cap and override consequences,
and the accepted-title comment states the deliberate whole-line emphasis
rule.
Headless stream-json and session expected outputs record the title
request's maxTokens, and every one of them still recorded the previous
base-bundle cap.
The README pair states that an accepted result is the first non-empty
line of the model text, with one wrapping emphasis pair removed, and that
the dispatched cap is the smaller of maxOutputTokens and the cap the
routed adapter declares. The pair is re-recorded with both sides.
The headless and Web mock providers recognize a title request by its
max_tokens, which still assumed the previous 256-token base-bundle cap.
The recorded expected outputs carry that cap too and follow with it.
The prompt asks for a short title, so the cap only stops runaway output:
reasoning a model cannot switch off also counts as output, and 256 tokens
truncated titles on the HFAI gateway. The measured worst title response
was 153 output tokens, so 4096 is about 25 times the observed maximum.
Claude wraps some titles in asterisk emphasis and writes the title, a
blank line, then an explanation when the messages say little. Joining the
text blocks made that explanation part of the saved title. Take the first
non-empty line, drop one emphasis pair around it, and ask the model for a
short best-effort title instead of an explanation.
The dispatched cap is now the smaller of the configured cap and the cap
the session's current main request recorded, so a route the deployment
capped below the shipped ceiling is never asked for more than that cap.
A session-title request dispatches off exactly as a selected off does,
which on protocols that omit the reasoning field leaves the gateway
default in effect. The README now says so instead of implying reasoning
is always switched off. requestReasoningLevel follows the
resolveReasoningLevel it calls and names when it returns no level, and
the Loader composition helper takes provider config and further entries
as separate parameters.
Headless stream-json and session expected outputs record the title
request's maxTokens, and the headless and Web mock providers recognize a
title request by its max_tokens. Both still assumed the previous 64-token
base-bundle cap.
The base bundle capped title generation at 64 output tokens, sized for a
route that disables thinking. Models whose reasoning cannot be switched
off spend output on it even at their lowest level: on the HFAI gateway,
GPT-6 Sol and GPT-6 Astra used up to 87 and 121 output tokens for
five-word titles at reasoning low, so Sol still finished with max-tokens
on most first prompts. Raise the shipped cap to 256; routes that disable
thinking stop after the title and are unaffected.
Session-title calls carry a 64-token output cap and rely on the adapter to
keep reasoning out of it. llm-deepseek maps purpose 'session-title' to
thinking disabled, but llm-pi-ai ignored the purpose, so pi-ai routes sent
title calls at the route's default effort. On HFAI's adaptive-thinking
Claude models (profile reasoning: max) the thinking consumed the cap, the
call finished with max-tokens, and the session kept its fallback title.
The pi-ai adapter now dispatches a session-title request at the model's
lowest supported level: off when the model offers it, otherwise the
lowest declared level. GenerateOptions.purpose documents that obligation
for every adapter.
A successful verifier message was treated as terminal even though the
Worker Thread was still alive. The parent immediately terminated the
worker and detached cancellation, so an error, nonzero exit, or caller
cancellation after that message could be ignored and verification could
resolve successfully.
Keep the first successful response pending until an observed natural exit
with code 0. A missing response or nonzero exit rejects after the worker
has already exited. Error and abort callbacks retain the existing
terminate-and-join path, including its first-error and AggregateError
behavior. The scheduler stays unchanged: its permit remains held until
the verification promise settles, now after successful worker quiescence.
The worker already closes its parent port in finally, so success needs
neither a forced stop nor another protocol message.
Replace success-termination expectations with deterministic message/exit
barriers and cover post-response errors, nonzero exit, cancellation, and
permit handoff. Remove the obsolete hand-written termination thenable and
its retired unknown-assertion baseline entry. Add real source workers for
plain and Zstandard input, and require the built plain-Node migration smoke
to complete with zero termination calls. Document the settlement condition.
Native Windows CI recorded process access violation 0xC0000005 while a
migration fixture was active. Removing unnecessary successful-worker
termination is consistent with that evidence, but this commit does not
claim that the native faulting module or mechanism has been established.
The deterministic regressions prove the lifecycle defect independently;
the next native CI run must assess whether the observed crash recurs.
Validation:
- Original source failed all new message-to-exit regression scenarios;
the updated focused lifecycle file passes 19 tests with 100% coverage.
- Node 24 owning package: 19 files, 741 passed, 1 skipped; every included
package source file reaches 100% statements/branches/functions/lines.
- Real source workers passed for none/zstd; built plain-Node smoke passed
with zero terminate calls and no timeout or process signal.
- Exact Node 22.19 source-mode none/zstd workers naturally exited 0 with
zero terminate calls, exercising the async Module.register fallback.
- Host TypeScript build, focused lint, no-new-unknown-assertion gate,
README translation pairing, and all 42 doc-sync gates passed.
Related to #4387 and #4394.
The master cwd integration updated the shipped headless profile, but three
current snapshot scenarios merged without textual conflicts and retained
expectations from before the directory service was composed. Full CI
replay found the stale product-subagent-both persona and missing current
working-directory context in the no-message-tool and structured-parent
parent logs; those two scenarios also owned stale child/header sidecars.
Refresh only those three scenarios through the real built headless profile.
Record the required runtime cwd section in their four parent/child V4
logs, remove static cwd text from three prompt sidecars, and include the
new cwd start parameters and working_directory tool in three schema
sidecars. Preserve every model response and event count. No production
code, scenario input, workspace oracle, or historical V0-V3 fixture changes.
Validation: selective refresh passed 3/3. Full DSH_EXAMPLE_MODE=lib pnpm
run test:snapshot passed all 8 files: 197 passed and 2 PowerShell scenarios
skipped. The first full local run selected macOS CPython 3.9.6 and failed
the two Python PTC scenarios; the final full run prepended the existing
CPython 3.12.13 bin directory to PATH, with no runtime or fixture workaround.
SHA256 guards confirmed all 363 retained historical inputs unchanged;
JSON comparison confirmed the four changed logs differ only by cwd context.
TextMate scanning cost grows superlinearly with line length, so a
generated line of tens of thousands of characters blocked the page for
seconds when the sidebar previewed the file. Lines of 1,000 or more
UTF-16 units now render as one plain run in every highlighted surface.
Integrate the observed master checkpoint 7ab215e23e5d977aae7eb5d4fb91782d39969dbb without rewriting the earlier published checkpoints. Master adds current-directory events, directory-aware consumers, and retained Git worktrees; its old subagent startup paths overlapped the unified activation implementation.
Resolve directory selection within managed startup after acquiring parent ownership. Preserve the original Session origin while setting each new local child directory, retain persisted directory state on cold resume, and pass a required absolute cwd to external providers. Keep unified local/external ownership, full completion notices, parked-input host completion, SDK wait/error/shutdown behavior, and ACP cancellation semantics. Retain removal of the old in-process driver and continuation managers.
Combine SDK wait with the new directory requests, preserve the new master directory/worktree fixtures, regenerate derived catalogs from the combined sources, and refresh only the four stale prompt/schema sidecars. Preserve every protected historical schema, archived note, catalog-v2 acknowledgement, and recorded predecessor input.
Validation: full build; 59 CI static gates; 855 subagent tests with 100% coverage on changed core/external sources; affected consumer suites including 119 repaired-fixture tests; SDK 11 and headless 16 recorded scenarios; two local headless expected cases; six built nested SDK/ACP cases; lint, strict cast inventory, workspace constraints, and whitespace checks. Native Windows worker diagnostics remain additive and require the next CI run to classify the original crash.
Repeated native Windows coverage runs stopped a Vitest fork while a JSONL
migration suite was unfinished. Vitest's generic unexpected-exit error
discarded the process exit code and signal, preventing classification of
the failure despite identifying the missing test file from the inventory.
Reuse the public ForksPoolWorker diagnostic implementation and protocol
fixtures from independently reviewed PR #4388, pinned at
77e215ff76711ec75ed8aa08aa5b414780e539ae. The runtime helper is unchanged.
It reports PID, project, worker identity, outstanding file request, code,
and signal before stock Vitest reports the original failure. It clears
ownership only on the typed testfileFinished response, suppresses expected
stop, and leaves cancellation and failure handling with the original pool.
Wire that existing pool into generated coverage partition configs. Keep
the original worker counts, partition scheduling, test timeouts, coverage
thresholds, and failure verdicts. No environment, raw argv, or test object
is serialized. This commit adds diagnostic evidence; it does not claim to
repair the still-unidentified Windows crash. PR #4388 remains the separate
reviewable owner; no duplicate diagnostic framework or PR is introduced.
Adapt the copied fixtures to the current no-new-unknown-assertions rule:
declare the private preload exit function, validate consumed logger fields
with the public logger type, and assign matcher/rejection values to typed
unknown locals without assertions. Add a concise bilingual testing note.
Validation:
- Diagnostic suite: 10 passed, including a real configured Vitest fork
exiting 23, stock-pool negative control, successful silent shutdown,
unexpected exit 0, early exit, independent forks, and signal metadata.
- Coverage coordinator suite: 51 passed.
- Targeted lint and focused strict TypeScript check passed.
- pnpm exec tsc -b tsconfig.host.json passed.
- verify-no-unknown-casts passed; 1430 existing assertions unchanged.
- doc-sync passed 40 gates; the two initially failing gates were repaired
and rerun successfully: doc-typecheck and verify-doc-budgets.
- Translation pairing and git diff --check passed.
Refs #4387, #4388, #4394.
Co-authored-by: Chinesezjc <[email protected]>
The cwd regression used a fake Context cast through unknown. Updating its service lookup for descendant shutdown changed the exact legacy assertion fingerprint, which the no-new-unknown-casts CI gate correctly rejected.
Exercise initialize, prompt, and wait with the real Context, Agent registry, Session store, and a scripted LLM adapter. Spy on the real service methods to keep the cwd, reasoning effort, and token-limit assertions. Remove both obsolete unknown assertions and prune only their recorded allowances; do not add or broaden an exception.
Validation: the cwd regression passes; all 54 SDK server tests pass with 100% server coverage. The full check:ci:static aggregate passes 59/59, including the strict unknown-cast inventory. No production behavior changes.
The gated final-flush fixture awaited the real flush but dropped its boolean return value. The regression ran correctly under Vitest, but the full Host build rejected the mock against Sessions.flush Promise<boolean>.
Return the real flush result after the gate. This retains the observation order and matches the service signature without a cast or altered production behavior.
Validation: targeted terminal-capture-failure regression passes; the complete build is rerun after this typing correction.
SDK shutdown disposed its root Agent handles without stopping managed
Activations below those exact roots. The manager owns descendant handles
in a separate context, so an interrupted child with parked input remained
registered after shutdown resolved. Its settlement watcher could then
outlive the SDK root and race later projection teardown.
Close scoped descendant admission and await child-first drain before
releasing root handles or the server adapter. Keep draining limited to the
server's exact root Agents so unrelated parent trees remain live. Collect
drain failures without skipping the existing root/adapter teardown, and
preserve the final single-error or aggregate-error result.
Add real AgentLoop/SubagentRuntime regressions for parked children at
one and two levels, child-first disposal, preservation of an unrelated
parked tree, and descendant/root teardown failure aggregation. Correct an
existing Context.get test stub to return its LLM only for the LLM service.
Document the lifecycle order in the server JSDoc and bilingual README.
Validation: 52 SDK server tests with 100% server.ts statements, branches,
functions, and lines; four final shutdown regressions; host TypeScript
build; full lint; 21 quick documentation checks; staged pairing/diff checks.
Doc-sync passed 41/42 checks; the pre-existing config catalog source link
for subagent-acp/index.ts is stale at base and left to integration refresh.
Interrupting a local child can preserve unclaimed steering while leaving the Agent idle. Activation residency intentionally continues in that state, but completion barriers joined its result forever. The real SDK server reproduced the hang for both direct children and a nested parked leaf.
Join progressing work and cleanup while retaining parked inboxes and their activation results. Observe local activity and maintenance, external execution, unpublished ownership holds, and materialization; rescan after changes. Recheck resumed Agents after concurrent idle observations. Inbox insertion and ownership publication wake pending observations, so late non-waking injection cannot strand a result wait. Existing descendants remain responsible for their own work; no second state store or polling timer is introduced.
Document that persistent SDK/ACP hosts keep these children available for later wake-up, while headless shutdown discards unclaimed input. Simplify the final settlement recheck to retry changed readiness uniformly. Update generated API and event references from their sources.
Validation: real SDK negative controls fail at both depths on the prior manager with clean teardown; 545 tests pass across 27 files. Manager, local activation, and headless runner each reach 100% statements/branches/functions/lines. Six deterministic races cover revival, preparation publication/rollback, flush-time injection, and late children. doc-sync 42/42, targeted build, lint, and whitespace checks pass.
Removing the duplicate ACP directory helpers moved its Config declaration by one line. The generated English and Chinese catalogs still referenced the old line, so the exact-source freshness gate rejected the otherwise unchanged configuration.
Regenerate only the affected source links. No configuration fields, defaults, examples, or translated prose change.
Validation: pnpm run gen-config-catalog produced exactly these two link updates; the combined doc-sync run verifies freshness.
A child completion can wake the parent after its first completed turn. If
that summary cannot append turn/start or turn/end, AgentLoop reports an
agent/error and contains the rejected driver. Whole-agent idleness then
resolves, but the headless runner previously reused the older completed
terminal and exited successfully, sometimes printing the older answer.
Retain failures from the exact owned Agent with the current Session log
offset. Only a later terminal committed by the same Session clears that
failure. After work and persistence drain, fail the run before emitting a
final answer when no such terminal exists. Dispose both observers with
the run. Ordinary durable error outcomes and later successful recovery
continue to use the log's final terminal.
Add real AgentLoop regressions for missing summary start/end commits in
plain and JSON output, ordinary hook failures with and without recovery,
recovery after persistence failures, and unrelated Agent errors. Update
the bilingual exit contract and generated event consumer matrix.
Validation: 106 headless package tests; 100% runner statements, branches,
functions, and lines; 9 focused terminal-failure tests after the typed
append wrapper correction; package TypeScript build; full lint including
host build; 21 quick documentation checks; 42 doc-sync checks; diff check.
Local activation results were derived only from committed Session events.
When the first turn/end append failed, no ending existed and the result
defaulted to completed. When the first turn/start append failed, the initial
message remained unclaimed and kept the idle activation resident forever.
Both paths emitted agent/error but had no durable terminal to project.
Retain the log offset of the latest exact-Agent live failure on the local
activation. Clear it only when the same Session commits a turn/end at or
after that offset, so a later recovery can supply its own outcome and a
reentrant older terminal notification cannot erase a newer failure. The
child scope owns both observers and removes them with its Agent handle.
Capture error while that live failure remains. Let the failed local
activation settle despite its own unclaimed inbox, while preserving the
existing requirement to wait for owned descendants. Wake its settlement
observer when a live failure changes readiness. No public API, raw Agent
idle semantics, or descendant teardown policy changes.
Add regressions through real local activation creation for first start/end
append failures, recovery by later durable endings, exact-Agent isolation,
and an error emitted reentrantly during an older terminal notification.
Update the bilingual package contract and generated event consumer graph.
Validation:
- Both first-append regressions reproduced before the fix: turn/start
remained pending; turn/end returned completed instead of error.
- Six focused behavior cases passed after the fix.
- 423 subagent package tests passed; activation.ts and manager.ts reached
100% statements, branches, functions, and lines under scoped coverage.
- pnpm exec tsc -b packages/subagent/subagent/tsconfig.json passed.
- Focused source/test oxlint and git diff --check passed.
- doc-sync passed 41 gates; its sole stale event-graph failure was corrected
with gen-doc-graphs, then verify-doc-graphs passed for all seven graphs.
- README translation pairing regenerated and passed the doc-sync check.
Caller delivery changes result ownership, but does not make local workflow children private or remove their continuable catalog entries. The workflow README stated durable discovery without making the message and cold-resume consequences explicit.
Document that resident workflow children accept continuation and that cold continuation requires persistence and Session query. An ephemeral child therefore returns NOT_RESUMABLE after disposal. This describes current supported behavior and does not add fallback storage or a new child mode.
Validation: checked against startActivation, sendMessage, and cold-resume admission; English and Chinese pairing updated. Full doc-sync follows the combined review fixes.
ACP retained a private copy of the external-provider directory helpers after the other providers adopted the shared implementation. The copies differed on absolute-path validation and imposed separate filesystem branches and maintenance.
Use resolveChildCwd and validateConfiguredCwd from the existing subagent package. Preserve configuration-time relative-path resolution, parent workspace inheritance, missing-directory failures, and ACP diagnostic normalization. This deletes the duplicate directory implementation without introducing an abstraction or dependency.
Validation: 85 passing tests across the ACP provider and shared out-of-process helper suites, including real child process cwd, configured overrides, and invalid-directory rejection.
Parent completion notices now always carry the child closing answer, even when the child can send progress messages. Two assembled headless expectations still asserted the removed omission and made the snapshots-and-artifacts CI lane fail.
Require CHILD_RESULT in the settlement assertion and refresh only the owning parent and stream output expectations. Preserve released replay inputs, event ordering, sandbox denial evidence, and the recorded model responses.
Validation: DSH_EXAMPLE_MODE=lib pnpm run test:expected apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts apps/cli/tests/profiles/headless/tests/subagent-inheritance.expected.e2e.ts (18 passed).
The subsystem result paragraph still said that a parent log records the
final cleanup-failure status and that the result had already been returned.
The manager instead captures the execution result, performs cleanup, sends
that preserved result in the parent notice, and then resolves the public
result. Cleanup failure separately rejects dispose and marks the live
subagent/end event as error.
Correct the bilingual paragraph to name those actual recipients and timing.
Do not imply that the successful execution notice becomes a failed notice,
or change the runtime's independent execution/cleanup outcomes.
Validation: the updated bilingual pair passes. The statement is checked
against finishDisposal/notifySettlement and the existing cleanup-failure
regressions; final documentation gates run before publication.
The initial-cancellation regression supplied a synchronous content array to
defineContentToolFixture, whose executor contract requires a Promise of
content blocks. Vitest executes the fixture, but the strict repository
Host build correctly rejects its signature with TS2739.
Return the same blocks through Promise.resolve. Keep the deterministic
stream/output gates and assertions unchanged; do not widen the shared
fixture API merely to accept this one caller.
Validation: the focused initial-cancellation regression passes. The full
Host build is rerun before publication to validate the fixture signature.
Cancelling during the prompt's initial whenIdle() wait could retain its
slot indefinitely. If a child delivered a result while the aborted root
was unwinding, Agent.whenIdle() followed the replacement driver and waited
for the newly autonomous summary. The earlier child-wait recheck could
not help because settlement had not yet reached that child wait.
This is an inherited latent issue: the pre-PR ACP bridge also awaited the
same replacement-following whenIdle() after cancellation. Existing
continuable spawn children could expose it; the unified asynchronous fork
default makes the ordering possible for fork delegations as well.
Give the existing InflightPrompt one cancellation-completion promise,
replacing the local cancellation wake-up and its listener. The private
admission AbortSignal is the cancellation flag instead of duplicated
boolean state. The exact root's emitted status identifies the interrupted
activity's retirement and synchronously adopts that output queue. Later
root work and later output cannot replace the first captured completion.
Record whether cancellation began at public idle. Maintenance has that
public status too, so its next running transition establishes release of
work behind maintenance; the ordinary whenIdle promise provides a
fallback when no transition occurs or plugin disposal detaches observers.
Both observations settle the same promise. Settlement races that promise
against initial idle and child waits, then waits only for the interrupted
activity/output on the cancellation path. No future work is re-cancelled,
child admission remains open, and session close keeps its separate full
quiescence and descendant teardown.
Add two deterministic real-Agent regressions. One gates aborted model
cleanup, previously queued image delivery, and the later autonomous
summary independently. The other gates cancelled maintenance cleanup and
the later summary. Both failed before this fix; both now wait for old work
and output, return cancelled while later work remains gated, and permit a
subsequent ACP prompt to execute another real local delegation. Existing
plugin-disposal tests also verify the idle fallback after observer cutoff.
Update the bilingual ACP semantics and generated event-consumer matrix.
Validation:
- Both focused regressions reproduced pending cancellation before the fix.
- pnpm exec vitest run packages/acp/acp/tests --coverage --coverage.include=packages/acp/acp/src/session.ts --coverage.include=packages/acp/acp/src/index.ts --coverage.reportsDirectory=/tmp/pr4394-acp-owned-cancel-89ec33e5788-consumer-v2
(150 passed; both changed source files have 100% statements, branches,
functions, and lines)
- pnpm exec tsc -b packages/acp/acp/tsconfig.json
- pnpm exec tsdown --config-loader native --env.DSH_BUILD_FACE host --filter '@deepseek-ai/dsh-acp'
- DSH_EXAMPLE_MODE=lib pnpm run test:e2e packages/subagent/subagent-dsh-sdk/tests/nested-delegation.e2e.ts -t '^external tasks.*acp'
(3 passed, 3 unselected)
- Focused lint, named translation pairing, generated graph verification,
and git diff --check passed.
Independent ACP rereview found no remaining issue, passed seven focused
turn/disposal cases, and verified a prepended idle listener can start a
new hanging root before ACP handles the old idle event without retaining
the cancelled prompt.
Preserve the published 78ae4fc2 integration checkpoint and merge the later master tip separately. This master delta changes only the browser-GIF skill to use the managed DSH credential store through normal application configuration; it changes no product source, schema, or test behavior. The existing credential-provider recording overlay already follows that guidance.
Seed a fixed POSIX effective directory before the first request and disable ambient instruction discovery in the two pressure fixtures. Preserve the original project and existing output, headroom, retention, and summary limits while keeping shadowed token counts independent of temporary workspace path length.
Supply the required directory collaborator to current runtime fixtures, verify absolute result navigation and deployment fallback, and retain the valid V4 working-directory recording instead of the unreleased V3 input that contains a V4-only event.
The completion coverage audit left two uncovered paths: replaying an ACP
abort that occurred before listener registration, and ignoring an Agent
error from outside the SDK server's owned root sessions.
Remove the redundant ACP abort replay. The private cancelPrompt method
is the sole owner of admissionController.abort(), sets cancelRequested
first, and never resets it. Settlement checks that sticky cancellation
state after its initial idle observation and before awaiting the local
cancelled promise. A pre-listener abort therefore never needs to resolve
that promise; the listener still handles cancellation during child waits.
Add a real local subagent failure regression for SDK ownership. The child
emits agent/error through the normal AgentLoop path and returns an error
result, while the SDK root's wait and a subsequent prompt both complete.
The test also rejects observer warnings, so dropping the ownership guard
fails on the swallowed TypeError rather than merely executing the branch.
No fake Agent identities or private-state mutation are used.
Validation:
- Focused descendant test passed; removing the ownership guard as a
temporary negative control failed on the unexpected observer error.
- pnpm exec vitest run packages/acp/acp/tests packages/sdk/server/tests packages/subagent/subagent-dsh-sdk/tests --coverage --coverage.include=packages/acp/acp/src/session.ts --coverage.include=packages/sdk/server/src/server.ts --coverage.reportsDirectory=/tmp/pr4394-completion-coverage-00243b2f89e-consumer
(251 tests passed; measured statements, branches, functions and lines 100%)
- Focused lint and git diff --check passed.
The early-cancellation audit also reproduced a separate initial-idle wait
liveness issue. It is intentionally left for its own authorized follow-up
commit rather than being combined with this coverage change.
The SDK subagent provider now uses session/wait to keep a delegated child
runtime alive through nested work. A deployment that explicitly selects an
older dshBin can still initialize and accept a prompt but cannot satisfy
that completion request; it must report an execution failure rather than
silently treating the first idle as success.
State the required operation in the provider's owning failure/recovery
contract and update the current activation upgrade guide to tell users to
upgrade a separately configured runtime with the provider. This adds no
capability negotiation, compatibility fallback, or new runtime behavior.
Validation: upgrade-guide structure/word budget and both bilingual pairs
pass. The requirement matches the provider's direct session/wait request
and the SDK server's explicit unknown-method rejection.
A summary can fail while appending turn/start or turn/end and still leave
the root idle with a stable Session sequence. The SDK wait operation then
returned {}, letting the external provider treat the preceding completed
terminal as the result of the entire task. A listener installed only for
the wait would also miss failures between prompt admission and the wait
request.
Retain one latest live failure on each existing SDK Session record, scoped
to the exact owned Agent. Record the Session's next log offset when the
failure occurs. A turn/end from the exact owned Session clears it only
when that terminal was committed at or after that offset. This preserves
failures raised during terminal publication, keeps ordinary represented
turn errors in the durable outcome, and permits later successful activity
to recover. At stable root/descendant completion, session/wait rejects any
remaining failure. Raw session.status and HarnessSession.run behavior are
unchanged; no generic failure history or new notification is introduced.
Add seven owner-local cases for later start/end failures before and during
wait, subsequent successful prompts, represented errors, and publication
ordering including an undefined thrown value. Six failure cases returned
success before the fix. Strengthen listener teardown coverage to assert
all actual subscriptions are disposed after a disposer fails.
Extend the existing recorded subagent-continuable SDK scenario to exercise
session/wait. It still asserts that run() returns DONE at parent turn 1,
then requires child completion and parent turn 2/idle plus its final
summary to be delivered before the wait response. Bypassing the wait made
that ordering assertion fail. No recorded Session or golden output was
invented or refreshed. Update the protocol/server bilingual READMEs and
regenerate the event-consumer matrix for the new error listener.
Validation:
- pnpm exec vitest run packages/sdk/server/tests/wait.spec.ts before the fix:
6 failed, 1 passed (the represented-error case)
- pnpm exec vitest run packages/sdk/server/tests/server.spec.ts packages/sdk/server/tests/wait.spec.ts (40 passed)
- pnpm run test:e2e packages/subagent/subagent-dsh-sdk/tests/nested-delegation.e2e.ts packages/subagent/subagent-dsh-sdk/tests/loader-composition.e2e.ts (8 passed)
- DSH_EXAMPLE_MODE=lib pnpm run test:e2e packages/subagent/subagent-dsh-sdk/tests/nested-delegation.e2e.ts packages/subagent/subagent-dsh-sdk/tests/loader-composition.e2e.ts (8 passed)
- pnpm run test:snapshot snapshots/sdk/sdk.snapshot.ts -t 'replays subagent-continuable through dsh --profile sdk' (1 passed, 24 unselected)
- pnpm run doc-sync: Host compile/bundle, doc-typecheck and 40 other gates
passed; the generated event graph was stale, then regenerated and passed
pnpm run verify-doc-graphs (all 7 graph docs current)
- pnpm run lint:contracts-ready and focused lint of all changed SDK code
- named translation pairing checks and git diff --check
Independent SDK rereview and 7/7 focused tests found no remaining issue.
The parent task owns unchanged Python advanced/recovery validation on the
combined branch; this commit changes no Python or high-level SDK results.
Master adds the claude-code-mods recorded scenario, whose full assembled
prompt and tool-schema pins still describe foreground fork calls and the
removed run_in_background switch. Git merged this new scenario cleanly,
but its actual tool composition uses this PR's managed activation tools,
so CI rejects the stale request header and shared delegation guidance.
Refresh only the scenario's system-prompt and tool-schema pins. Preserve
its Session recording, model replies, Mods behavior, and runtime code.
The expectation remains a strict comparison with the actual shipped tool
assembly; no comparison rule is relaxed.
Validation: the owning lib-mode scenario passes refresh and then strict
replay. The original mismatch is recorded in the current-head snapshots
and artifacts CI job.
The two-stage real-stack workflow test still expected an empty parent
catalog after the caller-delivery discovery fix. That obsolete assertion
fails deterministically in both Linux and Windows coverage jobs: the two
local children now correctly leave continuable membership records.
Compare catalog identities and modes with independently observed workflow
agent-start identities. Keep the assertions that neither parent followup
nor steer was called, and that both child Agents reached disposal. This
checks the intended separation between durable discovery and result
notification rather than restoring the old missing-navigation behavior.
Validation: all three workflow integration cases pass. The original empty
catalog assertion is the exact failure in both current-head coverage logs.
A later summary can fail while appending turn/start, before AgentLoop
advances phase.turn. Its agent/error event therefore carries the previous
completed turn number. ACP ignored that matching number and reported the
old completed outcome. A failed turn/end append was also ignored and
returned cancelled despite no cancellation.
Retain every live Agent failure observed while the prompt owns queued
work. A subsequently committed turn/end supersedes that live failure and
provides the authoritative terminal reason. If the start/end append fails,
no terminal event clears the error, so ACP rejects the prompt. Remove the
unused turn-number argument from the bridge callback; numeric equality is
not evidence that a failure has a durable outcome.
Add deterministic regressions for later turn/start and turn/end append
failures after an earlier completed turn. Both failed before this change:
start failure returned end_turn, while end failure returned cancelled.
Normal represented model/turn errors retain their existing error mapping,
and the existing cancellation/recovery cases remain covered. Update the
bilingual ACP behavior documentation.
Validation:
- pnpm exec vitest run packages/acp/acp/tests/turns.spec.ts -t 'rejects a later'
rejected both pre-fix outcomes (2 regressions reproduced)
- pnpm exec vitest run packages/acp/acp/tests (148 passed)
- pnpm exec tsc -b packages/acp/acp/tsconfig.json
- pnpm run verify-translation-pairing --write packages/acp/acp/README.md
- git diff --check
The ACP completion loop previously observed root idle and no children,
then awaited outputTail outside that loop. An autonomous root turn could
start while an earlier image update was blocked in conversion. Turn
correlation adopted the new turn and cleared endReason, but settlement
did not wait for that turn. Releasing the old image therefore returned
cancelled without any cancellation while the root was still running.
Keep committed output delivery inside the normal completion observation.
After output drains, re-read root status and Session sequence before
settling; any new root activity returns to the idle/descendant checks.
Read mutable cancellation and idle state through local observation
functions so each post-await decision uses the current values. Preserve
the separate cancellation path and its prompt output drain.
Add a deterministic regression that blocks the initial image update,
allows the first idle/no-child check, starts a gated autonomous root turn,
and releases the original image. The prompt remains pending until the
later turn completes, then reports end_turn. A subsequent ACP prompt
runs another real local subagent through the shared delegation fixture.
The bilingual README defines work accepted during output draining as
part of the still-open ACP activity interval.
Validation:
- The new focused regression failed before the fix: it observed cancelled
while the later root turn remained gated.
- pnpm exec vitest run packages/acp/acp/tests (146 passed)
- pnpm exec vitest run packages/acp/acp/tests/turns.spec.ts (39 passed)
- pnpm run test:e2e packages/subagent/subagent-dsh-sdk/tests/nested-delegation.e2e.ts (6 passed)
- pnpm exec tsc -b packages/acp/acp/tsconfig.json
- pnpm run lint:contracts-ready
- pnpm run doc-sync (41 gates passed; doc-typecheck identified an optional
Agent in the extracted test fixture, corrected with an explicit guard)
- pnpm run doc-typecheck (Host build and all 79 documentation blocks pass)
- pnpm run verify-translation-pairing --write packages/acp/acp/README.md
- git diff --check
Independent rereview confirmed both completion races are fixed and that
an original image-delivery failure remains attributed to the prompt even
when a later root turn has already started.
An ACP prompt can be waiting for descendants while its root is idle.
Cancellation aborts that wait and calls agent.cancel(), but a child's
settlement message can wake the root in the next microtask. The wait race
then returns with the root running, causing settlement to enter another
whenIdle() before checking cancelRequested. An unrelated long-running
summary consequently retains the cancelled prompt slot indefinitely.
Recheck cancellation immediately after the descendant wait, before any
new root idle wait. Cancellation stops work present at the cancellation
call and releases that prompt; child messages accepted afterward remain
autonomous work. Do not re-cancel those later turns or drain descendants,
which would either race further wakeups or close future child admission.
Document this cancellation boundary in the bilingual ACP README.
Add a deterministic regression that delivers a child message in the
microtask after agent.cancel(), gates the new root turn, and verifies the
original prompt returns cancelled while that turn is still gated. After
releasing the autonomous turn, a second ACP prompt invokes a real local
subagent activation through a tool and completes without another cancel.
Declare the test's subagent dependency and retain only its workspace
importer change in the lockfile.
Validation:
- The new focused regression failed before the fix: the prompt result was
undefined while the late root turn remained gated.
- pnpm exec vitest run packages/acp/acp/tests/turns.spec.ts (38 passed)
- pnpm run verify-translation-pairing --write packages/acp/acp/README.md
- git diff --check
A registered provider can be used in a composition without the agents
service. startActivation checked the absent registry against the supplied
parent first, so it reported UNAUTHORIZED instead of identifying the
missing runtime dependency with CONTINUATION_UNAVAILABLE. This misdirected
callers toward parent identity even though no activation manager existed.
Require the manager before checking the live parent. Keep provider lookup,
capability validation, and parent identity enforcement in place; provider
startup still cannot run in an incomplete composition. Document the two
failure conditions in the owning bilingual README.
Validation: the new missing-runtime regression failed with UNAUTHORIZED
before the change and passes afterward. All 51 service tests pass, including
unsupported providers/capabilities and invalid parent identity. The changed
README pair verifies successfully.
The unified delegation tool returns as soon as an activation accepts its
prompt. SDK and ACP child runtimes could therefore reach root idle while
an admitted fork was still running. Both external providers interpreted
that idle edge as successful task completion and disposed the runtime,
returning a waiting message while cancelling the nested work.
Add the opt-in SDK session/wait request for existing SDK-owned sessions.
It waits for root idle, managed descendants, and any resulting root turns,
then requires stable idle state and Session sequence across the final
check. Preserve raw session.status notifications and HarnessSession.run's
receipt-to-next-idle API; only the task-owning SDK provider uses the new
completion operation. Subscribe before prompt admission, validate and
fold queued root events through the FIFO wait response, and retain
accepted partial output on cancellation or transport failure.
Keep ACP prompt ownership through descendant completion and later root
summary turns, including their stop reasons and output-delivery failures.
Race child waiting against the existing prompt cancellation controller;
cancellation releases the prompt without permanently closing descendant
admission or repinning the original model selection onto later turns.
Session close retains child-first descendant teardown.
Add real Loader/profile regressions with a gated nested fork for SDK and
ACP completion, cancellation, and final-summary failure. Add focused
coverage for unknown/malformed wait requests, SDK root summary ordering,
ACP cancellation followed by another prompt, and later-summary image
output draining/failure. Update the five owning bilingual README pairs.
Validation:
- pnpm run build
- pnpm run typecheck
- pnpm exec vitest run packages/subagent/subagent-dsh-sdk/tests/subagent-dsh-sdk.spec.ts packages/sdk/server/tests/server.spec.ts packages/acp/acp/tests/turns.spec.ts (123 tests at initial fix)
- pnpm exec vitest run packages/acp/acp/tests/turns.spec.ts packages/sdk/client/tests/sdk-client.spec.ts (80 tests)
- pnpm exec vitest run packages/acp/acp/tests/turns.spec.ts packages/sdk/server/tests/server.spec.ts (70 tests)
- pnpm exec vitest run packages/acp/acp/tests (144 tests)
- pnpm run test:e2e packages/subagent/subagent-dsh-sdk/tests/nested-delegation.e2e.ts (6 tests)
- DSH_EXAMPLE_MODE=lib pnpm run test:e2e packages/subagent/subagent-dsh-sdk/tests/nested-delegation.e2e.ts (6 tests)
- pnpm run test:docs (21 gates)
- pnpm run doc-sync (42 gates)
- pnpm run lint:contracts-ready
- git diff --check
The unchanged SDK next-idle API remains covered by its existing client
tests; no raw notification or persisted Session event format changes.
A structured local activation now accepts continuation messages, but its
terminal predicate checked only delegated children. A correction accepted
during the model request could therefore remain in either Inbox queue while
the earlier response committed structured_output. The loop later consumed
the correction, but the terminal tool guard prevented revising the captured
value, so callers could receive the obsolete answer as completed work.
Require both Inbox queues to be empty as well as all owned children to be
released before staging a structured result. Keep input admission closed
from that synchronous staging point through authoritative result policy,
including the enclosing PTC result. Derive this admission state from the
existing staged, pending, and captured records instead of introducing a
second lifecycle controller. Failed results delete their stage and reopen
input; successful capture keeps the existing terminal behavior. Caller-owned
delivery, complete final text, descendant ownership, and cold resume remain
unchanged.
Add deterministic barriers for accepted steering and queued input, and for
Native/PTC post-execute acceptance and rejection. All six new cases failed
against the unchanged runtime for stale results or incorrectly accepted
late input, then passed with the fix. Extend the authored headless structured
scenario to reject answer 41, consume a parent correction, and commit 42.
Its two own schema sidecars also need the current master bash/edit/write
definitions: retaining their previous definitions fails verifyHeaders on
this pinned base. Update the owning bilingual result-admission documentation.
Validation:
- pnpm run build:native-system
- pnpm exec vitest run packages/subagent/subagent/tests/structured.spec.ts
packages/subagent/subagent/tests/continuation.spec.ts
packages/subagent/subagent/tests/external-activation.spec.ts (214 passed)
- pnpm run test:snapshot:refresh snapshots/session/headless.snapshot.ts
-t subagent-structured-parent
- pnpm run test:snapshot snapshots/session/headless.snapshot.ts
-t subagent-structured-parent (1 selected scenario passed)
- pnpm run test:docs (21 gates passed)
- pnpm run doc-sync (42 gates passed)
- pnpm run lint completed its host build; after correcting test deferred
generic types, pnpm run lint:contracts-ready passed
- git diff --check
SubagentRun.id previously promised uniqueness only within one parent,
but the unified manager indexes every external activation by that id in
one runtime-wide resident map. Publication also checks the live Agent and
Session registries. Two compliant-looking provider results could therefore
collide even when they belonged to different parents.
Document the existing shared Session id namespace across all parents,
providers, and local Sessions. Update the package reference, bilingual
type-equivalent declaration, and the current upgrade guide so custom
providers know to replace parent-local counters with fresh execution ids.
The shipped ACP, DSH SDK, Codex, and Claude Code providers already mint
UUIDs; no runtime mapping or additional retained identity state is needed.
Add a cross-parent collision test through startActivation. It proves that
publication rejects the duplicate, disposes only the new external handle,
leaves the second parent catalog empty, and preserves the first catalog
entry and execution result through normal cleanup.
Validation: all seven external-lifecycle tests pass, as do scoped bilingual
pairing, verify-upgrade-guides, doc-sync, and full lint. Cordis catalog and
inspection-catalog regeneration produces no tracked artifact changes.
The activation refactor removes tool-subagent configuration keys, changes
the model-visible result from foreground or Job-backed output to an
activation receipt, and removes a published in-process driver package.
Updated package references describe the new behavior but do not tell an
existing deployment or package consumer how to migrate those surfaces.
Add the bilingual v0.2.1-alpha.1 upgrade guide and pairing record. Explain
which cordis.yml keys and tool arguments to remove, how to consume parent
completion notices, when host code should await caller-delivery results,
and how publication changes cancellation ownership. Name the existing
Spawn/Fork providers and prepareContinuable API as the driver replacement
without adding a compatibility adapter or promising a foreground mode.
Validation: verify-upgrade-guides, scoped verify-translation-pairing,
verify-md-links, and doc-sync pass. A temporary runtime probe executed the
guide's exact YAML and TypeScript fences and confirmed the caller result,
catalog membership, and absence of a parent notice. The four existing
parent-result tests pass. The probe was removed after execution.
The retained continuation decision still claims a public followup method,
a bare MessageId start result, no activation result/cancellation ownership,
and no human Queue/Steer operations. Those statements contradict the current
startActivation receipt and adjacent-Agent/host message operations already
documented in the same record. The subsystem introduction also says the
activation result can settle before cleanup, despite the manager resolving
that result after teardown and notification.
Correct the current realization in the English/Chinese active documents.
Keep the original single-inbox and residency rationale, distinguish accepted
message identity from the activation receipt, and state the actual result
and disposal timing. Do not change implementation, archived decisions,
provider-level result timing, or runtime APIs.
Validation: doc-sync passed 41 gates on the first run. Its remaining pairing
gate found one Chinese README target using the English locale; correct that
target, re-record the pair, and verify both changed pairs successfully.
The two subagent scenarios introduced by this PR retain tool-schema pins
from before master's Bash deletion/path guidance and filesystem argument
ordering changes. Their actual runtime behavior and recorded model replies
still replay, but strict assembled request-header comparison now rejects
those stale schema descriptions and required-argument order.
Refresh only the three owning schema-pin files against the merged shipped
profile. Preserve every Session recording, model stream, event reference,
timestamp, prompt, and runtime implementation. This keeps the real header
assertion instead of weakening the normalizer or rolling back master tools.
Validation: both affected headless scenarios pass in refresh mode and then
in strict lib-mode replay; the other 33 selected replays passed unchanged.
The recording workflow currently points only to a repository .env file.
A valid key can instead live in ~/.dsh/.credentials.yaml, so missing
DEEPSEEK_API_KEY and .env do not establish that real-model recording is
blocked.
Name the managed credential source and route it through the existing
credentials-local path override when the recording uses a scratch
DSH_HOME. Preserve application-state isolation and keep secret resolution
inside the normal provider rather than printing or manually extracting
key values. This changes only recording instructions.
The collapsed Windows caption toggle and New Session kept a 50% circle after
the unified radius standard moved compact icon buttons to --dsw-radius-sm, so
the toggle's hover fill changed shape when the sidebar collapsed.
Integrate master 78ae4fc2d4d8ecaebb4b674028f781a358b92cc9 after the four
independent review fixes. Keep this base update separate from those fixes
and from subsequent review changes so the integration is independently
reviewable and the observed-master checkpoint remains preserved.
Retain activation-owned execution, caller-independent local discovery,
and unconditional parent final-answer delivery. Preserve master's preset
scoped scheduling tools and all delegated-child schedule deny rules. Move
the new scheduling regression from the removed driver package into the
surviving subagent package and run it through a real Spawn activation.
Preserve master's removal of runtime invariant plugins, dependencies,
exports, and test mounts. Keep context disposal before fixture directory
cleanup. Combine Python workflow ordering with activation/catalog events
without inventing unsupported source-plane import paths.
Keep all frozen Agent Note archives byte-for-byte identical to master;
do not accept Git's automatic edits to renamed archived notes. Retain
current lifecycle and delivery facts in active documentation, repair its
links, and regenerate catalogs/persistence schemas from merged source.
All 62 master persistence roots remain; only subagent/catalog gains the
PR's external membership extension. Keep master's third-party lock graph
and update only workspace dependency changes.
Validation before this checkpoint: 683 focused tests passed, seven
Windows-only cases skipped; 2,772 archived artifacts verified. Generated
catalog and active-pair checks passed. Fresh artifact build and remaining
build-dependent documentation checks continue before publication.
A local child can have send_message available and still finish with a plain
text answer or use the tool only for progress. Selecting completion content
from tool availability drops that captured answer and leaves the parent
with a status-only notice. The recorded spawn case already demonstrates
this: its child returns CHILD_OK without calling the available tool.
Remove the includeClosingText policy and pass every captured final text
through the existing text-only notice renderer. Preserve structured
results, safe diagnostics, external continuation wording, and caller-only
result delivery. No delivery ledger or additional lifecycle state is
needed. A child that sends its final answer explicitly may repeat it in the
completion notice; that cost is preferable to making result delivery
depend on model compliance.
Extend the real-loop parent-result regression with an available-but-unused
messaging tool, restore the unconditional lifecycle expectation, and pin
text alongside structured results and diagnostics. The new regression
fails on the prior implementation because CHILD_RESULT_42 is missing.
Update the owning bilingual descriptions and the exact parent notice
content in headless and TypeScript SDK recordings. Keep replay streams,
released generations, and unrelated recorded clocks unchanged.
Validation: 317 focused lifecycle/workflow tests; full build; 26 focused
headless/SDK replays; Python SDK advanced/recovery smoke (unchanged
fixtures); test:docs (20 gates); doc-sync (42 gates); full lint;
git diff --check.
Caller delivery previously suppressed both settlement messages and local
catalog membership. Workflows therefore stopped publishing the durable
parent-child edge that the parent header and recursive discovery require.
The workflow panel only opens running members, and the ordinary sidebar
hides subagent Sessions, so completed workflow transcripts had no normal
navigation path even though their logs still existed.
Record every successfully admitted local child in its parent's catalog,
independently of the result recipient. Membership answers which local
Sessions belong to the parent; delivery answers who consumes this activation's
result. Keeping those decisions separate restores completed-child and
descendant discovery without duplicating workflow completion messages or
adding return guidance to caller-owned work. Publication failures retain
the existing rollback transaction for both delivery modes.
These local children intentionally retain the unified continuable mode:
list_agents includes them, the parent catalog opens their transcripts after
reload, and the existing direct-parent controls can cold-resume them. No
one-shot execution path or additional lifecycle state is introduced.
Caller-owned external executions still use their consumer's membership
records because they have no local Session to open.
Update the existing bilingual service/workflow documentation and decision
records, refresh the owning workflow/Ralph/advanced Session recordings and
Python SDK event expectations, and preserve unrelated recorded stream times.
Validation:
- Full repository build passed.
- 443 subagent, workflow-host, and Ralph tests; 14 list_agents tests passed.
- Four focused headless workflow/advanced snapshot refreshes and replays passed.
- Three shipped-Web workflow tests passed, including completed-child catalog
navigation after reload and the continuable composer.
- Python SDK advanced and scheduler-recovery snapshots refreshed and replayed
through the built dsh CLI with Python 3.12.
- Three discovery regressions fail with the original catalog exclusion and
pass after restoring this fix.
- test:docs (20 gates), doc-sync (42 gates), lint:contracts-ready, Host and
Client TypeScript builds, and git diff --check passed.
Portal the session-header job popover to the document body and place it
with useAnchoredPosition, so the clipping center column and the right
sidebar no longer cut off its right edge.
Restore six expected-output files to the PR merge-base versions so the
subagent activation refactor does not carry unrelated recording churn.
The persistent-tools SDK notifications contain the same parsed records;
only JSON object key order changed. The multi-turn, empty-response-retry,
error-finish, packed-chunks, and text-turn writer oracles were refreshed
from V3 to V4 representations together with volatile stream clocks and
hook timing. These cases do not exercise the changed subagent behavior.
The snapshot harness explicitly accepts older supported writer oracles
and restores them through the strict Session catalog before comparison.
Keeping the existing outputs retains the same assertions and historical
coverage without rewriting canonical released Session generations.
Validation:
- pnpm install --frozen-lockfile completed.
- pnpm run build passed, including the host addon and runtime artifacts.
- Six focused lib-mode keyless replays passed: SDK multi-turn and
persistent-tools; headless empty-response-retry, error-finish,
packed-chunks, and text-turn (2 files; 6 passed, 155 filtered out).
- Parsed persistent-tools protocol records are equal before and after.
- All six files exactly match merge base 8af775c480d73c4692813387622fe16bf8eadf37;
no canonical Session recordings or production files changed.
- git diff --check passed.
Remove the second child_process mock added during subagent integration.
The existing mock already isolates Git from concurrent workspace writes
and reads the fixture's sourceCommit and dirtyFiles controls. The later
factory replaced those controls with a constant commit and clean status,
so the Windows tests could no longer detect changes made while awaiting
packaging confirmation.
Keep the original mock and its fail-loud command handling. This restores
the existing git-head and git-status rejection cases without changing
packaging behavior or adding a second fixture abstraction.
Validation:
- Owning Vitest file: 3 passed, 7 Windows-x64-only cases skipped on macOS.
- Prior isolated duplicate-mock probe: 2 failures with the duplicate,
2 passes with only the original state-controlled factory.
- Original exact-head Windows CI fails the same two Git-change cases.
JavaScript object enumeration moves canonical array-index property keys ahead
of other string keys. Describing every numeric-looking Loader ID as reordered
is too broad: leading-zero and other noncanonical numeric strings remain
ordinary string properties.
Name the exact key category in the inventory fixture's local comment. The
inventory-* IDs exclude every canonical array-index key, and the real Loader
activation, fixture order, output assertions, and production code are unchanged.
This addresses the automatic review's nonblocking terminology clarification.
Validation:
- Type-aware Oxlint passed for the owning inventory.spec.ts.
- git diff --check passed.
- No behavior or test inputs changed, so the already observed 46-test and
74/74-branch evidence remains applicable.
`collectCliHelp` stops propagating an alias when it reaches a page
already on the current path, so a `help [command]` row that prints its
parent's help no longer recurses until the stack overflows. `serve|s`
command rows are split into separately collected names.
The CLI reference and upgrade guide describe the forwarded-argument
boundary that Commander applies, including a leading pnpm option, and
args.spec.ts pins that `--profile` placed after a leading pnpm option is
forwarded. Generated pages use the repository's two-line generated
header, and docs/AGENTS.md lists the CLI help among the generated
references.
Refs #4114
`scripts/gen-cli-help.ts` runs the source `dsh` launcher with temporary
Harness and Agents homes and captures the launcher, `dsh plugin`, and every
shipped profile's text help (profile names come from `PROFILE_TEMPLATES`).
It recursively discovers subcommands from each page's `Commands:` section,
shows identical alias help once with every invocation, and writes
`docs/cli-help.md`, its Chinese page, and their pairing record. The record
resolves links between the two pages as written, so a first run already
matches `verify-translation-pairing`. `verify-cli-help` checks all three
files and runs as the `cli-help` doc-sync leaf, which the static CI lane
also runs on an unbuilt tree.
`dsh plugin` now owns `-h`/`--help` and reads its own options only before
the forwarded pnpm arguments: `dsh plugin --help` prints its help instead of
failing on the missing `--profile`, while everything after the pnpm command
(including `-h`, `--help`, and `--profile`) reaches pnpm verbatim. A
`--profile` placed after the pnpm command is no longer accepted; the
v0.2.1-alpha.1 upgrade guide covers the migration.
Refs #4114
The parameterized versioned/name-only inventory test reversed plugin arrival
order but left Loader entry IDs random. EntryTree generates hexadecimal IDs
and enumerates its object-backed store with Object.values(). Numeric-only
IDs become JavaScript integer property keys and enumerate before other keys,
so the fixture's declared arrival order did not determine the comparator's
operand order. Both parameter cases could exercise the same orientation
while retaining the correct sorted output, intermittently leaving the right
version's nullish fallback uncovered by the per-file 100% CI gate.
Use the typed root-group update API with fixed non-integer inventory-* IDs
for this fixture. Both requested arrival orders now reach the inventory
collector without integer-key reordering. Keep real Loader imports and
activation, duplicate entries, and the existing output assertions. Explain
the non-obvious identifier requirement locally in the test. Production
sorting, package identity rules, and request fields are unchanged.
A controlled negative uses numeric IDs for versioned entries: all 46 tests
pass, but package source branch coverage is 73/74 (98.64%), with index.ts:186
uncovered exactly as in the observed node 24 coverage failure. The final
non-integer IDs pass the same owning file with 74/74 branches (100%). No
global Math.random interception, retry, timeout, or coverage exception is
introduced.
Validation:
- pnpm install --frozen-lockfile --ignore-scripts installed the existing lockfile.
- pnpm exec vitest run packages/llm/plugin-package-inventory-deepseek/tests/inventory.spec.ts --coverage --coverage.include='packages/llm/plugin-package-inventory-deepseek/src/**/*.ts' --coverage.reporter=text --coverage.reportsDirectory=/tmp/dsh-merge-5719/inventory-negative
reproduced the controlled coverage failure with 46 passing tests.
- The same command with reportsDirectory=/tmp/dsh-merge-5719/inventory-fixed
passed 46 tests and 100% statements (80/80), branches (74/74), functions
(15/15), and lines (68/68) for the package's executable source.
- pnpm --silent run change-scope --base 70996a53b84212db9efafe21a4db3dddd46b47ec
confirms the sole changed path is the owning inventory.spec.ts.
- git diff --check passed.
The capture clone shares one source with the caller's body stream, so the
caller key alone keeps the Request reachable on every path, including when
capture has already stopped at its byte limit. Cover that path in the
regression test and document the process-wide --expose-gc flag.
Refs #5614
When a visible shared file became a content duplicate, reconciliation emitted
a removal notice and discarded its metadata. Its durable state then remained
remove, while the metadata fast path accepted only candidates without any
previous record. Every subsequent unchanged pass therefore reread the file,
even after a later read had populated the cache. This also affected the path
where an edited earlier winner made an unchanged later file a duplicate.
Retain the still-present duplicate's metadata with the removal's deferred
version update. Pass the already-cached state when removal follows an unchanged
metadata probe, and the newly read state when the duplicate's content changed.
Apply this metadata only when the associated removal survives rendering, using
the existing exact-transition filter; confirmed absence still clears metadata.
Allow both never-rendered candidates and previous removals to skip content reads
when unchanged provider metadata and an earlier kept digest identify the file as
a duplicate. Continue probing metadata. Once the winner changes or disappears,
read and promote the shared file normally, then suppress unchanged follow-ups.
Document the metadata guarantee in the local cache types and bilingual README.
Model-visible messages, source records, ordering, and byte budgets stay the same.
Add four production-plugin regressions covering duplication after either file
changes, then promotion after the winner is edited or deleted. They inspect the
provider's actual content reads across two unchanged passes and require exactly
one shared read for promotion, rather than trusting the context's self-report.
Validation:
- All four new cases failed before the fix because each unchanged pass reread
the shared file despite unchanged metadata.
- pnpm exec vitest run packages/context/agent-instructions/tests/agent-instructions.spec.ts -t 'caches a removed duplicate': 4 passed after the fix.
- README pairing was rerecorded; git diff --check passed.
- The removal regression narrows its source kind and compares the change as a
plain typed value; no matcher carrying any is assigned into an expected record.
- Type-aware Oxlint passed for the three affected packages and the new
metadata-failure fixture; focused cache regressions passed again after the
assertion correction.
A content-deduplicated shared file has no visible change record. If its
metadata became unavailable while the harness-home winner changed or vanished,
the reconciler skipped the failed candidate and still published the winner's
replacement or removal. Guidance that remained in the shared file disappeared
from the model's effective instructions. Content-read failures had the same
partial-group behavior even when metadata probing succeeded.
Rollback the probed candidate group whenever any metadata probe is unavailable
or its bounded content read cannot complete. Include candidates without a
visible predecessor and candidates previously removed as duplicates. Reuse
one rollback operation for proposed messages, deferred version updates,
metadata mutations, and retained digest bookkeeping, so a later successful pass
can reconstruct the complete group independent of cache warmth.
A filesystem path can also appear as a project instruction when the Harness
home is the project root. Releasing observed paths on rollback let that later
alias publish the very content withheld by the failed global group. A transient
failure of the first member also left failed and unvisited paths unreserved.
Factor the existing scope-to-file path calculation and reserve every eligible
member's absolute path for the rest of that pass when its group rolls back.
Project aliases therefore cannot bypass group preservation, without additional
filesystem operations. The shared resolver keeps probing and rollback path
calculations identical. Add metadata, content-read, and one-shot first-member
failure regressions for this supported same-file layout and its recovery.
Keep cancellation propagating to the caller. Keep explicit baseline-budget
exclusions outside observation rollback, because their removals are deliberate
policy decisions. No new session fields, baseline identities, polling, retries,
or process-global state are needed. Clarify the metadata/content and hidden
candidate guarantee in the bilingual README.
Add four real-plugin outage/recovery tests for metadata or content failure while
the retained home file is edited or deleted, plus a cold-cache reconciliation
regression. Add an authored headless session scenario with instance-local
provider failure injection: request two keeps baseline A while shared metadata
is unavailable; request three receives the home B replacement and shared A
addition together after recovery. The fixture restores the provider method on
disposal and asserts the real home/shared files before restoring observation.
Declare its test-only filesystem dependency without changing runtime packages.
Regenerate the persistence catalog's source anchors after the shared path
helper import shifts AgentInstructionSource by one line. Its persisted fields
and SHA-256 type fingerprint remain unchanged; no type acknowledgement or
Session migration is required for a source-location-only update.
Validation:
- All five new regressions failed with the intended partial-group emissions
before the source fix.
- pnpm exec vitest run packages/context/agent-instructions/tests/agent-instructions.spec.ts -t 'hidden duplicate has|cold global group|unavailable active candidate|unavailable scope': 8 passed.
- pnpm exec vitest run packages/context/agent-instructions: 174 passed.
- The focused authored headless snapshot refresh and replay passed.
- Restoring the early skip for an unavailable hidden candidate makes the
headless snapshot fail with the premature harness-home replacement.
- README translations were updated together and their pairing rerecorded.
- Three project-alias regressions failed on the initial group rollback fix,
then passed after reserving failed and unvisited candidate paths.
- pnpm exec vitest run packages/context/agent-instructions/tests/agent-instructions.spec.ts -t 'frozen across a project alias|hidden duplicate has|cold global group|caches a removed duplicate': 12 passed.
A baseline can deduplicate identical harness/shared instructions before the
byte renderer omits their retained winner. Removing every deduplicated scope
from excludedScopes then made the shared copy eligible on the next unchanged
step. It injected the broader instructions the baseline had deliberately
omitted, contradicting the retained-baseline budget policy. Project sibling
copies had the same problem.
Allow a duplicate to leave the exclusion set only when the included baseline
contains its candidate group and trimmed digest. Match the digest as well as
the group: a different sibling may fit the budget while the duplicate's winner
does not. Use the included files' original content so truncated and empty
represented winners retain their ordinary duplicate-promotion behavior.
Copies of omitted content inherit that omission for the visible baseline.
Duplicates of represented content remain probed and can be promoted when their
winner changes or disappears. Keep both README languages, the existing shared
root decision, and the local result-field documentation consistent with this
rule. No durable event fields or baseline identities change.
Add plugin regressions for identical global copies across repeated steps and
resumes, and an omitted project digest with a different retained sibling. Add
an authored recorded-session scenario that runs the shipped headless profile
with identical 1900-byte global files and a 700-byte budget: its two model
requests retain one project-only baseline and never inject the shared copy.
Validation:
- Both new plugin regressions failed on the reviewed implementation with two
contexts instead of one, then passed with the fix.
- pnpm exec vitest run packages/context/agent-instructions/tests/agent-instructions.spec.ts -t 'duplicate budget-omitted|different digest fits|content-deduplicated shared|changed shared file': 4 passed.
- pnpm run test:snapshot:refresh snapshots/session/headless.snapshot.ts -t agent-instructions-budget: passed.
- pnpm run test:snapshot snapshots/session/headless.snapshot.ts -t agent-instructions-budget: passed.
- Reverting only src/index.ts to the reviewed head makes that snapshot fail
with an extra agents-global set and 304 bytes of omitted global content.
- pnpm run test:snapshot scripts/session-snapshot-corpus.corpus.ts: 3 passed.
- Scoped bilingual pairing was rerecorded and verified; git diff --check passed.
The shared-root Agent Note still describes agentsHome as an accepted row field
and gives explicit row configuration precedence. The decision retained those
statements from the configurable-root proposal, although the instruction loader
now exposes neither dshHome nor agentsHome. Maintainers could therefore add an
unsupported row override or mistake the skill provider's root options for the
instruction loader's API.
State that instruction loading resolves the harness home from DSH_HOME or
~/.dsh and the shared agents root from DSH_AGENTS_HOME or ~/.agents. Keep the
shared resolver and its tilde/blank-environment behavior, while distinguishing
dsh-skill-filesystem's retained explicit home fields for its own skill roots.
Update the English and Chinese Decision/Consequences passages together and
record their confirmed pairing hashes. Root order, durable scope keys, and the
other decision rationale are unchanged.
Validation:
- pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-05-shared-agents-root-instructions.md
- pnpm run verify-translation-pairing .agents/notes/implemented/architecture/2026-10-05-shared-agents-root-instructions.md: one named pair consistent
- pnpm run verify-agent-note-format: all 276 Agent Notes conform
- git diff --check, scoped to the shared-agents-root-instructions EN/ZH/sidecar triplet: passed
The user-global instruction scope now reads two roots: $DSH_HOME/AGENTS.md
first, then the shared agents root that other agent tools use
($DSH_AGENTS_HOME, else ~/.agents). Both resolve from the process environment
through @deepseek-ai/dsh-home-paths; no config field is added, because the
stack base (PR #5718) removed the per-row home override.
The two files are candidates of one candidate group: identical trimmed content
renders once from the harness home, an unobservable candidate preserves the
group's last-good state, and reconciliation probes both roots so external
edits, removals, and new files become visible change notices. A candidate
dropped as a duplicate keeps being reconciled, so deleting or changing the
retained candidate promotes it. The harness-home file keeps its recorded
`user-global` scope key; the shared file carries `agents-global`.
A user-global delta states `These user-global instructions apply to all work.`
instead of naming its reconciliation scope, and a project path whose first
component would collide with a user-global scope directory renders with a
leading `.` so it keeps its own scope.
The removed dshHome row key still redirected instruction loading away from
DSH_HOME. Schemastery's non-strict object validator preserves unknown keys,
and resolveConfig forwarded the validated row wholesale to the library
discovery normalizer, which intentionally accepts an already-resolved home.
Removing the public field and schema entry therefore left the old runtime
override active and contradicted the upgrade guide.
Copy only the declared discovery controls when normalizing a plugin row.
Home resolution now follows process policy even when the row retains a stale
dshHome or another undeclared home field such as agentsHome. This allowlist
also applies when the shared-agents-root stack layer adds its internal home
parameter, without widening the deployment-facing configuration.
Keep the exported discovery and baseline-loading helpers' explicit home
parameter: it carries a library owner's already-resolved path, rather than
granting a plugin row its own home. Extend that regression through baseline
loading to preserve this distinction. Document the ignored stale row key in
both package READMEs and refresh their pairing record. No Session format,
candidate ordering, budget, or instruction rendering changes are required.
Add a production Cordis/schema regression that mounts a row with both stale
home keys and checks model-visible baseline and refresh content. Before the
fix, its baseline contained the stale row instruction instead of the process
home instruction. Both stages now use the process home, and the same test
will reject an agentsHome leak after propagation into the upper stack layer.
Validation:
- pnpm exec vitest run packages/context/agent-instructions/tests/agent-instructions.spec.ts -t 'ignores a stale row home'
failed before the source fix with the intended content mismatch.
- pnpm exec vitest run packages/context/agent-instructions/tests/agent-instructions.spec.ts -t 'ignores a stale row home|uses an already-resolved home'
passed both focused tests after the fix.
- pnpm exec vitest run packages/context/agent-instructions/tests/agent-instructions.spec.ts
passed all 157 tests with both stale row keys present.
- pnpm exec tsc -b packages/context/agent-instructions/tsconfig.json passed.
- pnpm run doc-sync passed all 42 gates.
- pnpm run test:snapshot snapshots/session/headless.snapshot.ts -t 'agent-instructions'
passed the selected recorded-session replay.
- pnpm run verify-translation-pairing --write packages/context/agent-instructions/README.md
refreshed the bilingual record; doc-sync verified it.
- git diff --check passed.
`resolveDiscoveryConfig` accepted `DiscoverOptions.dshHome` but resolved the
home from the environment alone, so a caller passing an already-resolved home
silently received the process home while the option comment claimed otherwise.
It now normalizes the passed value, which keeps the plugin's mount-time
resolution authoritative for discovery and reconciliation. A test pins that an
owner-resolved home wins over `$DSH_HOME`.
Review follow-ups:
- The guide claimed the removed field was the last per-row home override;
other providers (skill-filesystem, shell-env, credentials-local,
subagent-dsh-sdk, attachment-local) keep their own fields, so it now states
that the removal covers the instruction loader, and step 1 also names the
home-level `$DSH_HOME/cordis.patch.yml` layer.
- `DiscoverOptions` regains an already-resolved `dshHome` that `apply()` passes
from its mount-time `resolveConfig()`, so baseline discovery and
reconciliation share one resolution point instead of reading the environment
separately.
- The package README says the loader reads the process home rather than "the
same home as sessions, skills, and storage", and the resolver Agent Note
states what the skill provider's fields actually select.
- `discoverBaselineInstructionFiles` JSDoc no longer lists a removed option.
The agent-instructions row accepted `dshHome`, letting one consumer read a
different harness home than sessions, skills, and storage. Home is process
policy: the plugin now resolves `$DSH_HOME`, else `~/.dsh`, through
`resolveDshHome()` from `@deepseek-ai/dsh-home-paths`, and the config field and
the library's explicit-home option are gone.
Tests and both snapshot fixtures pin the home with `vi.stubEnv('DSH_HOME', ...)`
and the harness's existing `DSH_HOME` export instead of row configuration.
`dsh-skill-filesystem` keeps its own `dshHome`/`agentsHome` fields.
The removed cordis.yml key gets an upgrade guide.
Node's fetch forwards a caller's AbortSignal only while the Request it was
given is reachable. The fetch observer creates that Request locally, so after
garbage collection an abort after response headers no longer cancelled the
body: capture never finished, Network.loadingFinished was never sent, and the
connection stayed open.
Fixes#5614
Review follow-up: `const closed` beside the boolean `this.closed` reads as the
flag, so name the promise `closing` at every site; the test-side comment
repeated the production mechanism verbatim, so each test keeps only the
ordering assumption its fake encodes.
The EPERM path in the Windows coverage failure also matches the HMR
exact-config watcher: it watches the temp harness home with chokidar's
default 2 s write-settle window, and that spec constructs it on the very
directory its afterEach deletes. Apply the same containment to that
watcher and to the skill-filesystem root watcher, the remaining owners
that enable awaitWriteFinish.
A sloppy-mode .cjs or .js file can spell the name as '\141ppendPluginRecord',
which TypeScript decodes to appendPluginRecord, but the prefilter only
removed the backslash and skipped the file. The prefilter now decodes a
legacy octal escape as TypeScript does, taking three digits only after
0 to 3, so the value stays within 0o377. The spec rejects two spellings
that use three-digit octal escapes, and tests the decoder itself for
the digit limit, which no spelling of the name, whose letters are all
0o1xx, can exercise.
chokidar's close() drops every listener while a scheduled write-settle poll
can still run. That straggler stats a document the home teardown is
deleting, which Windows reports as EPERM, and an 'error' emission with no
listener is rethrown as an uncaught exception: the whole coverage
partition fails while every test file passes.
Re-attach the provider's error listener across the close so the straggler
stays inside the provider, and make it a no-op after disposal.
The caller gate parsed only files whose text held appendPluginRecord or a
\u escape, so a release package could reach the function through
S['\x61ppendPluginRecord'], a \u{61} code point escape, a needless
character escape, or a line continuation, which TypeScript reads as the
same string. The gate now also parses a file when its text with every
escape unescaped holds the name, and its spec rejects each spelling.
- Make the record commit an ES private method, so appendPluginRecord()
is its only runtime entry.
- Document PluginRecordType and PluginRecord on the session subsystem
page, and state in the README and JSDoc that the V3-to-V4 edge renames
unknown ignorable V3 events into the plugin: namespace and that plugins
outside this repository must not call the writer.
- The caller gate parses only files that contain the name or a \u
escape, documents that wildcard re-exports are not reported, and its
spec reads a temporary repository instead of this checkout.
- Cover Session.fromRestore in the record test and name SessionEventMap
in the catalog generator's diagnostic.
classShape dropped only #private properties; methods and accessors with a private name reached the declaration text the cordis_inspect catalog serves. It now drops every member whose name is a private identifier, as the public-api projection in verify-type-equiv does.
Add appendPluginRecord(), which appends one ignorable event whose type is
`plugin:` followed by lowercase slash-separated segments, and
pluginRecordOf(), which reads such an event back. Records are not
SessionEventMap members, so the persistence catalog is unchanged; a build
that does not recognize one retains and skips it, and records never enter
the model-visible surface. Resume and fork carry them with the log, and a
format migration keeps them on a best-effort basis.
Only production source under packages/experimental/ may call the writer.
The verify-plugin-record-callers hygiene gate checks every reference in
repository sources, and the persistence catalog generator rejects a
SessionEventMap member in the plugin: namespace.
Session.append() and the record writer share one commit path, so a record
follows the same reentrancy, detach, and observer-containment rules. The
ignorable-events decision, the session README, the format-version
cookbook, and the plugin-author practices describe the records.
A class's static initialization block has no type-level meaning, and the
public-api projection that verify-type-equiv checks already drops it.
The generated declaration text, which the model-visible cordis_inspect
catalog serves, printed it with the class's public members.
pi-ai 1.0.1 and 1.0.2 bring model catalog updates and adapter fixes:
Anthropic native tool changes define later tools inline, Bedrock binds
replayed thinking blocks, OpenAI-compatible requests can pick sampling
parameters by thinking level, and "model is at capacity" is retried.
The runtime dependencies change only in @anthropic-ai/sdk (0.124.0 to
0.129.0) and @earendil-works/pi-telemetry (^1.0.2).
1.0.2 still re-parses the whole accumulated tool-argument JSON on every
stream delta (earendil-works/pi#9265), so the patch is regenerated
against the 1.0.2 sources: it removes the same six per-delta parses as
the 1.0.0 patch, and only the bedrock-converse-stream.js hunk moves, by a
line.
State the Pi-to-DSH direction of the built-in tool translation, how setActiveTools treats Agent-scoped extension tools, that module-scope extension state is shared by concurrent Sessions, which format edges retain plugin records, and that SessionStartSource already declares clear and compact. Date the npm package count and narrow the plugin note's opening sentence.
Record two proposals from the Pi 1.0.0 compatibility investigation.
The Pi extensions bridge note states the design of an experimental bridge
that loads unmodified Pi extension sources on existing DSH extension
points, and ranks the DSH core mechanisms that are narrower than Pi's
extension API.
The Pi plugins note defers a compatibility layer for Pi's Chord-facet
plugins, whose API upstream declares unstable, and defines six levels of
support with the upstream trigger and the DSH work for each, plus the
runtime differences the plugin model exposes.
pi-ai 1.0.0 adds Sign in with ChatGPT to the `openai` provider, and that
login refuses to start unless the app supplies a stable installation UUID
through LoginOptions.getDeviceId. The harness keeps no installation ID
meant for a third-party account sign-in, so the route offers its API-key
login only; a Loader-composed test pins the offered methods.
catalogProvider(), catalogModels(), and catalogProviderIds() now read one
index that leaves out providers without a chat model, so a hand-declared
route under such a key inherits nothing from pi-ai. The browser-worker
stub gains the builtinModels refusal llm-pi-ai imports and drops the
getBuiltinProviders reader nothing imports. The tool-argument streaming
spec also covers the patched Anthropic Messages path.
The chat-model filter left a provider out whenever pi-ai listed no chat
model for it. The browser-worker preview replaces pi-ai with a stub that
lists no models at all, so every provider was dropped and the empty
directory registration failed the llm-pi-ai mount. Only a provider whose
catalog lists models but no chat model is left out now; the stub gains
the `getAllBuiltinModels` reader the filter calls.
pi-ai 1.0.0 adds classifier and image models to the built-in catalog and
ships `typesafe` with classifier models only. The adapter dispatches chat
requests only, so a catalog provider without a chat model gets no entry in
the configurable-provider directory and no sign-in flow.
Move the pi-ai provider adapter from 0.87.1 to 1.0.0 and re-create the
local tool-argument patch for the new dist files. pi-ai 1.0.0 still
re-parses the whole accumulated argument JSON on every stream delta in
the Anthropic, Bedrock, Mistral, OpenAI Completions, OpenAI Responses,
and pi-messages adapters (upstream earendil-works/pi#9265), so the patch
removes the same six per-delta parses; arguments are still parsed once at
toolcall_end.
The release-age exemption moves to the exact 1.0.0 pi-ai and
pi-telemetry versions, and the generated third-party notices and README
links name the new patch file.
Replace the persisted background marker with a client-only rule. A
foreground run writes run-end before its Tool Step ends and a background
run outlives that Step, so only a Turn closed after a crash or at a fork
boundary shows a run without run-end as interrupted.
The examples subpath alias of the mods bridge points at TypeScript entries
that ship no declarations; a documentation fence never imports them, so the
mapper keeps the authored path instead of failing the gate.
The aliases leave tsconfig.base.json, which doc-typecheck maps to built
declarations, and live in the Vitest projects' resolve.alias; the examples
subpath maps to itself there because example plugin entries ship no
declarations. The README's wrapper fence declares its register import so
the fence compiles on its own.
The coverage partitions list *.spec.ts files only, so the published
*.test.ts files reached no partition and the test kit's mount paths went
uncovered there. examples.spec.ts imports them by computed path; the kit
resolves the mod under test from the outermost describe when the file runs
through a spec.
The band's bordered boxes and buttons draw the theme's 0.5px hairline, as
the elevation gate requires; KNOWN_EVENTS is built from ENGINE_EVENTS
instead of repeating the list.
- Disposal cancels detached runs before the engine waits for timer callbacks,
so a callback parked in a $ call ends instead of blocking unload.
- A forgotten band wakes and ends its watch streams; a refresh asked for
during a pass joins one more pass that settles after it; the Web band drops
an ended stream so the next subscriber opens a fresh one.
- $ has Claude Code's namespaces: an unserved member rejects with
'no implementation for <namespace>.<member>' instead of a TypeError.
- A .catch handler's next makes the rewrite check; a tool rename is refused
like an argument rewrite; a null op answer is reported as no answer;
defineMod returns its disposer from apply; the live turn record is copied
before the chain freezes it; prompt.submit is raised only for batches with
a human message and names the submitting mod as origin.
- $.http.fetch bounds the body while streaming; bandColumns/bandRows are
Config; the client package drops its stale gateway edges; the source
overlay lives in the package and anchors its entries to the file, and the
dsh installation no longer depends on the experimental packages.
A scenario-local guard mod adds a context block after the prompt and
refuses a force push, beside Replay Theater and Token Weather from the
examples; the recording shows the context inside the user's message and
the refusal as the tool result. Blast Radius stays out of the headless
composition because it holds a command until a button press.
Hook results spread the typed result from beneath, element props accept an
explicit undefined as a JavaScript mod passes it, the band's registry
payload is narrowed by assertion, and the example wrappers join the Host
typecheck project.
docs/subsystems/claude-code-mods.md lists every way a mod behaves
differently through the bridge than under Claude Code 2.1.287 — packaging,
events, the chain, $ members, drawing, testing, and the example mods,
including the four official mods assessed by reading. The bridge README
describes defineMod, the band, and the test kit; the band package gets its
README; the group index, release families, config and client catalogs,
module and seam graphs, and the Cordis API catalog are regenerated.
A Client package mounts the bridge's Remote contribution, watches each
session's band through watchBand, draws the serialized tree in the input
dock with Claude Code's Box/Text/Button vocabulary on this theme's tokens,
and sends button clicks back through pressBand with the generation they
were drawn in. An opt-in overlay composes the bridge, the band, and the
three example mods for a source launch.
The bridge keeps one band per session: ui.render is raised through the
mods in load order, the tree's buttons keep their onPress host-side behind
per-drawing action ids, a $.state read during a render subscribes the band
to that slot, and $.ui.open/close/invalidate, the end of a tool.call chain,
and turn.complete redraw it. ClaudeCodeMods is a TypertRemoteService:
watchBand streams snapshots, pressBand runs a button and redraws. null is
an accepted hook answer (a surface drawn empty).
A background workflow run outlives the Tool Step that started it, so the
Web workflow node inferred interruption as soon as that Step closed and
showed live members as interrupted. Mark background runs on
tool-workflow/run-start and infer interruption only for foreground runs.
Refs #5638
A mod is now a plugin built with defineMod() around its register(on,
options); the bridge is the ClaudeCodeMods service (ctx.claudeCodeMods)
that keeps the loaded mods and raises their events. The plugin.json /
hooks.json loader, pluginDirs, and dynamic imports are gone.
prompt.submit context becomes text blocks appended to the human's own
prompt message and $.prompt.submit queues a user-sourced message, so the
bridge adds no message source kind: the persistence record and the
regenerated schema are removed and the artifacts match master again.
Engine correctness from the review round: next() is memoized and every
started run beneath is awaited before a hook's answer settles; a rewritten
tool argument skips the hook with a report pointing at the pre-tool input
rewrite proposal; timers belong to the session whose event started them;
$.tool.call injects a tool's deferred contexts; $.clock.sleep honors the
event signal; a built-in tool answered with { result } is validated against
its output schema before it becomes an error-shaped result; hooks on events
this host never raises are reported at load.
Examples: the three mods from the Claude Code blog post under examples/,
Token Weather and its test unchanged, Blast Radius and Replay Theater
completed from the published fragments, run by their own tests through a
claude-code/testing alias over the new test kit ($.ui.mount/find/press).
Review follow-up on the watchdog rewrite:
- Register the deadline reaction inside `next()` and detach it when the
demand settles. Racing against one watchdog-lifetime promise kept every
settled race reachable through that promise's reactions, retaining each
resolved `StreamChunk` for the length of the stream: a 10,000-value probe
kept 100/100 sampled values before this change and 0/100 after.
- Take the neighboring stall case's 1,000ms idle budget in the new
transport-recovery case: the retry performs a real local HTTP/SSE round
trip, and a 50ms budget would let coverage-lane scheduling decide it.
- Document the contract where it is described: `packages/util/timeout/README`
(both languages) and the bounded-recovery and timeout-library notes no
longer claim that only the transport observes the watchdog signal.
- prompt.submit sees and rewrites only the human's own messages, gains the
rewritten text on an image-only prompt, and keeps only typed result fields
- a mod's $.tool.call reaches only the mods loaded before it, attributed to
the caller, instead of re-entering every mod through the pipeline
- a .catch handler shares the failed hook's beneath state so nothing beneath
runs twice; a late next from an abandoned handler is ignored
- hook results are validated before use; detached runs report rejections
- session.end keeps $.state readable until its hooks settle; agent creation
cancellation abandons a waiting session.start hook; agents is read lazily
- a success a hook marks isError becomes a failure; replacement entries clear
on tools/result; per-agent registrations drop with the agent
- $.store writes are serialized per plugin and bounded in UTF-8 bytes;
$.http.fetch bodies are bounded; timers refuse rescheduling after unload
and unload awaits running callbacks; regex matchers reset lastIndex
An aborted read can stay pending forever: undici drops the error when the
abort lands on an in-flight response-body read (observed on the coverage
lane's Node v24.13.0), and the stalled body of
`llm-retry/tests/transport-recovery.spec.ts` then never settles. The idle
watchdog delegated termination to the iterator observing its signal, so the
request outlived its own deadline and the agent stayed `running` until
Vitest's 90s budget; awaiting the abandoned request generator's `return()`
queued behind that demand and would have re-hung the settled outcome.
`idleWatchdog.next` now races its demand against its own deadline and
rejects with the watchdog's TimeoutReason, so the deadline — not the
transport — decides when an outstanding demand ends. Both adapters that
consume the watchdog stop awaiting the abandoned generator's drain; the
abort they already issue owns socket and SDK teardown.
Evidence: instrumented stress harness at ~1s idle on Node v24.13.0 hung 4/160
iterations before this change (each with the abort delivered at ~1002ms and
the underlying read never settling) and 0/160 after; Node v24.19.0 never
hung. The new case in transport-recovery.spec.ts pins the behavior
deterministically: it fails (times out) with the watchdog change alone and
with no product change at all.
Root build scripts, the desktop bundle and the dev:web watcher pass the
native loader so tsdown never selects the undeclared unrun fallback. A spec
covers the build.ts rejection path.
Without process.features.typescript, tsdown falls back to the undeclared
unrun loader, which fails to import and, once installed, resolves
import.meta.url in packages/client/tsdown.client.ts to the entry config.
Refs #5622
Resolve expected module paths through the platform path module and keep
the signal-termination assertion to POSIX, where a self-killed child has no
exit code.
Type the mod tool's output as the answering text, read JSON into a typed
unknown, and replace the test doubles that went through unknown with a
real $ over a slow invoke, an Agent-comparable facts object, loosely typed
on() calls, and the Loader's own import path.
Add @deepseek-ai/dsh-experimental-claude-code-mods, an alpha bridge that
loads Claude Code mods (plugin directories whose hooks/hooks.json names a
hooks module), runs register(on, options) unchanged, and raises the mod
events from harness extension points: session.start on agent/created,
prompt.submit and turn.start on agent/pre-step, tool.call around
tools/execute, turn.complete on turn/end, session.end on agent/disposed,
and command.run from registered commands. The $ mods API is served over
composed services (commands, tools, user-questions, fs, subprocess,
storage domain, projections); unserved members fail by name.
Includes a middleware chain with Claude Code's failure rules and budget
clock, a claude-code/testing-style test kit, fixtures from the mods
tutorial, full-loop and Loader composition tests, the attribution-only
claude-code-mods message source with its persistence record, and the
regenerated catalogs.
Review follow-up: the developer message history is the net tool-list change
between two consecutive requests, not one entry per successful call, so a
remove/add pair batched into one step logs no message. Derive the expected
history from the recorded request headers, search for the later call after the
earlier one, and accept an already-removed tool in the removal prompt so the
REMOVED reply stays required.
The dynamic tool cache e2e asserted the exact recorded call sequence, so a
model that also removed the temporary tool inside the add turn failed an
otherwise valid run. Assert the calls the cache contract depends on and their
relative order instead, and derive the expected transcript registry history
from those recorded calls so one developer message per successful registry
change is still required.
Moving a loader's element (a keyed list reorder) or showing it again
after a display:none ancestor restarts its CSS animations, and the mount
ref does not run again, so the loader left the shared phase. The loader
now pins its animations to document time zero on every animationstart.
Split the single 'stats' conversation.composer.dock entry into
'activity' (counts, speed, time dialog) and 'usage' (tokens, cache hit)
so another plugin can replace one pill by id instead of reimplementing
the whole row. The rendered pills are unchanged.
The fixed 19387 listener fails when Windows (Hyper-V/WinNAT) reserves a dynamic
port range covering it, even though no process uses the port. Electron already
reads the bound port from the Host's ready message.
Ordinary plugins now show exported icons, and subpath plugins use
<subpath>/icon instead of a package.json. Update the display checklist
and nested-package gate cases to match.
Manifest icon diagnostics name the package.json the author edits.
verify-package-meta rejects a package.json export that resolves to build
output instead of reading it, and PackageManifest.exports uses a narrow
export-target type.
Desktop loads its profile with `loadProfileDirectory`, so the retired
bundle removal moves there from `loadProfile` and covers every loader.
A name in `dsh.profile.bundles` that neither the profile's dependencies
nor the installation holds had no uninstall on the Plugins page: the
button required `installed`. The Host now reports such a selection as
`removable`, and `removeBundle` deselects it without running pnpm. The
page shows uninstall for `installed || removable`.
`@deepseek-ai/dsh-experimental-schedule-bundle` shipped in dsh-v0.2.0-rc.1
and rc.2 and was removed when the Web composition started mounting
Schedule itself. A profile that switched it on kept the name in
`dsh.profile.bundles`, so every start reported an unresolvable bundle and
the Plugins page showed a failed entry with no removal action.
`loadProfile` now removes names listed in `RETIRED_BUNDLES` from any
profile's bundle list, alongside the existing installation-owned tuple
normalization, and rewrites the manifest only when the list changes.
Materialization tagged every untagged <style> in the document for the
materializing plugin, so styles inserted later by other owners (Cordis
dynamic plugins, runtime-injecting libraries) were removed when an
unrelated plugin was toggled.
Resolutions:
- `docs/subsystems/schedule.*` and `packages/client/ui-schedule/README.*` keep
this branch's shipped-composition text with master's newer wording (Rules and
Records tabs, the icon-button linked-session layout).
- `docs/event-producer-consumer.*` regenerated by `gen-doc-graphs`.
Delete the now-unused llm-retry history helper, drop the unreachable
credentials-local refresh rejection handler, and add direct tests for the
gateway SRC skip of unprovided Services and llm-retry projection dedupe.
Delete @deepseek-ai/dsh-invariants, every package's ./invariant companion
and its tests, the invariant gates and Vitest setup, the scoped-event
resolver generator, and the INVARIANT-coded listener rethrow paths.
Update docs, READMEs, Agent Notes, and add an upgrade guide.
Only packages/typert/loader/tests/loader.spec.ts conflicted. Master's side
was kept (master's non-awaited ctx.loader.remove calls, restored by the revert
of the transactional Cordis reload changes) and this branch's two vi.waitFor
conversions were re-applied on top, so the merged file differs from master
only by those two polling assertions.
Subpath plugins are not packages, so they never read a package.json and
take their icon from the exported <specifier>/icon resource, like locale.
Package roots keep manifest text fallback and icon, with ./icon as the
fallback image. Drop the bundle-only reader option.
`@deepseek-ai/dsh-schedule` and `@deepseek-ai/dsh-tool-schedule` now depend on
`@deepseek-ai/dsh-subagent`, and the schedule package no longer imports the
Session controller's ownership predicate, so the committed module graph moves.
Typing at the end of the highlighted command token lands in the styled
node. The decoration split the overflow back out, but splitText copied
the token style onto it, so Lexical's normalization merged the halves
again and the transform looped until Lexical aborted the update. The
DOM kept the native insertion while the editor state rolled back, which
left the composer desynchronized for every later keystroke.
Clear the overflow style in the same transform pass.
Refs #4490
Review follow-ups on the delegated-child rule:
- Every one of the four tools refuses a delegated caller before dispatch,
reading `delegationDepthOf(exec.agent)` and returning the stable
`subagent_session` refusal, so `schedule_list`, `schedule_delete`, and a
content-only `schedule_update` are covered too. The preset `toolFilter.deny`
stays as the layer that keeps the tools out of a child's prompt.
- The service keys the arming refusal on that same delegation accounting
alone; `hasApiSessionSubagentOwner` returns to package-private and
`@deepseek-ai/dsh-schedule` no longer imports it.
- The preset child row is `time-context` again: the `preset-` prefix separated
it from a Host row that no longer exists, and `verify-cordis-config` passes
with the sibling-style id.
- Docs, Agent Notes, and the generated catalogs follow.
Move the auto-growing textarea into ui-primitives as InlineEditor and use it
for both the queued-message and goal editors. It also refits when its width
changes and treats keyCode 229 as IME composition.
A delegated child could arm reminders through the tools its preset mount
passes down, even though delivery can never reach a child's Session. The
refusal now reads the delegation-depth accounting the delegation cap itself
enforces, and the shipped presets deny the four tools to children outright:
- `reminderTargetRefusal` refuses when `delegationDepthOf(agent)` is above
zero, or when the Session is subagent-owned without a depth record.
- `standard`, `cordis`, and `ptc` set `toolFilter.deny` on both delegation
rows, so the four tools leave a child's prompt and reject execution.
`create` still rejects and `update` still returns the non-mutating refusal.
Tighten goal-round-driver docs and regression assertions for the withdrawn
queued round: continuation disarms when the cancelled turn belonged to other
work, and the removal is a durable canceled next-turn splice.
Render one Button for both layouts so crossing the fit threshold keeps focus,
anchor the tooltip on the wrapper so a disabled compact entry still names
itself, observe each tab, and rename the tab in the user and subsystem docs.
Rebind the scrollbar thumb to the l2 pair on the elevated goal strip, treat
keyCode 229 as IME composition, and re-measure the editor when the inline
error changes its width.
Shrink the linked Session entry to a chat icon button with a Linked session
tooltip when the detail strip cannot fit it, hide the tab list scrollbar,
and rename the English Delivery records tab to Records.
A goal round queued behind human work stayed in the inbox after Stop. The
next human prompt claimed that stale round first, pre-step rejected it, and
the driver exited with the human prompt still parked until another send.
The goal edit field was a single-line input: Enter always saved, a newline
could not be entered, and a long objective scrolled sideways on one line.
Use an auto-growing textarea capped at six lines, matching the queued-message
editor: Enter saves, Shift+Enter breaks the line, IME confirmation does not
save, Escape cancels.
The Bash tool accepts sandbox_permissions equal to the current mode with a
blank justification, and a blank justification alone. The terminal card
rejected both, leaving successful commands without an expandable body.
Escalation validity depends on the Session sandbox mode, which only the Host
knows; a rejected call already settles as an error result.
Address the second ds-review-bot round on the bundle Source section:
- remove an http(s) URL's user information up to its last `@`, so a
username containing `@` no longer leaves the password behind;
- keep the dependency name before a local or git spec when the package's
own manifest name differs, so reinstalling restores the alias;
- accept scp-like git addresses with any user name;
- show Built in for a profile dependency whose loaded copy the
installation supplies, instead of hiding the section;
- align the PackageView, subsystem page, and README wording with the
shadowing rule and the kept query string, and assert the source of
not-bundle and unreadable bundles.
Address the first ds-review-bot round on the bundle Source section:
- strip the user information of http(s) and git+http(s) URLs so a private
Git URL's token reaches neither the page nor the plugin_manager tool;
- expand `~` in file:/link: paths to the home directory as pnpm does;
- write jsr: aliases after the dependency name, like npm: aliases;
- give no source for a profile dependency the installation also supplies,
whose installation copy is the one that loads;
- label the English row "Code source" to match the Chinese copy and hold
for built-in bundles, and sync the subsystem page, READMEs, CSS comment,
and tool test.
`update` threw `ScheduleInputError` for a Session subagent routing owns, so the
Remote gateway turned it into a failed result and the Web editor rendered
`rule.error.unknown`; the `timing.subagentSession` message could never appear.
The refusal now travels the non-mutating `ScheduleUpdateResult` path, which the
editor already maps through the same `code` lookup as `schedule_not_found` and
`schedule_conflict`. `create` keeps rejecting because it has no result path.
The bundle page ends with a Source section: the spec that installs the
bundle elsewhere, as `pnpm add` accepts it, or Built in for an official
bundle the installation ships, followed by its current version.
The Host's listBundles carries the new `source` field for profile
dependencies: git addresses and URLs as recorded, file:/link: paths made
absolute against the profile, and registry ranges, tags, or aliases after
the package name.
The measured-p99 source (7,229 events) forks in 1,066.2 ms on hosted CI,
below the 1,485-1,523 ms of the former 99.3% source; its budget drops to
1,375 ms. Record the new higher maxima of the other endpoints.
- Fork the measured p99 at rank 988, the highest top-level rank of that
anchor; rank 990 fell in the 99.3% anchor. Derive fork ranks from the
corpus size and pin their shapes in a unit test.
- Seed separate search and fork roots so fork children never reach the
search index.
- Bound child deadlines by the file limit and give every test a deadline
that outlasts its sequential children, so failures reap children before
cleanup.
- Budget peak RSS of the list, search, and fork processes, and check
every recorded median against its budget.
- Name a missing folded record, correct the list-order comment, state that
search models an opt-in deployment, and keep new dependencies sorted.
On the EPYC 7763 runner the file took 294.5 s, 85 s of it single-process
seeding. Two preparation processes now author, validate, and fold
byte-balanced anchor groups in parallel, and a seed process writes the
corpora from their saved bodies and records. Budgets cover the higher
median of the 7763 and 9V74 runner models.
Keep each case at its hardest setting that fits five minutes of hosted CI:
search and fork stay at 1,000 Sessions with the longest fork sampled once,
the list runs 3 samples over 3,000 Sessions, and seeding validates authored
bodies in memory instead of a separate verification pass. The file's last
case asserts the limit; the benchmark job timeout becomes 20 minutes.
Standard two-CPU hosted runs are 2.4-3.4 times the reference machine, above
the shared reference scale, so the budgets use rounded hosted medians with
the shared 1.25 headroom. The benchmark job took 14.4 minutes with these
cases; its timeout becomes 25 minutes.
Starting every frame compression at once kept one native Zstandard context
per pending frame alive and peaked the seed worker at 7.7 GB RSS, which
exceeds the standard hosted runner's memory. Bound compression like file
writes; the seed now peaks at 2.2 GB and writes identical bytes.
Add a required benchmark over synthetic corpora whose per-Session length
distribution is at least a measured local DSH corpus: list at 1,000 and
5,000 Sessions, cold and built content search and stratified forks at
1,000 Sessions, all through the real SessionController on built plain-Node
workers. Raise the benchmark job timeout to 30 minutes for the added cases.
The reminder stack no longer hides behind an optional experimental bundle.
`packages/bundle/web-app/cordis.patch.yml` inserts `schedule` and
`ui-schedule`, so a shipped profile mounts the task store, the delivery
runtime, and the Automation tasks page; the
`@deepseek-ai/dsh-experimental-schedule-bundle` package and its wiring are
removed.
Preset ownership is unchanged: `standard`, `cordis`, and `ptc` declare the
clock and the four reminder tools in their own scope, and `minimal` declares
neither, so a minimal session carries no reading and no reminder tool even
though the service is mounted.
`apps/cli` depends on `@deepseek-ai/dsh-tool-schedule`, and
`packages/bundle/web-app` declares `@deepseek-ai/dsh-schedule` and
`@deepseek-ai/dsh-client-ui-schedule`: preset and profile rows resolve plugin
names from the installation anchor, so a missing declaration leaves the row
without a fiber and every session creation fails with
`agent-preset/invalid`.
Documentation, Agent Notes, and the recorded Web fixtures follow the shipped
composition.
Review follow-ups: the Electron payload smoke allocates the way the product
does on Windows, so the packaged tree resolves the console host assets it now
depends on; the README states that dependency and its loud failure as a known
limitation; the terminal handle takes the platform this method already
resolved; and the note links the earlier tail-grace note, records why that
grace cannot cover a malformed tail, and drops the claim that the smoke
exercises the operating system host.
The per-platform case reads the third spawn argument to assert the console-host
option is absent on POSIX; an untyped vi.fn() declares no parameters, so the
indexed call argument fails typecheck.
The README, the Ctrl-C comment, and the bare-kill comment still described
Windows' own console host as the one backing the PTY. Name the host the code
now selects, record the exit-event behaviour of both teardown paths measured on
the pool machine, state that a missing conpty asset fails loud, and move the
note to the bug-fix class next to the other Windows console fix. The terminal
case now asserts the option key is absent on POSIX rather than not true.
The note records which categories carry this change: the per-platform unit
case, the master push's Windows standby run as the end-to-end signal, and the
desktop payload smoke for the packaged runtime tree.
The console host Windows provides, on the images the master standby pool runs
(Server 2022, Windows 10 22H2), replays a prompt line that scrolled out of the
viewport as `\ndsh>\x1b[1C`: the trailing space becomes a cursor move, so the
persistent-pwsh readiness path never sees its `dsh> ` tail and every send waits
out the silence tier. The same case passes on the Blacksmith Windows image,
whose console host emits the tail verbatim.
Allocate Windows PTYs with `useConptyDll: true`, so the PTY is backed by the
OpenConsole the node-pty package ships. The option is set only for `win32`;
the terminal spec pins it per platform.
Probing the pool machine with the bundled console host rendered the `dsh> `
tail for all seven prompts of the failing case's command sequence, and
`pty.pid` still resolves to `pwsh.exe` and equals the shell's own `$PID`.
master now ships the reminder stack as the optional
`@deepseek-ai/dsh-experimental-schedule-bundle`, which inserts the rows the
Web composition leaves out. This branch keeps the preset-level ownership
beside it.
Resolutions:
- The bundle inserts `schedule` and `ui-schedule` only. It no longer inserts
`time-context`: the clock belongs to the `standard`, `cordis`, and `ptc`
presets, and a Host row for the same package would mount it in two scopes
and append two readings per step.
- `packages/bundle/web-app/package.json` declares `@deepseek-ai/dsh-time-context`
again: the presets reference it by bare name, so `verify-cordis-config`
requires the dependency.
- The bundle's patch spec, locale, package, README pair, the optional-bundle
composition spec, the plugin-manager component list, and the
`schedule-after` roster assertions describe the two-row bundle and the
preset-owned clock and tools.
- Documentation and Agent Notes follow the same split; the note master
archived stays frozen with its manifest hashes.
Read fields from the YAML syntax tree so non-string scalars and aliases keep
their authored text, show the verbatim block while the field chunk loads or
when it fails, close an empty block at its first delimiter, and refresh the
skill preview golden.
Historical team/message/queued and team/message/delivered records remain
decodable and projected, but Team no longer drains or acknowledges them
after restart. Remove the compatibility dispatcher, its disposal wait,
and its tests, and document that historical pending mail stays undelivered.
The clock row is preset-level, so a `standard`, `cordis`, or `ptc` session
appends one reading per eligible step; the recorded sessions carry that
message and the identifier shifts it causes. The plugin manager loses the
clock wording from the bundle description.
Resolutions:
- `python/sdk-runtime/package.json` and `pnpm-lock.yaml` keep both sides'
additions (`@deepseek-ai/dsh-otel` beside the schedule packages).
- The generated graph pages are re-emitted from the merged sources.
master now ships the reminder stack as the optional
`@deepseek-ai/dsh-experimental-schedule-bundle`; this branch keeps the
preset-level ownership beside it.
Resolutions:
- The Host rows keep master's `disabled: true`. The optional bundle switches
`schedule` and `ui-schedule` on and no longer `time-context`: the clock row
is preset-level, so flipping the Host row as well would mount the plugin in
two scopes and write two readings per step.
- `standard`, `cordis`, and `ptc` declare `preset-time-context` and
`tool-schedule`; `minimal` declares neither.
- `tool-schedule` injects `tools` and resolves `schedule` per scope through
`ctx.inject`, so the row stays valid but produces no tools while the service
is off, and produces them once the bundle mounts it.
- The assembled-Web Schedule spec keeps master's bundle overlay and restates
the shipped `preset-standard` row instead of the deleted fixture, because a
top-level patch cannot reach a plugin a preset declares.
- Documentation and Agent Notes describe both layers; the note master archived
stays frozen with its manifest hashes.
The scenario boots the shipped composition, which disables the Schedule
service and the clock, and the two reminder rows live inside
`preset-standard`. A top-level patch cannot address a plugin a preset
declares, so the spec builds its overlay from the shipped preset and restates
that row with `preset-time-context` and `tool-schedule` enabled, beside the
Host row enables. Directly created Agents mount the default preset so their
tool set comes from the product composition, and the shipped-roster assertion
covers both planes.
The static fixture is deleted: it restated only the Host rows.
A preset row that waits for a disabled service fails the boot audit:
`tool-schedule` cannot resolve `schedule` while the shipped bundle keeps it
off, and the Web preset suite boots exactly that composition. The three
presets now declare `preset-time-context` and `tool-schedule` with the same
`disabled: true`, so the arrangement is recorded while nothing
reminder-related activates; the preset rows are enabled together with the
Host rows when the stopgap is lifted.
`#5175` disabled the three Host rows (`time-context`, `schedule`,
`ui-schedule`) as a temporary stopgap and refreshed the Web fixtures for that
composition. Those rows and fixtures stay as master has them; this branch
keeps its preset ownership of the reminder stack, which takes effect when the
stopgap is lifted.
Resolutions:
- `packages/bundle/web-app/cordis.patch.yml` keeps master's `disabled: true`
on all three rows.
- The three presets declare the clock under `preset-time-context`: a row id
belongs to exactly one plane, and the Host row retains `time-context`.
- `apps/web/tests/schedule-after.e2e.ts`, `apps/web/tests/fixtures/
time-context-every-step.patch.yml`, `web-default-isolation.expected.e2e.ts`,
and the `minimal-preset` session fixture keep master's stopgap-era state.
`packages/bundle/web-app/cordis.patch.yml` inserted `time-context` as a Host
row, so every session in the shipped Web profile — `minimal` included —
received a per-step clock reading its composition never declared. The row
moves into the `standard`, `cordis`, and `ptc` presets; `minimal` mounts
neither the reading nor the reminder tools. The Schedule service row and the
`ui-schedule` client row stay Host-wide. 139a7eb379 carried the bundle,
preset, and runtime-closure half of this move; this commit carries the
fixtures, documentation, and Agent Notes that follow it.
The `minimal` session fixture is refreshed keylessly, the assembled-Web spec
builds its every-step overlay from the shipped preset because a preset plugin
cannot be addressed by id from a top-level patch, and the composition Agent
Note points at the new note that owns the preset-scoped reading.
`SubagentSessionError` shifts line numbers in the Schedule types these
generated pages cite; both generators re-emit them, and the
persistence-catalog pair is re-recorded.
Resolutions:
- `snapshots/web/minimal-preset/tool-schemas.expected.json` keeps this
branch's side: the four reminder tools are absent from a `minimal` session
under the new composition, so master's description refresh of those entries
does not apply.
- `packages/schedule/tool-schedule/src/index.ts` keeps this branch's plugin
shape and ports master's trimmed tool descriptions (20bea14129) onto the
moved definitions; `docs/tool-catalog.md` is regenerated from them.
- `docs/tool-catalog.zh.md` has its schema code block realigned with the
regenerated English side, and the pairing record is re-recorded.
`subagent_session` joins the update-result error union, so the client's
exhaustive mapping needs an entry: the timing switch returns a new
`timing.subagentSession` key and both locale dictionaries carry the copy.
The editor shows it when someone edits a task whose Session subagent routing
owns.
A preset mount reaches the preset's whole subtree, so a `standard`, `cordis`,
or `ptc` subagent now sees the reminder tools the removed
`ctx.agents.roots()` filter kept away from it. A task bound to such a Session
can never be delivered — `ctx.sessionController.resolveAgent` refuses it — so
`ScheduleService.create` and `ScheduleService.update` refuse it with
`subagent_session`, in the operation that creates the obligation rather than
in each consumer.
`list` and `delete` keep serving those Sessions so a task stored before the
rule stays removable, and the tool maps the new code into its error output.
The Agent Note, both package READMEs, and the generated catalogs record the
subtree exposure and the enforcement point.
A preset now mounts `@deepseek-ai/dsh-tool-schedule`, so the executable
deploy manifest must supply it and every required workspace peer reachable
from it: the Python runtime manifest gains the tool package, the Schedule
service and the peers that service requires. The stale product/consumer and
module graphs are regenerated for the new package.
The Host plugin registered `schedule_create`, `schedule_list`,
`schedule_update`, and `schedule_delete` on every live root Agent, so a
`minimal` session received four reminder tools its preset never declared. The
tools move to the preset-scoped Consumer `@deepseek-ai/dsh-tool-schedule`,
which injects `tools` and `schedule` and registers them in the scope that
mounts it: `standard`, `cordis`, and `ptc` carry the row, `minimal` does not.
`@deepseek-ai/dsh-schedule` keeps the Host service, storage, delivery, and the
Automation surface.
The five `minimal` assertions in the Web preset suite and the `minimal-preset`
tool-schema snapshot return to the bash-only table, the tool catalog is
regenerated under the new owner, and the Agent Note that recorded the previous
composition points at the new one.
Review of the previous commit found two inaccuracies it introduced. The
repeat-tool-reminder producer still writes `form: 'notice'` and its
`<tool> × <count>` summary — `src/index.ts` spreads them onto
`REMINDER_SOURCE` — so only the wrapper should have been dropped. The cron row
also claimed an `inject()` path that `dsh-schedule` does not have:
`runtime.ts` always delivers through `followup()` and flushes the session.
Link `MessageSourceMap` to the page that owns it, and write `V4` as the corpus
does for the format name. Both pairs are re-recorded.
Session format v4 refuses the retired `{ kind: 'plugin', plugin: '<name>' }`
wrapper at message admission, and the v3-to-v4 migration maps released plugin
names to producer-owned kinds. Five paired documents still show the wrapper as
the current shape, so a plugin author copying them writes a source the session
refuses.
The examples now name the kind the producer declares through
`MessageSourceMap`, and the tool cookbook states that declaration requirement;
the wrapper appears only as the form the admission check rejects. The two
package READMEs also disagreed with their own sources: time-context writes
`{ kind: 'time-context', form: 'snapshot', sections }`, and
repeat-tool-reminder writes `{ kind: 'repeat-tool-reminder' }` with no `form`
or `summary`.
The installed-wheel smoke checks real model tool use and externally verifies
the created file, a fresh host-only challenge, the copied receipt, and the
unchanged source. DeepSeek can return the requested acknowledgement before
a file-details paragraph; requiring it to be the last line makes correct
SDK execution fail according to the placement of explanatory prose.
Accept the exact acknowledgement on any standalone line, retaining completed
turns, model-requested tool calls, exact file bytes, and session-log checks.
Replay the observed response through both turns and keep negative coverage
for absent or embedded acknowledgements and incorrect files.
'contains steady-state registration failures' flaked on the CI coverage
lane (mock called 0 times): the spec slept a fixed 20ms and asserted an
async continuation had run, which contention can outlast. Convert the
three positive-assertion sleeps to the file's existing vi.waitFor
pattern; the absence-assertion sleeps stay, because polling cannot prove
nothing happened.
2026-08-28 16:15:51 +08:00
6259 changed files with 166695 additions and 61614 deletions
@@ -16,7 +16,7 @@ Every Agent Note has two axes, both encoded in its **path** — `{lifecycle}/{cl
The date in the filename is when the topic was **first proposed** (per git history). Cross-references between Agent Notes use relative markdown links (`[topic](../../implemented/architecture/2026-…-….md)`) — never bare prose or numbers — so they are mechanically checkable and survive moves between folders.
The active lifecycle tree is the working inventory: browse its lifecycle/class folders or search the repository. Do not add a centralized `INDEX.md`; the [no-index Agent Note](implemented/process/2026-07-19-remove-generated-agent-note-index.md) owns the rationale. Implemented records with historical decision value but little future guidance move to the separate frozen [`archived/`](archived/AGENTS.md) tree described below.
The active lifecycle tree is the working inventory: browse its lifecycle/class folders or search the repository. Do not add a centralized `INDEX.md`: it duplicates path metadata and makes unrelated note changes contend on one generated file. Implemented records with historical decision value but little future guidance move to the separate frozen [`archived/`](archived/AGENTS.md) tree described below.
## Classification
@@ -53,7 +53,7 @@ A feature-addition note may be consolidated into the later removal note only whe
## The file format
Every active Agent Note follows one in-file format, enforced by `pnpm run verify-agent-note-format` ([scripts/verify-agent-note-format.ts](../../scripts/verify-agent-note-format.ts), part of `doc-sync`); the rationale for the format — and the alternatives it rejected — is [the uniform-format Agent Note](implemented/process/2026-07-05-uniform-agent-note-format.md). Archived notes retain the format they had when sealed plus the archive-date line above.
Every active Agent Note follows one in-file format, enforced by `pnpm run verify-agent-note-format` ([scripts/verify-agent-note-format.ts](../../scripts/verify-agent-note-format.ts), part of `doc-sync`); the rationale for the format — and the alternatives it rejected — is [archived the uniform-format Agent Note](archived/process/2026-07-05-uniform-agent-note-format.md). Archived notes retain the format they had when sealed plus the archive-date line above.
# Agent Note: The job registry absorbs the observation record; the standalone activity seam is removed
Status: implemented
Archived: 2026-09-30
Superseded: the record declaration (`JobStart.record`, `RecordingJob`, `readRecord`, `pumpJobOutput`) and the split wire (`SessionJob.record` on the session control stream, `ctx.jobOutput`) described below were consolidated into one output ring and direct operations on `ctx.jobs` — see [the jobs seam consolidation](2026-09-03-jobs-seam-consolidation.md). The argument for absorbing the standalone activity seam into `ctx.jobs` still holds.
Loaded 100 of 6259 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.