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).
@deepseek-ai/dsh-stdio-agent
The terminal stdio chat app: a Cordis app plugin that composes the providerless agent spine (@deepseek-ai/dsh-agent-core) with the front-door cluster a terminal chat needs, and a bin that boots a leaf cordis.yml.
It is the readline counterpart to @deepseek-ai/dsh-acp-agent: both consume the same spine, but each bakes in the OPPOSITE front-door cluster.
What it bakes in
A terminal chat always wants the same cluster, so the package owns it rather than trusting each leaf to re-wire it:
| Plugin | Why it is here |
|---|---|
@cordisjs/plugin-logger-console |
the console logger — stdout is just the terminal here, so logging to it is correct (the ACP app must NOT have this) |
@deepseek-ai/dsh-agent-core |
the spine, pre-creating a main agent from this app's model/systemPrompt |
@deepseek-ai/dsh-session-persistence-jsonl |
durable JSONL session log under persistenceRoot |
@deepseek-ai/dsh-ui-stdio |
the readline UI, bound to the main agent |
@cordisjs/plugin-hmr (the dev/demo edit-reload loop) is deliberately a leaf entry, NOT baked in here: it is a Loader-only, subprocess-only dev plugin — its constructor throws without node --expose-internals + a live loader, and the in-process test tier cannot even import it (so a package whose apply statically pulled it in 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. The demo:echo / demo:coding leaves load it and pass --expose-internals.
The leaf cordis.yml supplies only the swappable backends — an LLM adapter (llm-deepseek for the real model, or the mock mock-llm for a demo) and a bash executor (bash-local) — hmr, plus this app's Config. The whole plugin tree a run loads is therefore: this app's cluster, the spine inside agent-core, hmr, and the two leaf backends.
Config
| Key | Default | Routed to |
|---|---|---|
model |
(required) | the pre-created main agent's model |
systemPrompt |
(required) | the main agent's system prompt |
persistenceRoot |
./.sessions |
the JSONL backend's root directory |
welcome |
ready. |
the stdin-chat banner |
resumeSessionId |
— | resume a persisted session id instead of starting fresh (sourced from an env var in the leaf) |
The bin
dsh-stdio-agent [path-to-cordis.yml] (default ./cordis.yml) loads a gitignored .env from the cwd (DEEPSEEK_API_KEY / DEEPSEEK_BASE_URL), then drives the cordis Loader against the config — the boot glue the examples/*/start.ts files once each duplicated. The demo:echo / demo:coding scripts invoke it.
Example leaf cordis.yml
# A real coding agent: hmr + the DeepSeek adapter + local bash, then this app.
- id: hmr
name: '@cordisjs/plugin-hmr'
config:
root: ['.']
- id: llm-deepseek
name: '@deepseek-ai/dsh-llm-deepseek'
config:
apiKey: !!js process.env.DEEPSEEK_API_KEY
models: [deepseek-v4-flash]
- id: bash
name: '@deepseek-ai/dsh-bash-local'
config:
timeoutMs: 60000
- id: stdio-agent
name: '@deepseek-ai/dsh-stdio-agent'
config:
model: deepseek-v4-flash
systemPrompt: 'You are a CLI coding assistant. Your only tools are bash…'
Swap llm-deepseek for a mock-llm leaf plugin and you have the echo demo — "swap the backend, keep the app".