# The acp-agent plugin tree: the ACP server. Also the snapshot RECORD config # (the dsh-acp-agent bin selects it for DSH_SNAPSHOT=record): a real llm-deepseek # run whose persisted log the snapshot harness harvests. The swappable DeepSeek # adapter, local bash/filesystem executors, the ACP server app # (@deepseek-ai/dsh-acp-agent), and the optional model-facing fs/subagent/todo # tools loaded below. # # CRITICAL: this tree loads NO stdout logger and NO hmr — stdout is reserved for # the ACP JSON-RPC protocol (see packages/ui/acp). That guarantee is now a # property of @deepseek-ai/dsh-acp-agent (it contains no logger entry), not a # leaf convention: there is no logger here to get wrong. # # Requires DEEPSEEK_API_KEY (and optionally DEEPSEEK_BASE_URL) — the # dsh-acp-agent bin loads the gitignored repo-root .env first (on STDERR only). # The DeepSeek adapter. - id: llm-deepseek name: '@deepseek-ai/dsh-llm-deepseek' config: apiKey: !!js process.env.DEEPSEEK_API_KEY baseURL: !!js process.env.DEEPSEEK_BASE_URL models: - deepseek-v4-flash - deepseek-v4-pro # Local bash executor for agent-core's tool-bash schema (one of several tool # stacks in this tree: filesystem, subagent, and todo_write load below). - id: bash name: '@deepseek-ai/dsh-bash-local' config: timeoutMs: 60000 # The ACP server app: the agent-core spine + JSONL persistence + the ACP bridge. # Persistence root: $DSH_SNAPSHOT_SESSIONS_ROOT when the snapshot harness sets it # (so it can harvest / isolate the log), else ./.sessions for the demo. - id: acp-agent name: '@deepseek-ai/dsh-acp-agent' config: model: deepseek-v4-flash persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' # The persona: identity + behavior only, nothing about transports or # tooling — tool guidance lives with each tool plugin (descriptions + # prompt sections). {{model}} and {{cwd}} are prompt variables the agent # loop resolves per session (every ACP session carries the client's cwd, # so the persona can state the workspace). persona: | You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Verify your work by running the code or tests. Keep answers brief and factual. # The subagent seam + both in-process backends + two model-facing tools, as leaf # entries after the app (which provides ctx.agents/ctx.tools). spawn (a fresh # child) and fork (a child seeded with the parent's completed-turn prefix) are # both reachable by the model: dsh-tool-subagent is loaded once per backend with # a distinct toolName (subagent → spawn, subagent_fork → fork), so a multi-child # scenario can exercise both transports. - id: subagent name: '@deepseek-ai/dsh-subagent' - id: subagent-spawn name: '@deepseek-ai/dsh-subagent-spawn' config: providerName: spawn - id: subagent-fork name: '@deepseek-ai/dsh-subagent-fork' config: providerName: fork - id: tool-subagent name: '@deepseek-ai/dsh-tool-subagent' config: provider: spawn toolName: subagent - id: tool-subagent-fork name: '@deepseek-ai/dsh-tool-subagent' config: provider: fork toolName: subagent_fork # Dynamic workflows: the worker-thread engine (ctx.workflows) over the spawn # subagent backend above, plus the model-facing `workflow` tool. The model # writes a JavaScript orchestration script (meta + body); the engine runs it # in its own worker thread and fans agent() calls out as spawn children. - id: workflow-workerthread name: '@deepseek-ai/dsh-workflow-workerthread' config: provider: spawn - id: tool-workflow name: '@deepseek-ai/dsh-tool-workflow' # The model-facing todo_write tool: whole-list task tracking written to the # session log (todo/write), surfaced to the ACP client as a `plan` update. - id: tool-todo name: '@deepseek-ai/dsh-tool-todo' # The repeat-tool-call guard: advisory reminders (injected context, never a # block) when the model re-issues the same tool call with identical arguments; # defaults [3, 5, 8]. Loaded here so the snapshot tier exercises the reminder # transcript (the repeat-tool-guard scenario) — no other scenario repeats a # call three times, so it is inert everywhere else. - id: repeat-tool-guard name: '@deepseek-ai/dsh-repeat-tool-guard' # Filesystem capability stack: local provider, read-before-write/edit policy # gate, then the model-facing read/write/edit tools. Relative filesystem paths # resolve from the server launch cwd; the documented Zed setup launches this # demo from the harness checkout with `pnpm --dir`. - id: fs-local name: '@deepseek-ai/dsh-fs-local' config: cwd: !!js process.cwd() - id: fs-policy name: '@deepseek-ai/dsh-fs-policy' - id: tool-fs name: '@deepseek-ai/dsh-tool-fs' # The Claude Code hook bridge. `configPath` is PROCESS-LEVEL: it is read ONCE at # load and the relative `./hooks.json` resolves against the ACP server's launch # cwd, NOT each `session/new.cwd`. So a single `hooks.json` next to where the # server starts applies to every session; a project-local, per-session hooks.json # is NOT discovered (per-session config resolution is a TODO — see the bridge # README). With no file present the parse fails-soft and the bridge registers # nothing (a silent no-op). Hooks THEMSELVES run in the session cwd (the bridge # passes it as the workdir); only WHERE the config is read from is process-level. # stdout is the ACP JSON-RPC channel — the bridge's warnings go through ctx.logger # (no exporter here), never to stdout. - id: hooks-claude name: '@deepseek-ai/dsh-hooks-claude' config: configPath: ./hooks.json # The Codex hook bridge, loaded alongside the Claude one. It reads its OWN config # file (`./codex-hooks.json`, Codex's snake_case five-event dialect) — the two # bridges cannot share one file, so each owns a distinct path. Same process-level # read-once semantics and same fails-soft-when-absent contract: a launch cwd with # no `codex-hooks.json` registers nothing (a silent no-op through ctx.logger, never # stdout). The example ships both bridges so a scenario can exercise EITHER dialect # end-to-end by seeding the matching file in its workspace/. - id: hooks-codex name: '@deepseek-ai/dsh-hooks-codex' config: configPath: ./codex-hooks.json