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).
@deepseek-ai/dsh-acp-agent
The ACP server app: a Cordis app plugin that composes the providerless agent spine (@deepseek-ai/dsh-agent-core) with the front-door cluster an Agent Client Protocol server needs, and a bin that boots a leaf cordis.yml speaking ACP JSON-RPC on stdio.
It is the structured counterpart to @deepseek-ai/dsh-stdio-agent: both consume the same spine, but this one bakes in the OPPOSITE front-door cluster.
What it bakes in — and what it deliberately omits
stdout is the ACP JSON-RPC channel, so the cluster is defined as much by what it LEAVES OUT as what it includes:
| Plugin | Why |
|---|---|
@deepseek-ai/dsh-agent-core |
the spine, pre-creating no agents (ACP session/new creates them on demand) |
@deepseek-ai/dsh-session-persistence-jsonl |
durable JSONL session log (the bridge advertises loadSession) |
@deepseek-ai/dsh-acp |
the bridge that owns stdout for JSON-RPC |
| omitted — it writes to stdout and would corrupt the protocol frames (the stdout-purity footgun) | |
hmr |
omitted — the editor owns the subprocess |
Because the package wires no logger entry, an ACP leaf has nothing to get wrong by default: it only picks backends, so the common mistake — copying a console-logger entry from the stdio config — has no place here. (A leaf author technically can still add @cordisjs/plugin-logger-console as a sibling entry; the package can't forbid that. So the rule stands: never add a stdout logger to an ACP leaf — stdout is the JSON-RPC channel. Use a stderr exporter if you need logs.)
Config
| Key | Default | Routed to |
|---|---|---|
model |
(required) | the per-session agent template the bridge creates agents from |
persona |
— | the deployment persona template (may reference {{model}}/{{cwd}}), routed to dsh-system-prompt |
persistenceRoot |
./.sessions |
the JSONL backend's root directory |
The leaf supplies the swappable backends: an LLM adapter (llm-deepseek for the real model, llm-replay for keyless snapshot replay) and a bash executor (bash-local).
The bin
dsh-acp-agent [path-to-cordis.yml] (default ./cordis.yml):
- loads a gitignored
.envfrom the cwd — skipped in snapshot REPLAY so a stray key can never trigger a live call; - honors
DSH_SNAPSHOT=replayby booting the siblingcordis.snapshot.yml(the keyless replay tree,llm-replayin place ofllm-deepseek); - in a snapshot run, disposes the context on stdin EOF so the session log is fully flushed before exit.
Run it under node --expose-internals: the cordis Loader resolves the config's bare plugin specifiers through its internal module loader, active only under that flag. (demo:acp runs under tsx, whose tsconfig paths map resolves them instead.)
All diagnostics go to stderr — stdout is the protocol.