A cursor-less adoption (process restart + resume, fork, seam-module reload) replayed the session's full log from seq 0, re-exporting history that already left the process — a resume re-billed its entire stored log on every restart, and a fork re-shipped the parent's prefix under the child's id, doubling query-time counts on OTLP backends with no native ingest dedupe. dsh-session now exposes the fact the constructor already validated but discarded: Session.firstLiveSeq, the constructor-seed length — the first seq appended in this process. header.seedLength cannot serve here: it is the durable fork-lineage boundary, and a resumed session's constructor seed is its full stored log while the header keeps the original fork value (llm-replay and session-query-sqlite depend on that meaning). Constructor seeds also never publish on the session/event firehose, so adoption replaying them was inconsistent with the system's own publication semantics. Adoption's cursor-less fallback starts at firstLiveSeq; seed events still feed the chunk projection, so mid-step continuations re-drop after a resume. Fork streams are no longer self-contained: records now carry session.seed_length (with the existing session.parent_id) so receivers stitch the child's stream onto the parent's. Accepted cost, consistent with at-most-once delivery and recorded in the revival Agent Note: a resume no longer backfills records a previous process failed to deliver — a deployment with that requirement needs the deferred outbox, not replay. Pinned red-first: seeded adoption exports nothing (assertion reversed from the prior seed-readback test, obsolete behavior changed with its test), resume-shaped seed rebuilds the projection without exporting, and fork records carry the stitch attributes.
@deepseek-ai/dsh-tool-cordis
English | 中文
The self-referential cordis toolset: three model-facing tools over the live runtime the agent runs inside. Design home — sandbox semantics, mount lifecycle, cross-mount composition, the generated API catalog, standing decisions: the toolset Agent Note.
What it does
cordis_inspect— read-only report over the runtime: services, the loaded-plugin list, registered tools, the dynamic-mount table, and the catalog-backedapi/eventsreferences. An exactnamewithwhat: "api"orwhat: "events"narrows the report and adds the original source JSDoc.cordis_mount— evaluates model-written JavaScript (the body of an async function) in anode:vmsandbox; the code mustreturna cordis plugin, which is mounted under thecordis-dynamicgroup fiber and tracked asdyn-<n>.cordis_unmount— disposes one mount by id, returning only after quiescence.
Exact model-facing schemas: the generated tool catalog.
Canonical successes are the inspection string, mount { id, pluginName, state, provides, waitingFor }, and unmount { id, pluginName }. Native renderers preserve the existing prose, so programs can use mounted.id while ordinary function calling still sees mounted dyn-1 (...).
Trust stance
The sandbox isolates globals but is not a security boundary. Node globals are absent or redirect to Cordis services such as ctx.fs, ctx.web, and ctx.bash, and writes to globalThis stay local, but host-realm helpers make escape possible. Mounted plugins receive a façade without framework internals, yet its allowed services affect the live runtime. Dynamic tool schemas and annotations cross the realm through iterative JSON cloning and schema normalization, so valid deep declarations are memory-bounded rather than call-stack-bounded; records with JSON-invisible keys and subclassed or decorated schema arrays reject before normalization. Treat this toolset like bash access; see the design and trust stance.
Config
| Field | Default | Meaning |
|---|---|---|
vmTimeoutMs |
5000 |
Bound on the SYNCHRONOUS portion of mount-code evaluation; an async body escapes it |
The generated API catalog
src/api-catalog.ts is generated by scripts/gen-cordis-api.ts from the same AST walk as docs/cordis-catalog and freshness-gated by pnpm run verify-cordis-api (in doc-sync) — never edit it by hand. cordis_inspect intersects it with the live service store at call time. Broad api / events reports render summaries and signatures only; an exact name opts into the retained method/event JSDoc, and unknown or non-running service targets fail loud.
Rendering
All three tools render generic cards (read / execute / delete); cordis_mount carries the mount code as rawInput. Presenters are pure functions of the args; results keep the default text rendering.
Export shape
Namespace plugin: named exports name / inject / Config / apply, no default export (docs/postmortem/0001).
Model Experience
Tool schemas
What the model sees
The conversation model sees the generated cordis_inspect, cordis_mount, and cordis_unmount schemas whenever this plugin is visible.
Token effect
Fixed schema cost on every request in that tool view.
KV Cache effect
Prefix-stable while this tool view is unchanged. Scoping or plugin lifecycle changes that hide these definitions may invalidate reuse from the first changed schema token.
Tool-call history and results
What the model sees
Inspect joins selected sections exactly as ## <section> then a newline and the data-dependent body, with one blank line between sections. Its broad API/event reports omit JSDoc; name with what: "api" or what: "events" returns one exact target with its original JSDoc. Mount returns mounted <id> (plugin "<name>", state: <state>), optionally inserting — waiting for service(s): <names> (activates when provided) before the closing parenthesis. Unmount returns unmounted <id> (plugin "<name>"); an unknown id becomes Error: no dynamic plugin with id "<id>" (list mounts with cordis_inspect what:"dynamic"). The submitted mount program remains in the assistant tool-call history.
Token effect
Inspect output and mount code are data-dependent and resent until compaction; lifecycle acknowledgements are small.
KV Cache effect
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
Later requests after a mount
What the model sees
A mounted plugin may register tools, prompt contributions, or listeners that change later requests for the scopes it targets; unmount removes those contributions after quiescence.
Token effect
Indirect token impact equals the mounted plugin's contributions and lasts only for the mount lifetime.
KV Cache effect
Mounting or unmounting a prompt or tool contribution changes later request prefixes and may invalidate reuse from the first changed contribution; an unchanged mount set remains prefix-stable.
Known Limitations and Deferred Work
- The sandbox is containment for honest code, not a security boundary — host-realm helpers on the sandbox global are reachable, so mount code can reach Node; load this plugin as deliberately as you would grant a bash tool (see § Trust stance).
- The
ctxfaçade exposes noeffect()— mount code cannot register a bespoke disposer;on/provide/tools.registercover every mount seen so far, and a guardedeffectwaits on a real need (FIXME(sandbox-effect)). vmTimeoutMsbounds only synchronous evaluation — an async mount body escapes it; there is no async budget on mount code.