# Delegation Implementation Plan
Status: approved; in progress
## Objective
Make delegated agents (`agent_*`) and other SCV instances usable for sustained
work: multi-turn conversations, research, coding, and landing a change through
CI and release. SCV must always know what it started and clean it up.
## Decisions
- Delegated agents are trusted and run as the user. Cleanup is cooperative: it
catches accidental leaks, not a child that deliberately escapes. OS
sandboxing is out of scope.
- Delegation stays a tool. The core loop gains only progress reporting and
in-tool approvals.
- The model sees SCV-issued handles such as `codex-2`, never vendor session
IDs. Handles belong to the parent session.
- Each adapter declares its capabilities in the adapter table: output format,
resume support, live transport, permission flags, and conversation file
locations.
- Two transports:
- **resume**: one process per turn, continued through the agent's own
session files (`claude --resume`, `codex exec resume`);
- **live**: one long-running stdio child per conversation, speaking JSON-RPC
2.0 (ACP) or the SCV protocol.
## Steps
Each step is one PR landed through the feature-flow skill and released with the
next patch version at landing time. Steps 2 and 4 do not depend on the adapter
table and may be developed in parallel with others; landings stay sequential.
| 0a | Replay tool calls to the Responses API (done, 0.1.21) | — |
| 0b | Adapter table, grok/dsh/pi, full-work defaults, `permissions` (done, 0.1.22; zcode deferred) | 0a |
| 1 | Delegation foundation: structured results, registry, cleanup (done, 0.1.26) | 0b |
| 2 | ClawBot long-turn resilience (done, 0.1.23) | 0b landed |
| 3 | Multi-turn conversations (resume) (done, 0.1.28) | 1 |
| 4 | SCV web tools: `web_fetch`, `web_search` (done, 0.1.25) | 0b landed |
| 5 | Progress events and protocol v3 (done, 0.1.30) | 3 |
| 6a | Live mode: SCV to SCV (`agent_scv`) (done, 0.1.31) | 5 |
| 6b | Live mode: ACP adapter (done, 0.1.33) | 6a |
| 7 | Background delegations (done, 0.1.35) | 6, iLink check |
### 0b. Adapter table and full-work defaults
One data-driven adapter descriptor per agent. New defaults sized for feature
work through release: `tools.agent_timeout_seconds` 3600,
`tools.max_timeout_seconds` 14400, `tools.command_timeout_seconds` 600,
`agent.max_steps` 128. Per-agent `permissions = "default" | "full"`; `full`
adds each CLI's own full-autonomy and web-search flags and is shown in the
approval summary. The built-in default stays `default`.
### 1. Delegation foundation
- Adapters declare an output format; parsers produce one result:
`{agent, status: completed|failed|timeout|cancelled, reply, usage,
exit_code, stderr_tail, truncated}`. Claude uses
`--output-format stream-json --verbose --session-id <uuid>`; Codex uses
`exec --json` with `-o <file>` as a fallback; pi uses `--mode json`; Grok
and DeepSeek Harness stay plain text. Full logs never enter the parent
history.
- A delegation registry per SCV process records handle, agent, parent session,
`cwd`, pid, pgid, `/proc` start time, owning process, and state. As built,
the agent tools receive the registry and session ID through
`ToolsConfig.delegation` when the session's tools are built, rather than
through `ToolContext`, so the core crate stays unaware of delegation.
- Every child gets `SCV_PARENT=<instance>/<session>/<handle>` and
`SCV_DELEGATION_DEPTH`. Delegation is refused at
`agent.max_delegation_depth` (default 2; `[agents]` holds only per-adapter
tables). `scv run/start/stop/restart/update/channels` are refused at
depth > 0.
- `$SCV_HOME/state/delegations/<handle>.json` (0600, atomic) is written at spawn
and removed at reap. The daemon reconciles at startup and every 60 seconds,
killing groups whose owning process died.
- The daemon becomes a child subreaper on Linux. After a delegation exits,
processes still tagged with its handle get TERM, then KILL after 2 seconds.
- `scv agents ps [--all]`, `scv agents kill <handle>|--orphans`, and active
and reaped counts in `scv status`.
### 2. ClawBot long-turn resilience
- Keep polling while a sender's turn runs, so a long owner turn neither blocks
other senders nor the owner's later messages, which queue in the owner's
session.
- A final reply that iLink rejects as expired is kept and delivered with the
sender's next message instead of being dropped.
### 3. Multi-turn conversations
- `agent_* {prompt, cwd?, session?, timeout_seconds?, model?, effort?}`.
Omitting `session` starts a conversation and returns its handle; passing it
continues the conversation and returns `turn`.
- A conversation keeps its agent and `cwd`. One turn at a time; a busy
conversation returns `session busy`. A timed-out turn stays resumable.
- `agent.max_conversations` (default 8 per session) and
`agent.conversation_idle_seconds` (default 86400), under `[agent]` like
`agent.max_delegation_depth`. Handles end with the parent session.
- Claude Code, Codex, and pi resume; Grok and DeepSeek Harness start fresh on
every call until their resume options can be verified headless.
- `scv agents gc --older-than 30d` removes conversation files in the private
agent homes, skipping live ones (marked in `$SCV_HOME/state/conversations`)
and anything written in the last hour.
### 4. SCV web tools
- `web_fetch`: bounded HTTP(S) GET with HTML-to-text conversion, redirect and
size limits, and loopback, link-local, and private addresses refused by
default. HTTPS hosts in `web.auto_approve_domains` run without approval;
other URLs have `network` risk and need approval.
- `web_search`: the provider's hosted Responses `web_search` tool
(`web.search = "provider"`), or a `web_search` tool backed by SearXNG or
Brave Search. Without a configured source there is no search.
- Tool-free sessions never get them.
### 5. Progress events and protocol v3
- `CoreEvent::ToolProgress { call_id, text }` and a `progress` sink in
`ToolContext`.
- Protocol v3 adds `tool.progress` (at most two per second per call, 512 bytes
each) and an optional `session.start.delegation_depth`.
- Adapter parsers report commands run, files changed, and tool use. The TUI
shows the latest line under the running tool; ClawBot ignores progress.
- As built: the core loop paces events (one per 500 ms per call, dropping lines
reported within 500 ms of completion) so every server gets the same limit,
and the server bounds the text again. Parsers redact credential-like values.
A session's declared `delegation_depth` raises the depth its runs count from.
### 6. Live mode
6a (done, 0.1.31):
- A live child per conversation, registered like any delegation. Closing stdin
is followed by a 2-second grace period and a group kill.
`scv_tools::live::LiveChild` is protocol-neutral, and the conversation
store keeps it as the conversation's attachment, so 6b reuses both.
- `ToolContext.approvals` gives tools the session's approval gate.
- `agent_scv` runs `scv server --stdio` with its own home
(`~/.scv/agents/scv`), configured by `scv agents import scv`. The child's
`approval.requested` goes through the parent's approval gate; cancellation
and timeouts become `turn.cancel`.
- Future work: `[agents.scv] socket`, attaching to an existing daemon instead
of starting a child.
6b (done, 0.1.33):
- An ACP adapter maps `session/new`, `session/prompt`, `session/update`,
`session/request_permission`, and `session/cancel`, and offers no client
file or terminal capabilities. Adapters may prefer ACP and fall back to
resume.
- ACP v1 servers: Claude Code and Codex through the ACP organisation's
official adapters (`claude-agent-acp`, `codex-acp`), Grok Build and
DeepSeek Harness natively (`grok agent stdio`, `dsh --profile acp`). pi has
only a community adapter and stays on resume; `agent_scv` keeps the SCV
protocol.
- `[agents.<name>] transport = "auto" | "acp" | "resume"`; `auto` prefers an
installed ACP server unless `command` is customised. `permissions = "full"`
maps onto each agent's own session mode or flag.
### 7. Background delegations
- Checked with the owner first (2026-09-23): iLink delivers a `sendmessage`
without a `context_token` (an unprompted message) and a first reply sent two
minutes late, but silently drops a second send on one `context_token` while
answering it exactly like a success. See [channels](channels.md#wechat-ilink-contract).
- `agent_* {background: true}` returns `{"job","status":"running"}` at once;
the job runs the same call in its conversation, detached from the turn, and
its relayed approvals get the session's unattended answer (see
[tools](tools.md#background-jobs)). `agent_wait {job, timeout_seconds?}` and
`agent_status {job?}` observe jobs and `agent_cancel {job}` stops one (the
handle is a job, not a conversation: a job may continue a conversation, and
some agents have none).
- `agent.max_background` (not `agents.*`, which holds per-agent tables)
defaults to 4 per session; 0 turns it off. Closing the session cancels its
jobs.
- A finished job the model has not seen is reported in a turn the server
starts once the session is idle, marked by `origin` on `turn.started` and
the terminal event (optional, so protocol v3 stays).
- ClawBot sends the report to the owner as an unprompted message, keeps a
session with running jobs open and past its idle limit, cancels (rather
than replaces) a timed-out owner turn while jobs run, and never sends twice
on one context token: long replies continue unprompted. `scv exec` stays
open until its jobs are reported.
## Testing
CI uses fake CLIs, fake ACP agents, and fake providers. After each deploy the
implementing agent runs live checks against the real Claude, Codex, and SCV
and reports them.
## Risks
- Vendor JSON formats change. Parsers tolerate unknown fields, fall back to
text, and keep recorded-sample tests.
- Protocol v3 makes TUI processes from older versions reconnect with
`version_mismatch`; they must be restarted.
- ACP adapter packages may be unavailable; step 6 then falls back to resume.
- Conversation files grow in the private agent homes until `gc` runs.