Reusable subagents and ACP harnesses
Kit v0.2.2, synced from docs/user/subagents-and-acp-harnesses.md at 17752c1. The same text ships inside the binary; the agent searches it with docs({ query }).
Kit can start parent-owned nested agents through the Agent Client Protocol (ACP). ACP is an open interface between an agent client and an agent runtime. That separation lets Kit orchestrate Kit, Claude Code, Codex, Cursor, and other compatible harnesses through one lifecycle instead of maintaining a custom integration for each one.
A subagent is a reusable Runlet value, not a detached background task: start it with subagent, continue the same session with prompt, branch its completed context with fork, list retained handles with subagents({}), or terminate one with close.
Prompt content and file context
The prompt argument to subagent, prompt, and fork accepts either a string (the existing text-only form) or an ordered array of ACP content blocks:
child = subagent({
prompt: [
{ type: "text", text: "Review this interface against the requirements." },
{ type: "resource_link", uri: "file:///project/src/interface.rs", name: "interface.rs", mimeType: "text/x-rust" },
{ type: "resource", resource: { uri: "file:///project/requirements.txt", mimeType: "text/plain", text: "Keep existing callers compatible." } }
]
})
return child.output
textandresource_linkrequire no optional child capability. A resource link refers to a URI the child can access; Kit does not read the URI or copy the file automatically. A local file must be accessible to the child in its own filesystem.- Embedded
resourceblocks contain aresourceobject withuri, optionalmimeType, and eithertextor base64blob. They require the child’spromptCapabilities.embeddedContextcapability. imageblocks contain base64data,mimeType, and an optionaluri. They require the child’spromptCapabilities.imagecapability. These are ACP blocks, not Kit managed File references; the parent’s session-authorized File references are not transferred to the child.
Kit rejects unsupported content rather than silently converting it to text or dropping it. The array must be nonempty. Block order and metadata are preserved. When output_schema is set, Kit appends the JSON-output instructions as a separate text block after the supplied blocks. Roster task summaries use only text blocks, not embedded files or image data.
Why use another harness from Kit?
Keep Kit as the orchestrator and route only a bounded task to a specialist. The parent can start independent specialists concurrently, give each child explicit context, require structured output, continue a useful session, or fork an alternative. This makes the specialist’s result composable with shell commands, edits, tests, MCP calls, and other agents in the same Runlet program.
An external harness remains a separate program with its own configuration, tools, account, and usage limits. Kit does not turn a Claude subscription into provider credentials for the built-in acp.kit harness. The @agentclientprotocol/claude-agent-acp package includes the Claude Agent SDK CLI, so it does not require a separate Claude Code installation. Authenticate that adapter out of band with either a Claude subscription or Anthropic Console. With --claudeai, work counts against subscription limits instead of metered Console API billing; whether that costs less depends on the workload and plan. This also lets you choose a model for work where it is particularly effective—for example, Claude Opus for visual and graphic design—without moving the whole coding session out of Kit.
Configure a descriptive alias for that route:
[acp.claude]
command = "npx"
args = ["-y", "@agentclientprotocol/claude-agent-acp@0.69.0"]
permissions = "allow"
[subagent.harnesses."acp.claude".models]
designer = "opus"
Then ask for the configured specialist explicitly:
design = subagent({
name: "Visual Designer",
harness: "acp.claude",
model: "designer",
prompt: "Review the existing dashboard and propose a coherent visual direction for the new analytics view. Return design guidance, not code."
})
return design.output
designer is a Kit-side alias for the model ID advertised by that ACP adapter. The external harness must accept the configured ID. Before starting the first Claude subagent, authenticate the adapter outside Kit:
npx -y @agentclientprotocol/claude-agent-acp@0.69.0 --cli auth login --claudeai # Claude subscription
# Use --console instead for Anthropic Console API billing.
When a child reports auth_required, Kit reports the advertised authentication methodId values (and a methodId supplied by the error), so you can identify the login method. Log in to that harness outside Kit, then retry the subagent call. Kit does not execute advertised terminal authentication commands or forward authentication error messages and arbitrary data, which may contain secrets.
Kit does not perform this login. You can start Kit before authenticating the adapter; authentication only needs to finish before the subagent starts.
Start, prompt, and fork a reusable subagent
Use the object form of the hidden tools inside compose:
first = subagent({
name: "Implementer",
cwd: "../parser-worktree",
prompt: "Inspect the parser and identify the smallest risk."
})
second = prompt({
subagent: first,
prompt: "Now propose a minimal fix."
})
branch = fork({
subagent: second,
name: "Alternative Reviewer",
prompt: "Explore an alternative without changing the original session."
})
return { main: second.output, alternative: branch.output }
Each successful turn returns a session value with id, name, output, and generation. subagent creates an ID at generation 1. prompt keeps that ID and name while incrementing its generation. fork creates a different ID and uses its own preferred or fallback name; its generation is one greater than the supplied source value, and it does not advance the source session. Close a session with either close(value) or close({ id: value.id }); the latter is useful when only an ID is available. Closing an unknown ID fails with unknown subagent session. Kit sends ACP session/close when the harness advertises it. Explicit close also sends session/delete when advertised, removing the discarded branch’s persistent history after closing it. Delete failures are reported rather than silently ignored. Process shutdown and internal cleanup do not delete persistent history; this preserves completed child sessions for restart recovery. A standalone process without that capability is terminated when its handle is dropped. If native-fork siblings share a process and the harness cannot close one logical session, close fails rather than claiming success or disrupting the siblings.
Always pass the latest completed value back to prompt or fork. Reusing an older value fails with stale subagent generation N; current generation is M. This prevents two continuations from silently racing on one session. Prompt and fork calls on an individual ACP session are serialized, while separate forked sessions can be prompted concurrently. Steering injects guidance into a working turn without waiting for that turn to finish.
The optional name argument is preferred on subagent and fork; prompt has no naming input and preserves the session name. The optional harness, model, and cwd arguments belong only on subagent. harness overrides the user’s configured harness preference. model selects an exact model value ID advertised by that harness through its ACP session configuration, or a model alias configured for that harness. cwd selects the new subagent’s working directory; relative paths resolve from Kit’s working directory, and missing paths or non-directories fail before startup. Omit an argument to retain its configured default. prompt and fork retain the original session’s harness, model, and working directory. An explicit model fails before the first prompt if the harness does not advertise a selectable model option or rejects the value.
Steer a working subagent
Use steer({ id, prompt }) to inject guidance into an existing working turn,
without cancelling it or starting a new turn. While the originating compose
is backgrounded, call subagents({}) in a separate compose to find the child’s
immutable ID and confirm its status is working. Then use that ID:
return steer({
id: "s-…",
prompt: "Keep the change limited to the parser; do not modify the public API."
})
The prompt accepts the same text or ACP content-block input as prompt.
Steering requires ACP v2 and a child that advertises steer support. Starting,
idle, retired, and fork-reserved sessions are rejected, as are unknown IDs.
Unsupported peers return an error: Kit does not fall back to cancellation or
re-prompting. Use prompt with the latest completed handle for an idle child.
The returned value is the child’s acceptance receipt, not proof that the injection was delivered, applied, or finished. Steering does not wait for idle or change the turn, generation, or reusable handle. The original backgrounded compose remains responsible for returning the completed turn’s output. The child can finish or close between listing and steering, so a working listing does not guarantee acceptance. If acknowledgement times out, delivery is unknown; Kit does not cancel the original turn or retry the injection. Dropping the steering caller also does not revoke an injection already sent to the child.
Inspect display names
The calling model should give each new subagent and fork a concise role-oriented display name based on its task, such as Round 2 Implementer or Reviewer. This uses the model already making the tool call; Kit does not make a separate naming request. The name is display metadata and never changes the prompt sent to the child. Omitting name, or supplying an invalid name, uses the lowest available Agent N label.
Kit trims preferred names and accepts 1–32 bytes of printable ASCII. Names compare case-insensitively among one parent’s direct live children; clashes receive the lowest available numeric suffix, with the base shortened as needed to stay within 32 bytes. The immutable s-… ID remains authoritative; names never select handles.
A name is reserved when creation starts. A failed creation releases it; otherwise the reservation survives starting, working, idle, and reusable failures until close or terminal retirement. Nested Kit processes allocate independently, so separate descendant branches can generate duplicate visible names in the agent roster even though each parent keeps its direct-child names unique.
Use subagents({}) to inspect live direct children. Each row contains id, name, status, generation, and the bounded current task summary. Closed and terminally retired children are omitted.
Read the agent roster
The terminal client’s agent roster lists every live subagent with its status, current activity, and elapsed time. The activity excerpt uses a running tool’s title, otherwise an in-progress plan entry, otherwise the latest session title, otherwise the original prompt. These updates depend on what the harness reports over ACP. When a turn finishes, tool and plan activity clears and the excerpt returns to the session title or prompt. Kit publishes compose intent as an ACP tool-title update, which also supplies the main TUI’s tool summary.
When a harness reports context usage over ACP, the row shows the same percent used/size readout as the header on a separate line below the activity excerpt. Kit’s own ACP server always reports it, so nested Kit subagents show usage without configuration.
When every subagent runs on acp.kit, rows carry no harness mark. Once the roster mixes harnesses, each row gains a one-cell mark next to its name: a blue k for Kit, ✱ for Claude, ◎ for Codex, ◇ for OpenCode, ◈ for Copilot, ▸ for Cursor, π for Pi, ◭ for Antigravity, and · when the launch line names none of them. Kit infers the mark from the configured profile’s command and arguments, matching whole tokens such as claude-agent-acp or codex-acp; the [acp.<name>] label itself is not consulted.
Require structured JSON output with output_schema
Pass a JSON Schema object or boolean to any turn-producing call:
review = subagent({
prompt: "Review the proposed change.",
output_schema: {
type: "object",
properties: {
approved: { type: "boolean" },
reason: { type: "string" }
},
required: ["approved", "reason"],
additionalProperties: false
}
})
return { approved: review.output.approved, reason: review.output.reason }
Kit validates output_schema before dispatch and appends instructions asking the child for one bare JSON value. If the response parses and validates, output is that JSON value. If a completed response is malformed, wrapped in Markdown, or does not match the schema, output falls back to the raw response text. The turn still succeeds and its generation advances, so a later prompt can request a repair. A later prompt or fork may use a different schema. Runlet treats output dynamically; it does not statically derive field types from the schema. An explicit null schema is invalid and reports output_schema must be a JSON Schema object or boolean.
Text and ACP update capture
Without output_schema, output is text. A turn that emits selected non-text agent-message content, tool calls, tool-call updates, or plans also returns a sibling field:
{
id: "...",
output: "Completed review",
generation: 1,
updates: { items: [...], truncated: false }
}
Text-only turns omit updates. Capture is limited to 64 update objects and 64 KiB of serialized update data per turn. Excess or oversized updates are omitted and set updates.truncated to true. When an ACP tool update’s rendered content is only a JSON copy of its structured rawOutput, Kit retains rawOutput and replaces the duplicate content with an empty array. This preserves the update’s explicit replacement of earlier rich content. Kit also captures usage (including reported cumulative session cost), session info, available commands, notices, compaction lifecycle updates, and compaction summary chunks. These remain separate from the final answer text. Usage values describe context occupancy, not billable token deltas; cumulative costs must not be summed across updates. Kit does not add child costs to parent billing totals. Child thoughts, user-message echoes, modes, and configuration are not exposed through this value. Session titles and context usage also feed the live agent roster independently of these capture limits; thoughts are not forwarded as activity text.
After a successful child turn, ACP clients receive the child’s final diff and terminal content on the existing subagent, prompt, or fork tool card. Content updates replace earlier snapshots; content chunks append. V1 file snapshots also map to v2 file changes. Native v2 diffs without complete before/after text remain v2-only, as do agent-owned terminals. Child terminal output is replayed at completion, not streamed live; terminal IDs are scoped to the parent invocation. Kit does not execute captured terminal commands. Missing exit information remains unknown, and incomplete terminal captures carry kit/outputIncomplete metadata. Display is limited to 64 rich content items, with the same metadata marking additional display truncation. Raw child output and captured terminal IDs are not rewritten by this display projection. Failed or cancelled child turns do not publish a completion snapshot.
Choose the built-in acp.kit harness
acp.kit is always available and is the default when [subagent].harness is not configured. By default Kit launches the installed kit executable as kit acp, whose default stdio protocol is ACP v1. A built-in child uses subagent.cwd when provided and otherwise inherits Kit’s working directory; it also inherits the provider, model, MCP configuration and credential storage, cancellation, and nesting depth. An explicit subagent.model selection overrides the inherited model for that ACP session. It does not start an A2A listener.
You can override only the executable and base arguments while preserving built-in Kit behavior:
[acp.kit]
command = "kit"
args = ["acp"]
Kit appends its required runtime, resolved reasoning-effort, persistent-session, resume, MCP, credential, and inherited-depth arguments after those base arguments. Use installed-binary commands such as kit --help and kit acp --help to inspect the current command interface rather than relying on an exhaustive flag list here.
Kit’s ACP server also advertises a separate reasoning-effort session selector with Default, Low, Medium, and High values. Changes take effect on the next turn without changing the selected model. Default leaves the effort unset, preserving provider defaults and OPENROUTER_REASONING_EFFORT when OpenRouter supplies one. Set the startup default with top-level reasoning_effort or --reasoning-effort; the bundled TUI exposes the same selector through /effort. Built-in acp.kit children inherit the startup value resolved from CLI and TOML, while generic ACP harnesses keep their own behavior.
Persistent parent sessions record their direct children’s ACP session IDs, harness references, working directories, names, and completed handle generations in the parent transcript. Loading the same parent restores completed handles without immediately starting child processes. Child lifecycle checkpoints must reach disk before a mutable prompt or explicit deletion proceeds; unavailable storage can therefore block these operations. prompt and native fork reconnect on demand using the current harness configuration. Closing the parent still releases its child processes; it does not delete their history. Nonpersistent parents retain process-lifetime handles only.
Recovery reattaches the same mutable child session, not a snapshot or a new branch. It is not the generic immutable fork fallback discussed in #12. Interrupted turns are not automatically retried, and explicitly closed children are never restored—even if remote history deletion failed. Existing transcripts without child records remain readable but cannot reconstruct their old child handles. New child-state records require a reader that understands the newer transcript schema.
For v2 children, Kit waits for an idle state_update after prompt acceptance before returning output. Its stop reason uses the same success, cancellation, refusal, and request-limit handling as v1. Steering requires ACP v2 with advertised steer support. An omitted reason permits normal completion; an unknown reason reports an error rather than success. Whole-message updates replace text by message ID; omitted content preserves text, empty or null content clears it, and later chunks append.
For generic v2 children, recovery uses session/resume with replayFrom: {"type": "start"}; v1 children must advertise loadSession and are reattached with session/load. Built-in Kit children use their persistent-session launch path. Replay completes before the next prompt and never replaces the handle’s last-turn output. Kit uses only IDs recorded by the owning parent; it does not scan and adopt unrelated child sessions. If the harness was removed, cannot load sessions, or lost its history, reconnect fails without creating a replacement session. Restore the harness configuration or explicitly close the obsolete handle. Close during an in-progress reconnect reports an error without retiring the handle; retry after startup completes or is cancelled.
Configure a generic ACP harness
Kit offers ACP v2 with its client name and version during initialization, uses the version selected by the child, and falls back to ACP v1 when selected. Unsupported versions fail before a session is opened. Generic external child harnesses must speak newline-delimited JSON-RPC over stdio and support initialize, session/new, and session/prompt. session/fork is optional. session/close is optional in v1 and baseline for v2 sessions. Kit does not advertise filesystem or terminal services to children. Keep stdout protocol-only; the agent may log to stderr. Kit runs the executable directly from the subagent’s selected working directory, which defaults to Kit’s working directory, and inherits the parent environment. It does not invoke a shell, so pipes, environment assignments, compound commands, and shell quoting in command or args do not work.
Configure trusted argv profiles in ~/.kit/config.toml:
[acp.review]
command = "review-agent"
args = ["acp"]
permissions = "allow"
[subagent]
harness = "acp.review"
References use the fully qualified acp.<name> form. Profile names must be non-empty and contain neither whitespace nor dots. command must be non-empty; use an executable on PATH or an absolute path. Kit does not perform a generic agent’s login or ACP authentication flow, so authenticate and configure that agent before starting Kit. Generic session persistence beyond the parent process is agent-defined. Per-subagent model selection works for generic agents such as Codex, Claude, or Cursor only when their ACP adapter advertises and implements the selectable model session option.
An unknown selection fails with unknown ACP harness "acp.name" or unknown subagent ACP harness "acp.name". Invalid references may report ACP harness references must use acp.<name>.
Configure child session options
Set trusted initial options per harness in the subagent profile:
[subagent.harnesses."acp.review".config_options]
thought_level = "high"
mode = "code"
auto_compact = true
Values must be strings for ACP select options or booleans for ACP boolean options.
Use the child’s advertised option ID. If no ID matches, Kit accepts an unambiguous
advertised category such as thought_level (which may map to reasoning_effort).
Missing, ambiguous, or incompatible options fail startup; a child rejection also
fails startup rather than silently using its default. Option names and accepted
values depend on the harness. Model selection continues to use the dedicated
model argument and alias/allowlist policy, not config_options.
Kit applies an explicit model first, then profile options in key order, using the updated option list returned after each selection. Native forks inherit the child’s current configuration; they do not reset it to profile defaults. A model explicitly selected for the source is reapplied to its forks. Fallback forks start a new child and apply the profile again.
Returned updates.items includes the latest complete config_option_update
snapshot advertised by the child, including changes received between prompts.
Snapshots replace earlier state rather than merging option lists. They share the
existing bounded update budget: Kit prioritizes the latest snapshot over earlier
activity updates and sets updates.truncated when content cannot fit. A single
oversized snapshot is omitted. This is child-reported state, not an assertion
that the child accepted an unrequested or unsupported setting.
Configure model aliases and allowed overrides per harness
Model namespaces differ between ACP harnesses. Configure aliases and explicit-override policy under the fully qualified harness reference:
[subagent]
harness = "acp.review"
[subagent.harnesses."acp.review"]
allow_model_overrides = ["provider:model-a", "provider:model-b"]
[subagent.harnesses."acp.review".models]
review = "provider:model-a"
fast = "provider:model-b"
A call may use either a configured alias or an exact model ID. Aliases are scoped to one harness, resolve before ACP startup, and are then checked against that harness’s allow_model_overrides. Kit rejects configuration when an alias resolves outside its harness’s allowlist.
Omitting allow_model_overrides allows all explicit model selections accepted by the harness. An empty list disables all explicit model overrides for that harness. The allowlist applies only when subagent.model is present; it does not inspect or restrict the harness’s inherited or default model. Configured aliases indicate available routing choices, not recommendations. Agents should omit harness and model unless the user or active workflow explicitly requests an exact override or configured alias.
Fork capability and transcript fallback
Kit reads fork capability from the child’s ACP initialize response at runtime. The built-in Kit ACP server advertises native session/fork, so current Kit children fork in-process. If a child does not advertise it:
acp.kitclones a sanitized completed transcript and starts an isolated Kit process for the branch. This compatibility fallback applies to older Kit executables and to[acp.kit]overrides whose executable/base arguments do not expose native fork.- A generic profile fails with
ACP harness "acp.name" does not advertise session/fork; transcript fallback is only available for Kit. Its existing session still supports ordinarypromptcalls.
Do not infer support from the agent name or an old compatibility table; ACP capabilities can change between agent releases.
Headless permission policy
Nested agents run unattended and cannot ask the user interactively. Kit selects allow_always when offered, otherwise allow_once. If the child offers no allow option, Kit cancels the request; it never selects a rejection option.
permissions = "allow" is the default. The legacy values "deny" and "cancel" remain accepted for existing configuration files, but now also allow requests. Remove these old settings to avoid implying a restriction that Kit no longer enforces.
Only configure executables and fixed arguments you trust. Child edits and shell commands can run without approval; restrict access through the child’s configuration and process privileges, as when running that executable directly.
Child elicitation
Nested agents cannot collect interactive input. Kit answers child ACP elicitation/create requests with cancel, for both form and URL modes, rather than a method-not-found error. Kit does not supply form values, open URLs, or forward these requests to the parent model. This is independent of the child’s permission policy; the child decides how to continue after cancellation.
Cancellation, stop reasons, and retired sessions
Cancelling an outer turn propagates to nested work. For a dispatched prompt, Kit sends ACP session/cancel and allows up to five seconds for the child to settle; a child that does not settle is terminated. Cancellation while starting, waiting for the session lock, prompting, or forking returns a cancelled tool result. A cancelled fork or a fork that exceeds its 30-second deadline sends $/cancel_request for the in-flight request. Kit still waits for a late fork response to clean up any created session before releasing source-session serialization. A session/close request that exceeds its five-second deadline also sends $/cancel_request before its response is discarded. Protocol cancellation is advisory; it does not guarantee that the child stopped or rolled back the operation.
end_turn and max_tokens are successful completed turns. In particular, a max-token response returns its partial output and remains reusable. cancelled, refusal (nested agent refused the prompt), max_turn_requests (nested agent reached its turn-request limit), protocol errors, and unknown stop reasons are failures. Once a prompt continuation has been dispatched and fails, Kit retires that session because its transcript may have changed; retry by starting a new subagent rather than reusing the old value. Reuse can report unknown subagent session, subagent session is retired, or nested agent process is no longer running.
Limits and troubleshooting
Kit currently permits nesting to depth 2 and at most 120 live parent-owned subagent sessions per main session. At the maximum depth, Kit omits subagent and fork from the compose catalog because neither operation can succeed there; the runtime depth check remains as a fallback. Exceeding the depth or capacity bounds reports subagent depth limit (2) reached or live subagent session limit (120) reached. Use subagents({}) to inspect retained sessions and close to release sessions that are no longer needed. Closed, failed, or explicitly closed children no longer consume capacity.
ACP startup must complete within 30 seconds. Native session/fork must also answer within 30 seconds. Common diagnostics include:
ACP harness spawn failure: verifycommand,args, executable installation, andPATH.ACP harness handshake timeoutorACP harness protocol handshake failure: verify ACP v1 or v2 support, required methods, and that stdout contains only protocol messages. Check stderr for lines prefixedACP harness <name>:.ACP harness did not answer session/fork within 30 seconds: update or repair the agent, or avoidfork; onlyacp.kithas the transcript fallback.nested agent exited during startupornested agent process exited without a response: run the configured installed executable directly enough to verify installation and login, then inspect its stderr.- Structured output remains text: ask for bare JSON, inspect the raw
output, and use a repairpromptwith an appropriateoutput_schema.
For current top-level and subcommand options, run kit --help or kit <command> --help.
Child sessions inherit the owning ACP session’s additional project directories on
session/new and native session/fork, including the Kit fork fallback. These
directories are project context, not filesystem access boundaries. An explicit
subagent.cwd changes the primary working directory without discarding the
additional directories. Each parent session has its own directory list.
If the parent has additional directories, a child harness must advertise ACP
additional-directory support; otherwise Kit rejects startup with an explicit
error instead of silently dropping project context.