# Conflicts: # packages/core/scope/README.md # packages/session-persistence/session-persistence-jsonl/README.md # packages/session-persistence/session-persistence-sqlite/README.md # packages/subagent/subagent-acp/README.md # packages/subagent/subagent-fork/README.md # packages/subagent/subagent-inprocess/README.md # packages/subagent/subagent/README.md # packages/subagent/tool-subagent/README.md # packages/support/invariants/README.md # packages/support/subagent-mock/README.md # packages/workflow/workflow-workerthread/README.md # packages/workflow/workflow/README.md
67 lines
4.2 KiB
Markdown
67 lines
4.2 KiB
Markdown
# @deepseek-ai/dsh-subagent-acp
|
|
|
|
The ACP provider runs each subagent in a fresh subprocess and drives it as an Agent Client Protocol client. It is the out-of-process alternative to spawn and fork: the child has its own runtime, session, model configuration, and tools.
|
|
|
|
## Start and ownership
|
|
|
|
`start(request)` performs `spawn` → ACP `initialize` → `newSession` before it fulfills. Fulfillment therefore means a remote session is ready and ownership has transferred to the caller. A spawn, initialization, new-session, or pre-publication cancellation failure rejects only after the subprocess has been reaped.
|
|
|
|
After publication, the provider sends the prompt and collects streamed `agent_message_chunk` text into `SubagentResult.output`. A prompt/transport failure resolves with `stopReason: 'error'`, or `aborted` when the required request signal or disposal requested cancellation.
|
|
|
|
`dispose()` is idempotent. It removes the signal listener, requests ACP cancellation when possible, closes stdin, waits `disposeEofGraceMs`, escalates to SIGTERM, waits `disposeGraceMs`, and finally uses SIGKILL if necessary. Every run uses a fresh process; process pooling is not implemented.
|
|
|
|
## Capabilities and context
|
|
|
|
ACP advertises no start-time capabilities because this process cannot enforce the remote child's depth, tool filter, persona, or structured-output runtime. It also reports `inheritsParentContext: false`: the remote session starts fresh and ignores `request.parent` beyond the seam's required attribution field.
|
|
|
|
## Configuration
|
|
|
|
| Key | Default | Meaning |
|
|
|---|---|---|
|
|
| `providerName` | `acp` | Registry name on `ctx.subagents`. |
|
|
| `command` | required | Executable spawned for each run. |
|
|
| `args` | `[]` | Command arguments. |
|
|
| `cwd` | process cwd | Child process and ACP session working directory. |
|
|
| `permission` | `reject` | Auto-answer permission requests by rejecting or choosing the first allow-shaped option. |
|
|
| `env` | `{}` | Explicit child environment layered over a credential-scrubbed parent environment. |
|
|
| `disposeEofGraceMs` | `6000` | Grace after stdin EOF before SIGTERM. |
|
|
| `disposeGraceMs` | `3000` | Grace after SIGTERM before SIGKILL. |
|
|
|
|
```yaml
|
|
- id: subagent-acp
|
|
name: '@deepseek-ai/dsh-subagent-acp'
|
|
config:
|
|
providerName: acp
|
|
command: node
|
|
args: ['--import', 'tsx', './packages/ui/acp-agent/src/bin.ts', './examples/acp-agent/cordis.yml']
|
|
permission: reject
|
|
env:
|
|
DEEPSEEK_API_KEY: !!js process.env.DEEPSEEK_API_KEY
|
|
```
|
|
|
|
## Stop-reason mapping
|
|
|
|
| ACP | Harness |
|
|
|---|---|
|
|
| `end_turn` | `completed` |
|
|
| `max_tokens` | `max-tokens` |
|
|
| `refusal` | `refusal` |
|
|
| `cancelled` | `aborted` |
|
|
| `max_turn_requests` or unknown | `error` |
|
|
|
|
## Process boundary
|
|
|
|
The child environment is built by [`buildChildEnv`](../subagent-subprocess/README.md): credential-shaped ambient variables are removed, then explicit `config.env` values are applied. The ACP wire is the real serialization boundary; same-process subagent values are not defensively cloned.
|
|
|
|
The package has no default export. Cordis loader unwrapping would otherwise hide the named `inject` metadata; see [postmortem 0001](../../../docs/postmortem/0001-acp-default-export-drops-inject.md).
|
|
|
|
Keyless tests drive a scripted ACP subprocess over real stdio. The with-key e2e drives the repository's real ACP agent and self-skips without `DEEPSEEK_API_KEY`.
|
|
|
|
## Known Limitations and Deferred Work
|
|
|
|
- **A fresh process per run** — persistent-process pooling is a future optimization ([the seam RFC](../../../docs/rfc/implemented/feature/2026-06-21-subagent-capability-seam.md)).
|
|
- **No optional start-time capabilities** — this provider cannot apply the local harness's `outputSchema`, depth cap, tool filter, or persona inside the remote process, so it advertises none and the service rejects requests that require them.
|
|
- **Only `agent_message_chunk` text is collected** — the child's tool-call activity, thought chunks, and plan updates are not surfaced to the parent.
|
|
- **Permission prompts are auto-answered** (`permission: allow | reject`) — no human is surfaced a child's `session/request_permission` in this cut.
|
|
- **No snapshot-tier replay coverage** (`TODO(acp-subagent-replay)`) — an ACP child is its own process with its own replay shape, deferred.
|