The keyless Loader-path smokes killed the child 10s after spawn, but a
loaded CI e2e runner routinely needs longer just to boot the unbuilt tsx
tree: on this branch's run the coding-agent smoke burned both retries and
failed at 30s wall-clock, and the sibling smokes passed only on retry x2
(master's latest run shows the same near-misses). The budget guards
against a HANG, not slowness — raise kill to 30s and the vitest test
timeout to 45s so a slow boot no longer masquerades as one.
Pure mechanical rename now that the package's internals are the
worker-thread engine: directory, package name, spec/e2e filenames,
module tags and logger prefixes, tsconfig/knip/run-gates/AGENTS.md
references, example cordis.yml plugin ids, doc links; catalogs
regenerated and the lockfile refreshed.
The outer ring catches up with the engine swap (the package's own
README/JSDoc rode the port commit):
- Seam module doc and README name the worker-thread engine as THE
implementation, with isolated-vm/separate-process sandboxing as the
deferred hardening; the seam service doc states the holder-owned-runs
contract (engine-fiber disposal deliberately leaves live runs to
their holders).
- Seam contract precision: agentsStarted documents the termination-path
degradation to the host-observed count; the events section scopes the
agent-start/agent-end pair to calls that STARTED a child run;
WorkflowRun wording drops the vm-era abandonment language.
- The dynamic-workflows RFC is rewritten in place to the shipped
mechanism (implemented-RFC rule): why worker threads, the thread's
concrete buys, the in-process node:vm first cut recorded under
alternatives considered; the tool section describes the usage policy
as the tool's own prompt section.
- gen-doc-graphs: six workflow/* DYNAMIC_EVENT_DISPATCHERS entries (the
catalog no longer claims nothing dispatches them) and the seam-note
wording; core-data-structures gains its workflow.md index row;
packages/README + AGENTS.md layout line + example cordis.yml comments
say worker-thread; catalogs regenerated.
Both demo:code-mode UIs now share one mechanism: the base example plus a
same-shaped code-mode.cordis.yml include overlay (insert the worker
runtime, flip tools.mode). Previously the REPL side was a hand-forked
example (examples/code-agent) that had also silently diverged — it
dropped compaction and the subagent stack — so the demo's UI argument
switched agents, not just surfaces. The fork is retired: coding-agent
gains the overlay, a Code Mode README section absorbing code-agent's,
and both of its tests (the keyless boot guard, retargeted at the
overlay; the with-key RFC proof, which hand-mounts its own harness and
moves untouched). The RFC's composed-surface and e2e-tier lines, the
examples index, the AGENTS.md smoke table, and the dsh-tools README
link now describe the overlay shape.
Verified live: overlay keyless smoke, with-key code-mode e2e from its
new home, demo:code-mode banner + EOF exit, and the acp handshake.
Beyond the mechanical conflicts (provider capability lines vs master's new
inheritsParentContext field; generated catalogs regenerated rather than
hand-merged; knip/lockfile), three master-side reworks required semantic
adaptation of this branch:
- The persona rework removed AgentOptions.systemPrompt, which was the
structured-output instruction's channel. The instruction now rides the
SAME final-request enforcement listener that injects the schema'd tool:
appended per request to final.system (per-request wire state, not agent
prompt state). Tests assert the wire request (adapter.requests) instead
of child.options; the bare-direct-dispatch test pins the no-system arm.
- Tool guidance moved out of deployment prompts into per-tool prompt
sections; the examples' workflow paragraph became a tool:<toolName>
section contributed by dsh-tool-workflow (explicit-ask-only policy),
and both example personas resolve to master's minimal identity+behavior
form. tool-workflow gains inject: systemPrompt (+ peer dep, tsconfig
ref); the export-shape guard updated.
- The uniform-RFC-format gate: the dynamic-workflows RFC restructured to
the implemented/ skeleton (bare Status line; Proposal -> Decision;
What-was-rejected -> Alternatives considered; new Consequences), and
the overall-run-timeout deferral is now recorded in the RFC's Deferred
list. The doc-graphs atlas classification gains the workflows seam
(workflow-vm implementation, tool-workflow consumer).
Master's harness-identity section made "empty assembled prompt" states
unreachable through the loop, so the instruction-append is a plain
undefined-ternary and the structured tests assert append-not-replace.
All snapshot goldens (including workflow-run) replay unchanged. Full
local CI-equivalent gate sequence green on the merged tree.
Review round 2 (tianyicui inline comments):
- dsh-system-prompt itself registers the harness:identity (-100) and
deployment:persona (0) sections — they must survive a swapped loop
plugin, so they leave dsh-agent-loop; the persona text is the plugin's
own validated 'persona' config. The model/cwd variables STAY on the
loop: runtime facts of the agents it drives.
- AgentOptions.systemPrompt is deleted with all its forwarding plumbing:
the app configs' systemPrompt keys become 'persona' routed through
dsh-agent-core (schema = z.intersect of the owners'), the ACP bridge
and tool-subagent stop carrying persona configuration, and subagent
children now render the deployment persona like every other agent.
- Example personas drop transport/interface trivia (ACP, CLI) — facts
irrelevant to the model.
- Root CONTEXT.md removed (not idiomatic); its persona definition was
wrong under the new ownership anyway.
- Docs, READMEs, the prompt-variables RFC, and generated catalogs
updated; new loop test pins the assemble-waterfall escape valve
(an emptied assembly sends NO system field).
Reconciliations beyond textual conflicts:
- product rename (DeepSeek Code -> DeepSeek Harness SDK) applied to the
PR-added assertion in system-prompt.spec.ts that master's rename
commit could not reach
- architecture.md: master's rewrite kept; this PR's prompt-assembly
semantics re-added in the new doc's voice (Turn Flow footnote +
service-spine row), within the 1630-word ceiling
- cordis catalog regenerated into master's split events.md/services.md
(events-and-services.md deleted); module graph and doc graphs
regenerated to pick up this PR's new events and dependency edges
A new capability family at packages/workflow/ in the bash seam shape,
modeled on Claude Code's dynamic workflows: the model writes a JavaScript
orchestration script (export const meta = {...} + plain-JS body), a runtime
executes it, and the script — not the conversation — holds the loop, the
branching, and the intermediate results.
- dsh-workflow (ctx.workflows): abstract WorkflowService + run vocabulary
(WorkflowRun whose result NEVER rejects) + observe-only workflow/* events
carrying data snapshots (id + meta, never the live run), per-listener
contained like subagent/*.
- dsh-workflow-vm: in-process node:vm engine. Meta extraction via a
string/comment-aware scanner (template interpolation rejected; literal
evaluated alone in an empty timed context; statement blanked line-
preservingly so stacks keep script line numbers). Hooks: agent(prompt,
{label, phase, schema, model}) over ctx.subagents, parallel(), pipeline()
(no cross-stage barrier), phase(), log(), args. Fatal-vs-null discipline:
hook misuse (unknown/deferred options, bad arguments, unsupported
schemas, tripped caps, seam start failures, cancellation) throws fatal
WorkflowErrors the combinators RE-THROW — never dissolved into the
per-item null reserved for child failures. Realm boundary: inbound values
materialized by descriptor walks that never invoke accessors (defineProperty
copies, __proto__-safe); outbound values rebuilt in-realm via the
context's own JSON.parse. Determinism bans (Date.now/Math.random/argless
new Date) kept so future resume support cannot break scripts. Caps and
timeouts are validated Config. Every hook promise carries a no-op
rejection consumer (app-boot exits on unhandled rejections).
- dsh-tool-workflow: the model-facing workflow tool, synchronous like
dsh-tool-subagent (start → await → try/finally dispose; abort bridged;
non-completed → isError). Generic render card titled by a textual
meta.name sniff. The tool description carries the authoring contract.
Wired into examples/{coding-agent,acp-agent} with explicit-ask-only
guidance. Coverage at every tier: unit (meta scanner, materializer incl.
counting-getter and __proto__ regressions, combinator semantics,
concurrency ceiling, caps, cancellation, no-unhandled-rejection abandon),
integration over the real spawn stack, with-key e2e (real two-phase run +
the tool through the registry pipeline), and a recorded ACP snapshot
scenario (workflow-run, 1 child session). RFC:
docs/rfc/implemented/feature/2026-07-05-dynamic-workflows.md (deferred
work explicitly listed). AGENTS.md budget 1575 → 1590 for the new group's
layout line.
One principle: every fact in the assembled prompt has exactly one owner.
- dsh-system-prompt: merge-extensible AssembleContext on assemble();
a variable(name, provider) registry; {{name}} interpolation in
renderPrompt, strict (unknown/valueless/malformed references throw);
duplicate section and variable names rejected; assembly carries
resolved section text + variables through the assemble waterfall.
- dsh-agent declares AssembleContext.agent; dsh-agent-loop registers
the agent:persona section (order 0 - identity renders before tool
guidance) and the model/cwd variables, and drops its string join:
renderPrompt(assembly) IS the full prompt.
- Tool guidance moves to its owners: descriptions carry per-tool
semantics; sections only cross-call habits (tool:bash exit-code
habit at order 105; read's not-shell nudge). todo/subagent need no
section - their descriptions already carry the contract.
- SubagentProvider.inheritsParentContext (spawn/acp false, fork true);
dsh-tool-subagent derives truthful per-provider wording and resolves
the provider at load (backend must be listed first).
- Example personas shrink to identity + behavior with {{model}} (and
{{cwd}} in the ACP tree); the welcome banner stops enumerating tools.
RFC: docs/rfc/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md
The packages/README.md group table still described support/ as holding
the stdio UI; and three prose sites credited the keyless smokes with
guarding the app export shape, which a bundle without inject cannot do
(a stray default boots rather than crashes) — the shape is pinned by
the stdio-agent unit suite's explicit unwrapExports assertion; the
smokes prove the composed tree boots.
The readline UI lives inside @deepseek-ai/dsh-stdio-agent as the
in-package stdio-chat module; the packages/support/ui-stdio package is
gone. The app's front-door cluster always includes this UI and nothing
else composes it, so the boundary bought manifest/tsconfig/module-graph/
README/publint surface for a helper that is not independently
swappable — and a product app no longer depends on a support package
documented as not-product-surface.
createStdioChat, the StdioRuntime test seam, and both unit suites moved
verbatim (imports rewired to the module path); the named
name/inject/Config/apply export shape stays, being the contract the
app's ctx.plugin mount consumes. Coverage stays per-file 100%; the
built-bin smoke under plain node and both keyless Loader-path smokes
prove the published artifact and the demos end-to-end.
Implements docs/rfc/implemented/simplification/2026-07-04-fold-stdio-ui-helper.md
(moved from proposed/ and amended to the shipped shape).
Load dsh-fs-local + dsh-fs-policy + dsh-tool-fs after tool-todo (mirroring the
acp-agent wiring), and steer the system prompt to prefer read/write/edit for
file ops with bash for shell/tests/search. Update the welcome line and the
FIXME(config-comments) bash note.
Doc sweep now that both demos ship the fs tools and the seam resolves per-session
cwd: architecture.md and the event-gate RFC no longer say the demos do file ops
through bash / that no config wires the tools; the coding-agent + examples
READMEs and the AGENTS.md layout blurb list the fs tools; the acp-agent README
drops the launch-dir caveat (per-session cwd now works, so the server can launch
anywhere).
(stdio-agent is single-session, so fs-local's cwd = process.cwd() is the
workspace. Keyless boot smoke is blocked locally by an unrelated inotify
watcher-limit ENOSPC that also hits demo:echo; the config parses and the same fs
stack boots green in the acp-agent snapshot tier.)
The compaction e2e is the only coverage of runaway compaction; there is
no keyless full-transcript snapshot. Record why in a FIXME on the e2e
module doc: dsh-llm-replay rebuilds one model call per (turn, step) from
assistant/chunk events, but summarize() assembles its stream locally and
appends none, so the interleaved summarization call is unreplayable until
the replay harness can serve it.
Address @tianyicui's minor-revision review on PR #110:
- Make every BasicCompactConfig knob required except `auto` (defaults
true): there is no data yet to justify default thresholds/budgets, so
a consumer states each value explicitly. Drop the DEFAULTS export and
the constructor's `= {}` default; example cordis.yml, the compaction
e2e, the README, and every test construction site now pass a complete
config (tests route through a `cfg()` helper).
- Add a TODO on estimateContentTokens: char/4 is coarse; replace with a
real tokenizer or post-response usage feedback in a follow-up.
- Add a TODO on the agent/pre-step `fullSystemPrompt` param flagging it
as a smell on a generic per-step seam (compaction is its sole
consumer); a `//` line comment so it stays out of the generated catalog.
Use maxTokens as the provider generation cap and remove the confusing stored-summary max config.
Strip reasoning blocks before storing compaction summaries, reject non-shrinking summaries, and retry bounded re-compaction when the surface remains over threshold.
Add config validation for numeric and type-shaped knobs plus unit and real-API e2e coverage for reasoning-capable summarization.
Honor cancellation and disposal around async pre-step setup before the loop can open a step or call the model.
Route compaction summarization through agent/request so router agents can select the model, and remove the stale model argument from agent/pre-step.
Document serial events and the approximate convergence bound, regenerate the Cordis catalog, and add regression coverage for router compaction, HMR cleanup, and assembly/pre-step interruption.
Record the `todo-plan` snapshot scenario: a real prompt drives the model to call
todo_write, and the golden captures the resulting `plan` sessionUpdate (three
entries, priority synthesized as medium, status 1:1) plus the persisted
todo/write event. Registered in SCENARIOS; replays deterministically keyless.
Add a with-key coding-agent e2e that verifies the WORLD — a real model call to
todo_write lands a todo/write event whose snapshot is a valid, one-in-progress
list — not the agent's self-report. Wire tool-todo into the e2e harness.
Add @deepseek-ai/dsh-tool-todo (a new packages/todo/ group): a model-facing
todo_write(todos: [{content, status}]) tool with whole-list-replace semantics.
Each call appends the full list as a todo/write event to the calling agent's
session log; the current list is the most recent such event (last-write-wins).
Single-owner — a non-agent caller is rejected. Beyond the schema's
type/required/enum checks, execute rejects empty/duplicate content and more than
one in_progress task, narrowing the loosely-typed args into a real TodoItem[].
Both UIs render off the existing session/event: the stdio UI prints a glyphed
checklist; the ACP bridge maps the list to a `plan` sessionUpdate (todosToPlan
synthesizes the priority ACP requires; status maps 1:1). Wired into the
coding-agent, acp-agent, and snapshot example configs with a system-prompt nudge.
Tests: unit (schema, validation, append/replace, no-agent rejection, presentCall,
HMR-safety, Loader export-shape guard), full-loop integration through the agent
loop, the ACP todosToPlan mapping + stream-update arm, the stdio render arm, and
a session/load replay that re-emits the plan. New-group TS wiring added to
tsconfig.base/json/build. RFC + a doc-inventory sweep (architecture, packages
README, AGENTS layout, cookbook group list, example READMEs) ship with it.
The todo-plan ACP snapshot scenario is recorded separately (needs an API key).
Implements the split-the-filesystem-seam RFC. ctx.fs shrinks to a text-storage
provider seam (resolve/stat/readText/streamText/writeText/editText with branded
FsTargetKey/FsVersion and an explicit FsWriteExpectation); the new
dsh-file-context package owns the model-facing policy (read windowing,
observed-state, write/edit freshness) as the concrete ctx.fileContext service.
Authorization is now freshness-based rather than full/partial view: a windowed
read records the file version and authorizes a later edit when the file is
unchanged, removing the dead-end where reading lines 100-150 of a large file
could not edit line 120. editText stays a provider primitive so version guard +
literal match + atomic rewrite remain one critical section, and the stale check
runs before matching so a stale edit reports FS_STALE_VERSION. tool-fs injects
fileContext, never reaching around to ctx.fs (the no-bypass contract).
The compaction e2e never exercised compaction: its window/fixture combo
(contextWindow 8000, thresholdRatio 0.5 → threshold 4000; four small files)
peaked at ~1389 estimated tokens, so compactIfNeeded declined every pre-step
and compact/start never landed. Shrink the window (contextWindow 2400 →
threshold 1200; retainTokens 500 + summarizationMaxTokens 300 = 800 < 1200,
convergence holds) and grow the fixture to six files so a couple of bash steps
reliably cross the threshold. Verified compaction fires and the suite passes
across repeated real-API runs.
Sync docs left stale by the landed compaction work: list compaction.e2e.ts and
keyless-smoke.e2e.ts in the coding-agent README (and fix the wrong "Both
self-skip" count), add compaction to the examples with-key inventory, and
replace the hypothetical compaction/marker / "future plugin" naming in the
session README, session types JSDoc, and the core-data-structures catalog with
the real compact/start, compact/summary, compact/end events.
codex review round 2 (non-blocking) CBR-004: the example's compaction
wiring comment still named the old `agent/pre-request` seam. Renamed to
`agent/pre-step` to match the shipped seam.
Reform the compaction blueprint so a runaway turn survives and the design
stops drifting across review rounds:
- Drop in-flight-turn protection ("layer 2"). Retention is a uniform tail→head
whole-unit walk; the only structural guard is step-alignment. A single turn
that alone exceeds the window now compacts its own early closed steps instead
of being retained verbatim (the failure mode that motivated this).
- Move auto-compaction off the agent/request waterfall onto a new awaited
agent/pre-request loop seam, fired before history derivation. Compaction
mutates the surface; the loop derives once from the result — no double-derive,
and a listener structurally cannot act on not-yet-derived messages.
- Tighten compactIfNeeded to required (session, system, model, signal).
- Enforce a single-pass convergence invariant in resolveConfig: reject configs
where summarizationMaxTokens + retainTokens exceeds the threshold, so a
compaction can never immediately re-trigger.
- Document the crash vs recoverable failure taxonomy; core session repair stays
compaction-agnostic (a log-only orphaned compact/start is inert).
- Wire dsh-compact-basic into examples/coding-agent and add a with-key
compaction e2e (compaction's first real-world exercise + runaway net).
- Rewrite the RFC to encode the blueprint and move it to implemented/.
The runaway-turn snapshot is a named deferred follow-up: dsh-llm-replay cannot
yet serve the interleaved summarization model call.
The shared run driver lived inside dsh-subagent-spawn, so the spawn package
carried fork-aware seeding logic and dsh-subagent-fork depended backward on
dsh-subagent-spawn — the two in-process backends were not independent.
Move the driver (startInProcessRun, depthOf, SubagentDepthError,
InProcessRunOptions) into a new pure-library package
@deepseek-ai/dsh-subagent-inprocess that registers nothing. spawn and fork now
both depend only on that driver and neither knows about the other; spawn no
longer re-exports it and fork no longer imports from spawn.
Also wire BOTH backends in examples/coding-agent/cordis.yml (config-only): load
dsh-subagent-spawn + dsh-subagent-fork + two dsh-tool-subagent instances with
distinct toolNames (subagent → spawn, subagent_fork → fork), demonstrating that
exposing multiple transports needs no code change.
Two merge-blocking bugs in the shared in-process run driver, both rooted in
`readResult` scanning the whole child session and deriving the stop reason only
from `turn/end`:
- A pre-turn `cancel()` cleared the queued prompt before any `turn/end` was
logged, so the run settled `error` instead of `aborted`, violating the
`SubagentRun.cancel()` contract. The driver now tracks that a cancel was
requested and maps the no-turn case to `aborted`.
- A fork child whose own turn produced no `assistant/message` returned the
SEEDED parent's last message as a `completed` success. `readResult` now scopes
to the child's OWN events (after the seed prefix), so a message-less child
yields empty output.
Both fixes carry a regression test proven to go red on the pre-fix driver.
Also: correct the `SubagentRun.id` / event-payload docs (it is the child AGENT
id, not a session id — the backend mints distinct tokens); refresh the stale
`coding-agent` welcome string (subagent is now a tool); and replace the stale
`TODO(sub-agents)` "deferred" prose in the Agent interface, core.md, and
architecture.md with an accurate pointer to the realized seam.
The second PR of the subagent seam: the two in-process backends that run a
child agent on the same cordis context, reusing the agent factory's quiescent
AgentHandle teardown. Both register on ctx.subagents (PR1's named-provider
registry) and share one run driver.
- dsh-subagent-spawn: a FRESH child via ctx.agents.create — own session, the
parent's model by default (overridable), zero inherited conversation. Also
exports the shared in-process run driver (startInProcessRun): mint ids, stamp
cwd/parentSession-lineage/depth, drive the one-shot (send → whenIdle), read
the last assistant/message + turn/end reason, dispose to quiescence.
- dsh-subagent-fork: a child SEEDED with the parent's balanced completed-turn
prefix (the log up to and including its last turn/end), so the child inherits
context. The in-flight unbalanced turn is excluded — a raw seed would fail the
invariants replay. Proven: a regression test goes red if the boundary seeds
the open turn.
- Seam extension: CreateAgentOptions.seed, threaded through AgentLoop.createAgent
→ ctx.sessions.prepare({ seed }) (the primitive resume already used). This is
the fork-lineage path the TODO(sub-agents) markers anticipated.
- Depth: a merge-extensible AgentOptions.subagentDepth (0 top-level, parent+1 for
a child); the depthLimit capability refuses a spawn past request.maxDepth.
Tests: real-loop unit tests for both backends (mock MODEL only, real loop +
invariants), a multi-subagent test (one parent drives a fork AND a spawn child
then keeps working), and a with-key e2e (a real parent delegates via the
`subagent` tool to a real child that writes a file on disk — world-verified).
100% per-file coverage. The coding-agent demo wires the spawn backend + tool.
Snapshot coverage of nested agents is deferred to a stacked follow-up
(TODO(subagent-snapshots)): dsh-llm-replay is a single global positional cursor
that cannot route calls to a parent vs. a child on one context. Recorded in the
RFC's deferrals and a new AGENTS.md rule: designing a subsystem must design its
test infrastructure END TO END up front, verifying the snapshot/e2e harness can
express the new shape — a gap this plan hit.
Codex review of PR #88 found three issues in the example-app extraction:
A1 — examples/coding-agent/README.md's plugin table still listed the OLD
direct-wired leaf entries (agent-loop, session-persistence, src/stdio-chat.ts —
the whole src/ dir is gone). Rewrite it to the four real leaf entries the
current cordis.yml loads (hmr, llm-deepseek, bash, stdio-agent), noting that
tool-bash/persistence/agent/loop now live inside the agent-core + stdio-agent
bundles.
A2 — the three new app/spine packages (agent-core, stdio-agent, acp-agent)
export NO `inject`, so a stray `export default apply` would let unwrapExports
collapse the module and silently DROP name/Config WITHOUT crashing — the
real-load-path smokes would stay green. agent-core is never Loader-unwrapped at
all. Add an explicit export-shape guard per package: assert no `default` export
and that the real Loader.unwrapExports leaves name/Config/apply intact. Verified
each fails when `export default apply` is added.
B — soften "structurally unreachable / cannot wire a stdout logger" overclaims
in the acp-agent/agent-core READMEs and the implemented RFC: a leaf CAN still add
a sibling logger entry; the accurate claim is the app omits one so the default
leaf has nothing to get wrong. Keep the safety directive (never add a stdout
logger to an ACP leaf).
Implements docs/rfc/.../2026-06-20-extract-example-app-packages.md. Each
example was thick — a hand-rolled start.ts, an infra preamble, nested
base.yml/base-core.yml/acp-tail.yml includes, and a coupled front-door
cluster enforced only by prose. This moves the composition into packages so
each example is a thin leaf cordis.yml: pick the swappable backends, load one
app package.
New packages:
- @deepseek-ai/dsh-agent-core (packages/core/agent-core): one bundle plugin
that loads the providerless/executor-less/UI-less spine (timer + llm +
sessions + system-prompt + tools + agents + invariants + tool-bash +
agent-loop) via ctx.plugin(...) inside apply(), and forwards agent-loop's
`agents` list as its own Config (export const Config = AgentLoop.Config,
default []).
- @deepseek-ai/dsh-stdio-agent (packages/ui/stdio-agent): terminal chat APP —
agent-core + console logger + readline UI + a pre-created `main` agent, with
a bin. The demo:echo/coding front door.
- @deepseek-ai/dsh-acp-agent (packages/ui/acp-agent): ACP server APP —
agent-core + JSONL persistence + the acp bridge, NO stdout logger, with a
bin. The stdout-purity footgun is structurally unreachable from the leaf.
Amendment to the RFC: hmr stays a LEAF cordis.yml entry, not baked into
dsh-stdio-agent. hmr is a Loader-only dev plugin (throws without
--expose-internals; the in-process test tier can't even import its decorator
form), so a package statically importing it could never carry the per-file
coverage gate. Unlike the console logger, a stray hmr is not a stdout-purity
footgun, so leaving it at the leaf costs no safety. With hmr out, all three new
packages carry in-process unit specs at 100%.
Boot glue (Loader tail, .env load, snapshot-mode selection, stdin-dispose
lifecycle) moves into each app's bin; start.ts and base.yml/base-core.yml/
acp-tail.yml are deleted. Each app package gets a keyless real-load-path test
that boots through its bin + the cordis Loader (guarding the unwrapExports
export-shape bug class, postmortem 0001). ACP snapshot replay stays green
against the existing committed goldens (pure boot restructuring). RFC moved
proposed->implemented with the amendment recorded; package/example/architecture
docs and the module graph updated.