Codex's confirmation pass found the teardown-framing error went deeper than the three prose spots already fixed: the whenIdle() JSDoc itself (and its mirrors) claimed "the quiescence signal a teardown awaits ... a lifecycle owner disposes the agent through its AgentHandle which ... awaits THIS". The disposer does not call whenIdle() — it does `stop(); await agent.done` directly (packages/core/agent-loop/src/index.ts:271). whenIdle() is the NON-OWNER observation hook; owner teardown awaits the loop-exit promise (done) through AgentHandle.dispose(). Reframe every copy accordingly: - packages/core/agent/src/types.ts: the Agent.whenIdle() contract JSDoc. - packages/core/agent-loop/src/agent.ts: the impl JSDoc. - packages/core/agent/README.md and docs/core-data-structures/core.md (the type-equiv mirror of the types.ts JSDoc — re-copied verbatim). - docs/rfc/proposed/feature/2026-06-14-acp-agent-client-protocol.md:40 and :70: owner teardown via AgentHandle.dispose(); a non-owner observing quiescence uses the interface-level agent.whenIdle(), not hand-rolled agent/status. - Regenerate the cordis catalog (whenIdle source line moved).
core/ — product API spine
The packages every harness build is assembled from: the session log, the system-prompt assembly, the tool registry, the agent vocabulary, and the one concrete loop that drives them. These are product packages — the stable surface plugins and consumers build against.
| Package | Role | ctx key |
|---|---|---|
session/ |
Event-sourced session log + in-memory store | ctx.sessions |
system-prompt/ |
Prompt-section + tool-schema assembly registry | ctx.systemPrompt |
tools/ |
Tool registry + tools/execute waterfall |
ctx.tools |
agent/ |
Agent interface, registry, agent/* event vocabulary |
ctx.agents |
agent-loop/ |
The concrete loop plugin: ReactLoopAgent + the loop driver |
ctx.agentLoop |
agent-loop is the one concrete implementation of the agent seam and lives here because it is the harness's default product loop; everything else in core/ is interface/vocabulary. Plugins depend on the agent vocabulary, never on agent-loop directly, so the loop stays swappable.