# Conflicts: # docs/capability-seams.md # docs/config-catalog.md # docs/event-producer-consumer.md # docs/module-graph.md # examples/acp-agent/tests/snapshots/advanced-toolchain/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/bash-spill/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/both-mode-turn/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/cancel-tool-calls/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/cancel/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/code-mode-turn/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/code-mode-workspace-context/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/config-options/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/error-finish/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/escalation-approved/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/escalation-rejected/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/fs-edit/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/fs-escalation-approved/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/fs-policy-reject/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/fs-read-window/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/fs-read/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/fs-terminal-card/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/fs-write-overwrite/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/fs-write/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/handshake/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/hook-cc-posttool-block/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/hook-cc-posttool-context/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/hook-cc-pretool-ask/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/hook-cc-pretool-deny/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/hook-cc-promptsubmit-block/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/hook-cc-promptsubmit-context/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/hook-cc-stop-continue/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/hook-codex-posttool-block/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/hook-codex-posttool-context/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/hook-codex-pretool-block/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/hook-codex-promptsubmit-block/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/hook-codex-promptsubmit-context/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/hook-codex-stop-continue/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/model-switching/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/multi-turn/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/parallel-tool-calls/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/permission-switching/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/repeat-tool-guard/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/skill-load/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/subagent-depth-two-rejection/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/subagent-fork/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/subagent-mixed/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/subagent-multi/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/subagent-spawn/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/text-turn/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/todo-plan/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/tool-call-turn/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/workflow-run/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/workspace-context/stdout.expected.jsonl # examples/acp-agent/tests/snapshots/workspace-edit/stdout.expected.jsonl # packages/core/tools/tests/gen-tool-catalog.spec.ts # packages/ui/acp/acp-feature-support.md # packages/ui/acp/src/index.ts # scripts/gen-doc-graphs.ts
acp-agent example
The DeepSeek Harness SDK agent demo exposed as an Agent Client Protocol (ACP) server over JSON-RPC stdio — drive it from Zed or any other ACP client.
pnpm run demo:acp # needs DEEPSEEK_API_KEY (repo-root .env or env)
pnpm run demo:code-mode acp # the same server in Code Mode: one wire tool, run_code
The leaf config loads the ACP app, DeepSeek adapter, session modes, sandboxed bash, the sandboxed filesystem stack, approval and permission services, model-facing tools, and repeat guard. The app bundles the agent spine, JSONL persistence, and bridge, creates agents on session/new, and keeps stdout logger-free. fs.cordis.yml adds local tool-result spill storage for its dedicated scenarios; code-mode.cordis.yml adds run_code and its generated TypeScript SDK. See Code Mode.
stdout is the protocol
This example loads no stdout logger — stdout carries the JSON-RPC frames, and any other write corrupts them. @deepseek-ai/dsh-acp-demo includes no logger entry, so this leaf has none to get wrong by default; do not add one (use a stderr exporter if you need logs).
Zed configuration
Add to your Zed settings.json under agent_servers:
{
"agent_servers": {
"DeepSeek Harness": {
"command": "pnpm",
"args": ["--dir", "/path/to/deepseek-harness", "run", "demo:acp"],
"env": { "DEEPSEEK_API_KEY": "sk-…" }
}
}
}
The editor sets each session's cwd to the project it opens, and bash uses that directory as its workdir. The current sandbox write boundary is nevertheless fixed when the server starts (workspaceRoot: process.cwd()), so launch the server from the workspace it should be allowed to modify; making that root session-scoped is deferred in the sandbox Agent Note. The filesystem tools now ride the same sandbox policy through @deepseek-ai/dsh-fs-sandbox, so read/write/edit are available under every mode and confined to the same workspaceRoot.
Plan mode
The same demo:acp server composes @deepseek-ai/dsh-mode, so a capable client advertises default and plan in its mode picker. Plan mode adds its guidance section and the reviewed exit_plan_mode tool while leaving the complete coding toolset available; ask_user_question carries blocking decisions through ACP elicitation. Presenting a plan through exit_plan_mode renders the exact logged plan for approval, and a keep-planning response returns the reviewer's free-text feedback to the model. The mode picker and the permission select are independent: switching modes never changes sandbox or approval state, and deployments that need a hard read-only planning floor configure that independent policy rather than baking it into the mode. The plan-mode Agent Note owns the state and review contract.
Snapshot tests (record-once / replay-deterministic)
This example hosts the ACP snapshot suite, including the picker advertisement and both plan-review branches. It replays through dsh-llm-replay, which reconstructs model streams from assistant/chunk events in each scenario's session JSONL. Recording runs the real ACP agent and harvests its logs; refresh keeps the committed transcript as mock input and rewrites current replay outputs. replay.override.json covers throw and hang cases that chunks cannot express, and an optional workspace/ seeds files. The snapshot Agent Note owns the ACP harness design.
Permissions and sandboxing
The default tree composes @deepseek-ai/dsh-sandbox-local, @deepseek-ai/dsh-sandbox-policy, @deepseek-ai/dsh-bash-sandbox, @deepseek-ai/dsh-fs-sandbox, @deepseek-ai/dsh-user-approval, and @deepseek-ai/dsh-permission. Bash and the read/write/edit tools start in workspace-write; a denied operation returns a structured marker, and a retry with sandbox_permissions plus justification becomes a one-shot session/request_permission prompt in the editor. "Allow once" runs exactly that retry under the wider mode (sandbox Agent Note § Escalation).
- One session config option is live: a capable client shows one
Permissionsselect.workspace-writemeans workspace-confined bash plusask;danger-full-accessmeans unconfined bash plusnever. Switching writes onepermission/presetevent through to the sandbox-mode and approval-policy events, andsession/loadreports the resumed value. - Every approval is one-shot: the choices are
Allow onceandReject; a dismissal, rejection, missing editor, or unavailable runner fails closed. - The boundary spans bash and the filesystem tools, and is config-fixed today: bash confines through the OS runner and the
read/write/edittools through an in-process path fence (dsh-fs-sandbox), both keyed to the sameworkspaceRoot— which remains the server's launch directory (a per-session root is deferred).
tests/escalation.e2e.ts boots this default tree keyless, drives the permission select, and—with a key and usable runner—proves both approval outcomes against the filesystem. Most snapshots use that tree and start at danger-full-access so bash fixtures remain runner-independent; scenarios that call read, write, or edit use the fixed full-access fs overlay and a separate request-header pin. The permission-switching and escalation inputs select workspace-write before exercising the bash policy path. No fixture pins a real denial because kernel error text is backend-specific; real confinement remains covered by the sandbox packages' kernel e2e suites.
MVP limitations
The bridge supports N concurrent sessions per connection, each with its own cwd (RFC 011). Prompts support ACP's baseline text and resource_link blocks only; additionalDirectories and mcpServers are rejected. See packages/ui/acp/README.md for the full contract.