detached 7b4c47b

fix(subagents): preserve pending resume tracking

commit
7b4c47be1665df6463bd3aafcdb84988bf947f6b
author
brice · · 2026-08-09 08:52 UTC
merge of
bd96a88 + 1adbe2f · diff against the first parent
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
11 files · +224 -58
M README.md +3 -3
@@ -401,11 +401,11 @@ Background subagents that run alongside your main session. Each one is a separat
401401
402402 **`/session export` does NOT bundle subagents.** A `.zutsession` is just the main chat transcript; per-agent state (session file, unix-socket inbox) is machine-local and doesn't round-trip through a JSONL file. To share what an agent said, copy it out of the transcript view manually.
403403
404-**Auto-subagents.** With `/settings` -> auto-subagents on, the interactive primary agent enters strict orchestrator mode and gets whichever of the canonical `subagent_spawn` and `subagent_status` tools are permitted by launch-time policy. It must delegate all implementation, debugging/testing, and code-review work to an appropriately named profile, or to a clearly described general worker when no profile fits. It must not write or edit code, make direct implementation tool calls, inspect or review code, or apply worker patches itself. It may decompose requests, select and spawn workers, check status, coordinate follow-ups, and synthesize results. Give workers self-contained tasks: shared-worktree workers edit the same working directory and need coordination, while `isolation:"worktree"` is for parallel coding in separate trees; a worktree patch must be integrated by a worker rather than applied by the primary agent. Child workers cannot recursively spawn more sub-agents in v1. The `subagent_status` tool accepts an optional `agent_id`: omit it to list visible workers, or pass a full id / unique prefix to query one without waiting for completion. It returns privacy-safe JSON metadata with the worker id, lifecycle state (`starting`, `running`, `completed`, `failed`, `cancelled`, or `detached`), underlying process/turn state, timestamps, a bounded first-line task summary, and terminal-result metadata when available; it does not return prompts, transcripts, result output, credentials, provider settings, or filesystem paths. The spawn tool returns the child id and initial state immediately; completion is a durable `turn.result` with a `subagent://<id>/result` reference. It accepts named profiles, timeout, `max_turns`, and shared/worktree isolation. Omit `max_turns` to use the supervisor policy; the default ceiling is 3 and `subagents.max_turns` changes it. Global concurrency limits apply equally to slash commands, tool calls, and batches. Completion is host-event-driven: wait for the host-injected `[auto-subagents update]`, the only completion signal. Never use `bash sleep`, `watch`, `tail -f`, polling loops, repeated `subagent_status`, or dashboard/metadata/event-log/file checks solely to wait; work on unrelated independent tasks or end/yield your turn. Legitimate waits inside user-requested commands, provider flows, extensions, or tests remain allowed. Off by default; toggle from `/settings`. When off, the permitted canonical subagent tools remain available for user-requested delegation or when an active skill workflow requires it; the primary otherwise performs work itself without the strict orchestrator contract or profile manifest.
404+**Auto-subagents.** With `/settings` -> auto-subagents on, the interactive primary agent enters strict orchestrator mode and gets whichever of the canonical `subagent_spawn` and `subagent_status` tools are permitted by launch-time policy. It must delegate all implementation, debugging/testing, and code-review work to an appropriately named profile, or to a clearly described general worker when no profile fits. It must not write or edit code, make direct implementation tool calls, inspect or review code, or apply worker patches itself. It may decompose requests, select and spawn workers, check status, coordinate follow-ups, and synthesize results. Give workers self-contained tasks: shared-worktree workers edit the same working directory and need coordination, while `isolation:"worktree"` is for parallel coding in separate trees; a worktree patch must be integrated by a worker rather than applied by the primary agent. Child workers cannot recursively spawn more sub-agents in v1. The `subagent_status` tool accepts an optional `agent_id`: omit it to list visible workers, or pass a full id / unique prefix to query one without waiting for completion. It returns privacy-safe JSON metadata with the worker id, lifecycle state (`starting`, `running`, `completed`, `failed`, `cancelled`, or `detached`), underlying process/turn state, timestamps, a bounded first-line task summary, and terminal-result metadata when available; it does not return prompts, transcripts, result output, credentials, provider settings, or filesystem paths. The spawn tool returns the child id and initial state immediately; completion is a durable `turn.result` with a `subagent://<id>/result` reference. It accepts named profiles, timeout, `max_turns`, and shared/worktree isolation. Omit `max_turns` to use the supervisor policy; the default ceiling is 3 and `subagents.max_turns` changes it. Global concurrency limits apply equally to slash commands, tool calls, and batches. Completion is host-event-driven: wait for the host-injected `[auto-subagents update]`, the only completion signal. Never use `bash sleep`, `watch`, `tail -f`, polling loops, repeated `subagent_status`, or dashboard/metadata/event-log/file checks solely to wait; work on unrelated independent tasks or end/yield your turn. Legitimate waits inside user-requested commands, provider flows, extensions, or tests remain allowed. Off by default; toggle from `/settings`. When off, the permitted canonical subagent tools and profile manifest remain available for user-requested delegation or when an active skill workflow requires it; the primary otherwise performs work itself without the strict orchestrator contract.
405405
406406 ### Named subagent profiles
407407
408-zut discovers the common markdown/frontmatter profile layout from `~/.agents/agents/*.md` by default. With auto-subagents enabled and `subagent_spawn` available, the orchestrator receives `[subagents_list]` metadata and should select the appropriately named profile for each worker when one matches; otherwise it can spawn a clearly described general worker without an `agent` field. If launch-time policy withholds `subagent_spawn`, strict orchestrator guidance remains active but profile metadata is omitted; the primary reports that delegation is unavailable instead of implementing directly. Profile bodies are applied only to the selected child. See [docs/subagents.md](docs/subagents.md) for discovery, supported fields, reasoning levels, and examples.
408+zut discovers the common markdown/frontmatter profile layout from `~/.agents/agents/*.md` by default. Whenever `subagent_spawn` is available, the primary agent receives `[subagents_list]` metadata and should select the appropriately named profile for each worker when one matches; otherwise it can spawn a clearly described general worker without an `agent` field. If launch-time policy withholds `subagent_spawn`, strict orchestrator guidance remains active but profile metadata is omitted; the primary reports that delegation is unavailable instead of implementing directly. Profile bodies are applied only to the selected child. See [docs/subagents.md](docs/subagents.md) for discovery, supported fields, reasoning levels, and examples.
409409
410410 ### `/settings`
411411
@@ -415,7 +415,7 @@ Opens a dialog with every persistent setting. `up`/`down` to navigate, `enter` o
415415 - **render images when supported** — draw screenshots / `read`-returned images inline using the terminal's image protocol, or fall back to a text placeholder. Auto-detected from `TERM_PROGRAM`; the toggle overrides the detection. The row is greyed out and forced off on terminals that don't speak any image protocol.
416416 - **terminal alerts** — emit a terminal bell when the main session stops with work that may need attention, or when an extension raises a structured alert. Enabled by default; changes apply immediately and persist as `terminal_alerts_enabled`. Terminal emulators may render the bell audibly, visually, or not at all.
417417 - **AI terminal titles** — after the first real prompt of a fresh interactive session, make one small hidden request to the active model and set the terminal title to `zut: <title>`. The title is limited to 40 Unicode characters, persisted with the session, restored on resume, and never added to the conversation. Enabled by default; disable it to avoid the extra model request. The toggle applies immediately, but title generation still waits for the first real prompt and never starts from startup context, resumed history, or slash commands. Persists as `terminal_title_enabled`.
418-- **auto-subagents** — enable strict primary-agent orchestrator mode: delegate implementation, debugging/testing, and code-review work to named or general workers; the primary agent coordinates but does not make direct implementation changes. It can spawn background sub-agents via `subagent_spawn` and query live state via `subagent_status` when those tools are permitted. If delegation is unavailable because of launch-time restrictions, the contract remains active, profile metadata is withheld, and the primary reports the limitation rather than implementing directly. Off by default. When off, the permitted canonical subagent tools remain available for user-requested delegation or when an active skill workflow requires it; the primary otherwise works directly without the strict orchestrator contract or profile manifest. The tools accept named profiles, reasoning, timeout, turn limits, and shared/worktree isolation where applicable; lifecycle and result references are persisted. Completion is host-event-driven: `[auto-subagents update]` completion updates are the only signal; do not poll or inspect dashboards, metadata, event logs, or files solely to wait. See `/subagents` and [docs/subagents.md](docs/subagents.md) for details.
418+- **auto-subagents** — enable strict primary-agent orchestrator mode: delegate implementation, debugging/testing, and code-review work to named or general workers; the primary agent coordinates but does not make direct implementation changes. It can spawn background sub-agents via `subagent_spawn` and query live state via `subagent_status` when those tools are permitted. If delegation is unavailable because of launch-time restrictions, the contract remains active, profile metadata is withheld, and the primary reports the limitation rather than implementing directly. Off by default. When off, the permitted canonical subagent tools and profile manifest remain available for user-requested delegation or when an active skill workflow requires it; the primary otherwise works directly without the strict orchestrator contract. The tools accept named profiles, reasoning, per-spawn fast-mode overrides, timeout, turn limits, and shared/worktree isolation where applicable; lifecycle and result references are persisted. Completion is host-event-driven: `[auto-subagents update]` completion updates are the only signal; do not poll or inspect dashboards, metadata, event logs, or files solely to wait. See `/subagents` and [docs/subagents.md](docs/subagents.md) for details.
419419 - **Ponytail coding mode** — include compact engineering guidance in each resolved system prompt; the guidance tells the model to apply it to coding, debugging, and review work rather than ordinary conversation. It favors understanding the real flow, small validated changes, reuse, and preserving safety checks. Enabled by default; changes apply to the next model call and persist as `ponytail_enabled` in `$ZUT_HOME/config.json`. It is included by interactive, print, stream, JSON, RPC, subagent, bot, SDK, and Zutfile agents.
420420 - **fast mode** — request the provider's fast tier where supported. It currently uses OpenAI's Fast service tier for OpenAI, OpenAI Responses, and OpenAI Codex models. Off by default; toggle it with `/fast` or `/settings`; changes apply on the next model call and persist as `fast_mode`. Other providers currently return an unsupported-provider error. Fast mode may cost more and depends on the selected model/account.
421421 - **lsp in main session** — enable the built-in `lsp` tool and code diagnostics for the main agent. Enabled by default; changes persist as `lsp_enabled`.
@@ -401,11 +401,11 @@ Background subagents that run alongside your main session. Each one is a separat
401 401
402 **`/session export` does NOT bundle subagents.** A `.zutsession` is just the main chat transcript; per-agent state (session file, unix-socket inbox) is machine-local and doesn't round-trip through a JSONL file. To share what an agent said, copy it out of the transcript view manually.402 **`/session export` does NOT bundle subagents.** A `.zutsession` is just the main chat transcript; per-agent state (session file, unix-socket inbox) is machine-local and doesn't round-trip through a JSONL file. To share what an agent said, copy it out of the transcript view manually.
403 403
404-**Auto-subagents.** With `/settings` -> auto-subagents on, the interactive primary agent enters strict orchestrator mode and gets whichever of the canonical `subagent_spawn` and `subagent_status` tools are permitted by launch-time policy. It must delegate all implementation, debugging/testing, and code-review work to an appropriately named profile, or to a clearly described general worker when no profile fits. It must not write or edit code, make direct implementation tool calls, inspect or review code, or apply worker patches itself. It may decompose requests, select and spawn workers, check status, coordinate follow-ups, and synthesize results. Give workers self-contained tasks: shared-worktree workers edit the same working directory and need coordination, while `isolation:"worktree"` is for parallel coding in separate trees; a worktree patch must be integrated by a worker rather than applied by the primary agent. Child workers cannot recursively spawn more sub-agents in v1. The `subagent_status` tool accepts an optional `agent_id`: omit it to list visible workers, or pass a full id / unique prefix to query one without waiting for completion. It returns privacy-safe JSON metadata with the worker id, lifecycle state (`starting`, `running`, `completed`, `failed`, `cancelled`, or `detached`), underlying process/turn state, timestamps, a bounded first-line task summary, and terminal-result metadata when available; it does not return prompts, transcripts, result output, credentials, provider settings, or filesystem paths. The spawn tool returns the child id and initial state immediately; completion is a durable `turn.result` with a `subagent://<id>/result` reference. It accepts named profiles, timeout, `max_turns`, and shared/worktree isolation. Omit `max_turns` to use the supervisor policy; the default ceiling is 3 and `subagents.max_turns` changes it. Global concurrency limits apply equally to slash commands, tool calls, and batches. Completion is host-event-driven: wait for the host-injected `[auto-subagents update]`, the only completion signal. Never use `bash sleep`, `watch`, `tail -f`, polling loops, repeated `subagent_status`, or dashboard/metadata/event-log/file checks solely to wait; work on unrelated independent tasks or end/yield your turn. Legitimate waits inside user-requested commands, provider flows, extensions, or tests remain allowed. Off by default; toggle from `/settings`. When off, the permitted canonical subagent tools remain available for user-requested delegation or when an active skill workflow requires it; the primary otherwise performs work itself without the strict orchestrator contract or profile manifest.404+**Auto-subagents.** With `/settings` -> auto-subagents on, the interactive primary agent enters strict orchestrator mode and gets whichever of the canonical `subagent_spawn` and `subagent_status` tools are permitted by launch-time policy. It must delegate all implementation, debugging/testing, and code-review work to an appropriately named profile, or to a clearly described general worker when no profile fits. It must not write or edit code, make direct implementation tool calls, inspect or review code, or apply worker patches itself. It may decompose requests, select and spawn workers, check status, coordinate follow-ups, and synthesize results. Give workers self-contained tasks: shared-worktree workers edit the same working directory and need coordination, while `isolation:"worktree"` is for parallel coding in separate trees; a worktree patch must be integrated by a worker rather than applied by the primary agent. Child workers cannot recursively spawn more sub-agents in v1. The `subagent_status` tool accepts an optional `agent_id`: omit it to list visible workers, or pass a full id / unique prefix to query one without waiting for completion. It returns privacy-safe JSON metadata with the worker id, lifecycle state (`starting`, `running`, `completed`, `failed`, `cancelled`, or `detached`), underlying process/turn state, timestamps, a bounded first-line task summary, and terminal-result metadata when available; it does not return prompts, transcripts, result output, credentials, provider settings, or filesystem paths. The spawn tool returns the child id and initial state immediately; completion is a durable `turn.result` with a `subagent://<id>/result` reference. It accepts named profiles, timeout, `max_turns`, and shared/worktree isolation. Omit `max_turns` to use the supervisor policy; the default ceiling is 3 and `subagents.max_turns` changes it. Global concurrency limits apply equally to slash commands, tool calls, and batches. Completion is host-event-driven: wait for the host-injected `[auto-subagents update]`, the only completion signal. Never use `bash sleep`, `watch`, `tail -f`, polling loops, repeated `subagent_status`, or dashboard/metadata/event-log/file checks solely to wait; work on unrelated independent tasks or end/yield your turn. Legitimate waits inside user-requested commands, provider flows, extensions, or tests remain allowed. Off by default; toggle from `/settings`. When off, the permitted canonical subagent tools and profile manifest remain available for user-requested delegation or when an active skill workflow requires it; the primary otherwise performs work itself without the strict orchestrator contract.
405 405
406 ### Named subagent profiles406 ### Named subagent profiles
407 407
408-zut discovers the common markdown/frontmatter profile layout from `~/.agents/agents/*.md` by default. With auto-subagents enabled and `subagent_spawn` available, the orchestrator receives `[subagents_list]` metadata and should select the appropriately named profile for each worker when one matches; otherwise it can spawn a clearly described general worker without an `agent` field. If launch-time policy withholds `subagent_spawn`, strict orchestrator guidance remains active but profile metadata is omitted; the primary reports that delegation is unavailable instead of implementing directly. Profile bodies are applied only to the selected child. See [docs/subagents.md](docs/subagents.md) for discovery, supported fields, reasoning levels, and examples.408+zut discovers the common markdown/frontmatter profile layout from `~/.agents/agents/*.md` by default. Whenever `subagent_spawn` is available, the primary agent receives `[subagents_list]` metadata and should select the appropriately named profile for each worker when one matches; otherwise it can spawn a clearly described general worker without an `agent` field. If launch-time policy withholds `subagent_spawn`, strict orchestrator guidance remains active but profile metadata is omitted; the primary reports that delegation is unavailable instead of implementing directly. Profile bodies are applied only to the selected child. See [docs/subagents.md](docs/subagents.md) for discovery, supported fields, reasoning levels, and examples.
409 409
410 ### `/settings`410 ### `/settings`
411 411
@@ -415,7 +415,7 @@ Opens a dialog with every persistent setting. `up`/`down` to navigate, `enter` o
415 - **render images when supported** — draw screenshots / `read`-returned images inline using the terminal's image protocol, or fall back to a text placeholder. Auto-detected from `TERM_PROGRAM`; the toggle overrides the detection. The row is greyed out and forced off on terminals that don't speak any image protocol.415 - **render images when supported** — draw screenshots / `read`-returned images inline using the terminal's image protocol, or fall back to a text placeholder. Auto-detected from `TERM_PROGRAM`; the toggle overrides the detection. The row is greyed out and forced off on terminals that don't speak any image protocol.
416 - **terminal alerts** — emit a terminal bell when the main session stops with work that may need attention, or when an extension raises a structured alert. Enabled by default; changes apply immediately and persist as `terminal_alerts_enabled`. Terminal emulators may render the bell audibly, visually, or not at all.416 - **terminal alerts** — emit a terminal bell when the main session stops with work that may need attention, or when an extension raises a structured alert. Enabled by default; changes apply immediately and persist as `terminal_alerts_enabled`. Terminal emulators may render the bell audibly, visually, or not at all.
417 - **AI terminal titles** — after the first real prompt of a fresh interactive session, make one small hidden request to the active model and set the terminal title to `zut: <title>`. The title is limited to 40 Unicode characters, persisted with the session, restored on resume, and never added to the conversation. Enabled by default; disable it to avoid the extra model request. The toggle applies immediately, but title generation still waits for the first real prompt and never starts from startup context, resumed history, or slash commands. Persists as `terminal_title_enabled`.417 - **AI terminal titles** — after the first real prompt of a fresh interactive session, make one small hidden request to the active model and set the terminal title to `zut: <title>`. The title is limited to 40 Unicode characters, persisted with the session, restored on resume, and never added to the conversation. Enabled by default; disable it to avoid the extra model request. The toggle applies immediately, but title generation still waits for the first real prompt and never starts from startup context, resumed history, or slash commands. Persists as `terminal_title_enabled`.
418-- **auto-subagents** — enable strict primary-agent orchestrator mode: delegate implementation, debugging/testing, and code-review work to named or general workers; the primary agent coordinates but does not make direct implementation changes. It can spawn background sub-agents via `subagent_spawn` and query live state via `subagent_status` when those tools are permitted. If delegation is unavailable because of launch-time restrictions, the contract remains active, profile metadata is withheld, and the primary reports the limitation rather than implementing directly. Off by default. When off, the permitted canonical subagent tools remain available for user-requested delegation or when an active skill workflow requires it; the primary otherwise works directly without the strict orchestrator contract or profile manifest. The tools accept named profiles, reasoning, timeout, turn limits, and shared/worktree isolation where applicable; lifecycle and result references are persisted. Completion is host-event-driven: `[auto-subagents update]` completion updates are the only signal; do not poll or inspect dashboards, metadata, event logs, or files solely to wait. See `/subagents` and [docs/subagents.md](docs/subagents.md) for details.418+- **auto-subagents** — enable strict primary-agent orchestrator mode: delegate implementation, debugging/testing, and code-review work to named or general workers; the primary agent coordinates but does not make direct implementation changes. It can spawn background sub-agents via `subagent_spawn` and query live state via `subagent_status` when those tools are permitted. If delegation is unavailable because of launch-time restrictions, the contract remains active, profile metadata is withheld, and the primary reports the limitation rather than implementing directly. Off by default. When off, the permitted canonical subagent tools and profile manifest remain available for user-requested delegation or when an active skill workflow requires it; the primary otherwise works directly without the strict orchestrator contract. The tools accept named profiles, reasoning, per-spawn fast-mode overrides, timeout, turn limits, and shared/worktree isolation where applicable; lifecycle and result references are persisted. Completion is host-event-driven: `[auto-subagents update]` completion updates are the only signal; do not poll or inspect dashboards, metadata, event logs, or files solely to wait. See `/subagents` and [docs/subagents.md](docs/subagents.md) for details.
419 - **Ponytail coding mode** — include compact engineering guidance in each resolved system prompt; the guidance tells the model to apply it to coding, debugging, and review work rather than ordinary conversation. It favors understanding the real flow, small validated changes, reuse, and preserving safety checks. Enabled by default; changes apply to the next model call and persist as `ponytail_enabled` in `$ZUT_HOME/config.json`. It is included by interactive, print, stream, JSON, RPC, subagent, bot, SDK, and Zutfile agents.419 - **Ponytail coding mode** — include compact engineering guidance in each resolved system prompt; the guidance tells the model to apply it to coding, debugging, and review work rather than ordinary conversation. It favors understanding the real flow, small validated changes, reuse, and preserving safety checks. Enabled by default; changes apply to the next model call and persist as `ponytail_enabled` in `$ZUT_HOME/config.json`. It is included by interactive, print, stream, JSON, RPC, subagent, bot, SDK, and Zutfile agents.
420 - **fast mode** — request the provider's fast tier where supported. It currently uses OpenAI's Fast service tier for OpenAI, OpenAI Responses, and OpenAI Codex models. Off by default; toggle it with `/fast` or `/settings`; changes apply on the next model call and persist as `fast_mode`. Other providers currently return an unsupported-provider error. Fast mode may cost more and depends on the selected model/account.420 - **fast mode** — request the provider's fast tier where supported. It currently uses OpenAI's Fast service tier for OpenAI, OpenAI Responses, and OpenAI Codex models. Off by default; toggle it with `/fast` or `/settings`; changes apply on the next model call and persist as `fast_mode`. Other providers currently return an unsupported-provider error. Fast mode may cost more and depends on the selected model/account.
421 - **lsp in main session** — enable the built-in `lsp` tool and code diagnostics for the main agent. Enabled by default; changes persist as `lsp_enabled`.421 - **lsp in main session** — enable the built-in `lsp` tool and code diagnostics for the main agent. Enabled by default; changes persist as `lsp_enabled`.
M docs/subagents.md +7 -6
@@ -70,9 +70,9 @@ Output follows the selected existing mode. Print writes only the final synthesis
7070
7171 When **auto-subagents** is enabled in `/settings`, the interactive primary agent runs in strict orchestrator mode. It must delegate all implementation, debugging/testing, and code-review work to an appropriately named profile, or to a clearly described general worker when no profile fits. The primary agent must not write or edit code, make direct implementation tool calls, inspect or review code, or apply worker patches. It may decompose requests, select and spawn workers, check status, coordinate follow-ups, and synthesize their results.
7272
73-When auto-subagents is disabled, the canonical subagent tools remain available subject to launch-time policy. The primary may use them when the user requests delegation or an active skill workflow requires delegation. It continues to perform work itself otherwise and does not receive the profile manifest or strict orchestrator contract. The setting changes prompting, not tool availability.
73+When auto-subagents is disabled, the canonical subagent tools remain available subject to launch-time policy. The primary may use them when the user requests delegation or an active skill workflow requires delegation. It continues to perform work itself otherwise and does not receive the strict orchestrator contract. The setting changes orchestration prompting, not tool or profile availability.
7474
75-When `subagent_spawn` is available, the orchestrator receives a compact `[subagents_list]` section in its system prompt and should select the profile whose description best matches each worker task. If launch-time policy withholds `subagent_spawn`, strict orchestrator guidance remains active, profile metadata is omitted, and the primary must report that delegation is unavailable rather than implementing directly. If no profile fits when spawning is available, omit `agent` to use a general worker:
75+When `subagent_spawn` is available, the primary agent receives a compact `[subagents_list]` section in its system prompt and should select the profile whose description best matches each worker task. If launch-time policy withholds `subagent_spawn`, strict orchestrator guidance remains active, profile metadata is omitted, and the primary must report that delegation is unavailable rather than implementing directly. If no profile fits when spawning is available, omit `agent` to use a general worker:
7676
7777 ```json
7878 {
@@ -99,17 +99,18 @@ This profile metadata is a tool-selection boundary, not a general network sandbo
9999
100100 Completion is host-event-driven. After spawning, never use `bash sleep`, `watch`, `tail -f`, polling loops, repeated `subagent_status`, or dashboard, metadata, event-log, or file checks solely to wait; those are not completion signals. The primary may work on unrelated independent tasks, but otherwise must end or yield its turn until the host injects `[auto-subagents update]`. Completion updates are the only completion signal. Legitimate waits inside user-requested commands, provider flows, extensions, or tests are not prohibited.
101101
102-A per-spawn reasoning override is also accepted:
102+Per-spawn reasoning and fast-mode overrides are also accepted:
103103
104104 ```json
105105 {
106106 "task": "Implement the parser change and add regression tests.",
107107 "agent": "implementer",
108- "reasoning": "high"
108+ "reasoning": "high",
109+ "fast_mode": true
109110 }
110111 ```
111112
112-`thinking` is accepted as an alias for `reasoning`. If neither is supplied, the child inherits the host reasoning level for an unnamed spawn, or the profile's `thinking` value for a named spawn.
113+`thinking` is accepted as an alias for `reasoning`. If neither is supplied, the child inherits the host reasoning level for an unnamed spawn, or the profile's `thinking` value for a named spawn. An explicit `fast_mode` value takes precedence over the selected profile and host setting; omit it to inherit them.
113114
114115 The interactive command also supports the same selection explicitly:
115116
@@ -118,7 +119,7 @@ The interactive command also supports the same selection explicitly:
118119 /subagents new --agent implementer --reasoning high Implement the parser change
119120 ```
120121
121-Shared mode preserves the historical host working directory, so workers there must be coordinated to avoid conflicting edits and to sequence dependent tasks. For parallel coding, pass `isolation:"worktree"` to `subagent_spawn`; zut creates a detached Git worktree and captures changed files and a patch without merging automatically. In orchestrator mode, assign any worktree patch integration to a worker rather than applying it in the primary session. Named profiles change the child's instructions and configuration; they are not a security sandbox. A profile's `systemPromptMode` controls its own body relative to the built-in identity; globally appended instructions, including enabled Ponytail coding guidance, remain present. Child credentials are transferred over stdin rather than argv or persisted metadata, and the active provider endpoint/TLS setting is inherited only when the child uses that provider. Fast mode is inherited by default. A profile with `fastMode: false` opts out, while `fastMode: true` enables fast mode even when the host setting is off; the `subagent_spawn` result warns the parent session when this override occurs. Child providers that do not yet support fast mode return an unsupported-provider error instead of silently ignoring an enabled setting.
122+Shared mode preserves the historical host working directory, so workers there must be coordinated to avoid conflicting edits and to sequence dependent tasks. For parallel coding, pass `isolation:"worktree"` to `subagent_spawn`; zut creates a detached Git worktree and captures changed files and a patch without merging automatically. In orchestrator mode, assign any worktree patch integration to a worker rather than applying it in the primary session. Named profiles change the child's instructions and configuration; they are not a security sandbox. A profile's `systemPromptMode` controls its own body relative to the built-in identity; globally appended instructions, including enabled Ponytail coding guidance, remain present. Child credentials are transferred over stdin rather than argv or persisted metadata, and the active provider endpoint/TLS setting is inherited only when the child uses that provider. Fast mode is inherited by default. An explicit `fast_mode` spawn argument has highest precedence. Otherwise, a profile with `fastMode: false` opts out, while `fastMode: true` enables fast mode even when the host setting is off; the `subagent_spawn` result warns the parent session when this profile override occurs. Child providers that do not yet support fast mode return an unsupported-provider error instead of silently ignoring an enabled setting.
122123
123124 ## Lifecycle, results, and recovery
124125
@@ -70,9 +70,9 @@ Output follows the selected existing mode. Print writes only the final synthesis
70 70
71 When **auto-subagents** is enabled in `/settings`, the interactive primary agent runs in strict orchestrator mode. It must delegate all implementation, debugging/testing, and code-review work to an appropriately named profile, or to a clearly described general worker when no profile fits. The primary agent must not write or edit code, make direct implementation tool calls, inspect or review code, or apply worker patches. It may decompose requests, select and spawn workers, check status, coordinate follow-ups, and synthesize their results.71 When **auto-subagents** is enabled in `/settings`, the interactive primary agent runs in strict orchestrator mode. It must delegate all implementation, debugging/testing, and code-review work to an appropriately named profile, or to a clearly described general worker when no profile fits. The primary agent must not write or edit code, make direct implementation tool calls, inspect or review code, or apply worker patches. It may decompose requests, select and spawn workers, check status, coordinate follow-ups, and synthesize their results.
72 72
73-When auto-subagents is disabled, the canonical subagent tools remain available subject to launch-time policy. The primary may use them when the user requests delegation or an active skill workflow requires delegation. It continues to perform work itself otherwise and does not receive the profile manifest or strict orchestrator contract. The setting changes prompting, not tool availability.73+When auto-subagents is disabled, the canonical subagent tools remain available subject to launch-time policy. The primary may use them when the user requests delegation or an active skill workflow requires delegation. It continues to perform work itself otherwise and does not receive the strict orchestrator contract. The setting changes orchestration prompting, not tool or profile availability.
74 74
75-When `subagent_spawn` is available, the orchestrator receives a compact `[subagents_list]` section in its system prompt and should select the profile whose description best matches each worker task. If launch-time policy withholds `subagent_spawn`, strict orchestrator guidance remains active, profile metadata is omitted, and the primary must report that delegation is unavailable rather than implementing directly. If no profile fits when spawning is available, omit `agent` to use a general worker:75+When `subagent_spawn` is available, the primary agent receives a compact `[subagents_list]` section in its system prompt and should select the profile whose description best matches each worker task. If launch-time policy withholds `subagent_spawn`, strict orchestrator guidance remains active, profile metadata is omitted, and the primary must report that delegation is unavailable rather than implementing directly. If no profile fits when spawning is available, omit `agent` to use a general worker:
76 76
77 ```json77 ```json
78 {78 {
@@ -99,17 +99,18 @@ This profile metadata is a tool-selection boundary, not a general network sandbo
99 99
100 Completion is host-event-driven. After spawning, never use `bash sleep`, `watch`, `tail -f`, polling loops, repeated `subagent_status`, or dashboard, metadata, event-log, or file checks solely to wait; those are not completion signals. The primary may work on unrelated independent tasks, but otherwise must end or yield its turn until the host injects `[auto-subagents update]`. Completion updates are the only completion signal. Legitimate waits inside user-requested commands, provider flows, extensions, or tests are not prohibited.100 Completion is host-event-driven. After spawning, never use `bash sleep`, `watch`, `tail -f`, polling loops, repeated `subagent_status`, or dashboard, metadata, event-log, or file checks solely to wait; those are not completion signals. The primary may work on unrelated independent tasks, but otherwise must end or yield its turn until the host injects `[auto-subagents update]`. Completion updates are the only completion signal. Legitimate waits inside user-requested commands, provider flows, extensions, or tests are not prohibited.
101 101
102-A per-spawn reasoning override is also accepted:102+Per-spawn reasoning and fast-mode overrides are also accepted:
103 103
104 ```json104 ```json
105 {105 {
106 "task": "Implement the parser change and add regression tests.",106 "task": "Implement the parser change and add regression tests.",
107 "agent": "implementer",107 "agent": "implementer",
108- "reasoning": "high"108+ "reasoning": "high",
109+ "fast_mode": true
109 }110 }
110 ```111 ```
111 112
112-`thinking` is accepted as an alias for `reasoning`. If neither is supplied, the child inherits the host reasoning level for an unnamed spawn, or the profile's `thinking` value for a named spawn.113+`thinking` is accepted as an alias for `reasoning`. If neither is supplied, the child inherits the host reasoning level for an unnamed spawn, or the profile's `thinking` value for a named spawn. An explicit `fast_mode` value takes precedence over the selected profile and host setting; omit it to inherit them.
113 114
114 The interactive command also supports the same selection explicitly:115 The interactive command also supports the same selection explicitly:
115 116
@@ -118,7 +119,7 @@ The interactive command also supports the same selection explicitly:
118 /subagents new --agent implementer --reasoning high Implement the parser change119 /subagents new --agent implementer --reasoning high Implement the parser change
119 ```120 ```
120 121
121-Shared mode preserves the historical host working directory, so workers there must be coordinated to avoid conflicting edits and to sequence dependent tasks. For parallel coding, pass `isolation:"worktree"` to `subagent_spawn`; zut creates a detached Git worktree and captures changed files and a patch without merging automatically. In orchestrator mode, assign any worktree patch integration to a worker rather than applying it in the primary session. Named profiles change the child's instructions and configuration; they are not a security sandbox. A profile's `systemPromptMode` controls its own body relative to the built-in identity; globally appended instructions, including enabled Ponytail coding guidance, remain present. Child credentials are transferred over stdin rather than argv or persisted metadata, and the active provider endpoint/TLS setting is inherited only when the child uses that provider. Fast mode is inherited by default. A profile with `fastMode: false` opts out, while `fastMode: true` enables fast mode even when the host setting is off; the `subagent_spawn` result warns the parent session when this override occurs. Child providers that do not yet support fast mode return an unsupported-provider error instead of silently ignoring an enabled setting.122+Shared mode preserves the historical host working directory, so workers there must be coordinated to avoid conflicting edits and to sequence dependent tasks. For parallel coding, pass `isolation:"worktree"` to `subagent_spawn`; zut creates a detached Git worktree and captures changed files and a patch without merging automatically. In orchestrator mode, assign any worktree patch integration to a worker rather than applying it in the primary session. Named profiles change the child's instructions and configuration; they are not a security sandbox. A profile's `systemPromptMode` controls its own body relative to the built-in identity; globally appended instructions, including enabled Ponytail coding guidance, remain present. Child credentials are transferred over stdin rather than argv or persisted metadata, and the active provider endpoint/TLS setting is inherited only when the child uses that provider. Fast mode is inherited by default. An explicit `fast_mode` spawn argument has highest precedence. Otherwise, a profile with `fastMode: false` opts out, while `fastMode: true` enables fast mode even when the host setting is off; the `subagent_spawn` result warns the parent session when this profile override occurs. Child providers that do not yet support fast mode return an unsupported-provider error instead of silently ignoring an enabled setting.
122 123
123 ## Lifecycle, results, and recovery124 ## Lifecycle, results, and recovery
124 125
M packages/agent/build.go +13 -19
@@ -859,36 +859,30 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
859859 append_ = append(append_, skillAddendum)
860860 }
861861 interactiveMode := args.Mode == "" || args.Mode == ModeInteractive
862- if selectedProfile == nil && args.Orchestrate {
863- // Headless orchestration owns its prompt contract rather than inheriting
864- // the interactive setting. Discover and append the compact manifest once;
865- // child profile bodies remain private to the selected child.
866- if autoSubagentsToolAllowed(args) {
867- homeDir, _ := os.UserHomeDir()
868- profiles, _ := subagents.Discover(args.CWD, homeDir)
869- if subagentsAddendum := subagents.SystemPromptAddendum(profiles); subagentsAddendum != "" {
870- append_ = append(append_, subagentsAddendum)
871- }
862+ primaryInteractive := selectedProfile == nil && interactiveMode
863+ primaryOrchestrator := selectedProfile == nil && args.Orchestrate
864+ if (primaryInteractive || primaryOrchestrator) && autoSubagentsToolAllowed(args) {
865+ homeDir, _ := os.UserHomeDir()
866+ profiles, _ := subagents.Discover(args.CWD, homeDir)
867+ if subagentsAddendum := subagents.SystemPromptAddendum(profiles); subagentsAddendum != "" {
868+ append_ = append(append_, subagentsAddendum)
872869 }
870+ }
871+ if primaryOrchestrator {
872+ // Headless orchestration owns its strict prompt contract rather than
873+ // inheriting the interactive setting.
873874 append_ = append(append_, AutoSubagentsSystemAddendumFor(
874875 autoSubagentsToolAllowed(args),
875876 autoSubagentsStopToolAllowed(args),
876877 autoSubagentsResumeToolAllowed(args),
877878 ))
878- } else if selectedProfile == nil && interactiveMode && cfg.AutoSubagentsEnabled != nil && *cfg.AutoSubagentsEnabled {
879- if autoSubagentsToolAllowed(args) {
880- homeDir, _ := os.UserHomeDir()
881- profiles, _ := subagents.Discover(args.CWD, homeDir)
882- if subagentsAddendum := subagents.SystemPromptAddendum(profiles); subagentsAddendum != "" {
883- append_ = append(append_, subagentsAddendum)
884- }
885- }
879+ } else if primaryInteractive && cfg.AutoSubagentsEnabled != nil && *cfg.AutoSubagentsEnabled {
886880 append_ = append(append_, AutoSubagentsSystemAddendumFor(
887881 autoSubagentsToolAllowed(args),
888882 autoSubagentsStopToolAllowed(args),
889883 autoSubagentsResumeToolAllowed(args),
890884 ))
891- } else if selectedProfile == nil && interactiveMode && autoSubagentsAnyToolAllowed(args) {
885+ } else if primaryInteractive && autoSubagentsAnyToolAllowed(args) {
892886 append_ = append(append_, OnDemandSubagentsSystemAddendum)
893887 }
894888 if selectedProfile != nil && selectedProfile.SystemPromptMode != "replace" && selectedProfile.SystemPrompt != "" {
@@ -859,36 +859,30 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
859 append_ = append(append_, skillAddendum)859 append_ = append(append_, skillAddendum)
860 }860 }
861 interactiveMode := args.Mode == "" || args.Mode == ModeInteractive861 interactiveMode := args.Mode == "" || args.Mode == ModeInteractive
862- if selectedProfile == nil && args.Orchestrate {862+ primaryInteractive := selectedProfile == nil && interactiveMode
863- // Headless orchestration owns its prompt contract rather than inheriting863+ primaryOrchestrator := selectedProfile == nil && args.Orchestrate
864- // the interactive setting. Discover and append the compact manifest once;864+ if (primaryInteractive || primaryOrchestrator) && autoSubagentsToolAllowed(args) {
865- // child profile bodies remain private to the selected child.865+ homeDir, _ := os.UserHomeDir()
866- if autoSubagentsToolAllowed(args) {866+ profiles, _ := subagents.Discover(args.CWD, homeDir)
867- homeDir, _ := os.UserHomeDir()867+ if subagentsAddendum := subagents.SystemPromptAddendum(profiles); subagentsAddendum != "" {
868- profiles, _ := subagents.Discover(args.CWD, homeDir)868+ append_ = append(append_, subagentsAddendum)
869- if subagentsAddendum := subagents.SystemPromptAddendum(profiles); subagentsAddendum != "" {
870- append_ = append(append_, subagentsAddendum)
871- }
872 }869 }
870+ }
871+ if primaryOrchestrator {
872+ // Headless orchestration owns its strict prompt contract rather than
873+ // inheriting the interactive setting.
873 append_ = append(append_, AutoSubagentsSystemAddendumFor(874 append_ = append(append_, AutoSubagentsSystemAddendumFor(
874 autoSubagentsToolAllowed(args),875 autoSubagentsToolAllowed(args),
875 autoSubagentsStopToolAllowed(args),876 autoSubagentsStopToolAllowed(args),
876 autoSubagentsResumeToolAllowed(args),877 autoSubagentsResumeToolAllowed(args),
877 ))878 ))
878- } else if selectedProfile == nil && interactiveMode && cfg.AutoSubagentsEnabled != nil && *cfg.AutoSubagentsEnabled {879+ } else if primaryInteractive && cfg.AutoSubagentsEnabled != nil && *cfg.AutoSubagentsEnabled {
879- if autoSubagentsToolAllowed(args) {
880- homeDir, _ := os.UserHomeDir()
881- profiles, _ := subagents.Discover(args.CWD, homeDir)
882- if subagentsAddendum := subagents.SystemPromptAddendum(profiles); subagentsAddendum != "" {
883- append_ = append(append_, subagentsAddendum)
884- }
885- }
886 append_ = append(append_, AutoSubagentsSystemAddendumFor(880 append_ = append(append_, AutoSubagentsSystemAddendumFor(
887 autoSubagentsToolAllowed(args),881 autoSubagentsToolAllowed(args),
888 autoSubagentsStopToolAllowed(args),882 autoSubagentsStopToolAllowed(args),
889 autoSubagentsResumeToolAllowed(args),883 autoSubagentsResumeToolAllowed(args),
890 ))884 ))
891- } else if selectedProfile == nil && interactiveMode && autoSubagentsAnyToolAllowed(args) {885+ } else if primaryInteractive && autoSubagentsAnyToolAllowed(args) {
892 append_ = append(append_, OnDemandSubagentsSystemAddendum)886 append_ = append(append_, OnDemandSubagentsSystemAddendum)
893 }887 }
894 if selectedProfile != nil && selectedProfile.SystemPromptMode != "replace" && selectedProfile.SystemPrompt != "" {888 if selectedProfile != nil && selectedProfile.SystemPromptMode != "replace" && selectedProfile.SystemPrompt != "" {
M packages/agent/build_test.go +15 -3
@@ -514,20 +514,29 @@ func TestAutoSubagentsSystemAddendumRequiresHostCompletionUpdate(t *testing.T) {
514514 }
515515 }
516516
517-func TestResolveOmitsAutoSubagentsOrchestratorContractWhenDisabled(t *testing.T) {
517+func TestResolveKeepsNamedSubagentsListWithoutOrchestratorContractWhenDisabled(t *testing.T) {
518518 root := t.TempDir()
519519 home := filepath.Join(root, "home")
520520 zutHome := filepath.Join(root, "zut-home")
521521 project := filepath.Join(root, "repo")
522+ profilesDir := filepath.Join(home, ".agents", "agents")
523+ if err := os.MkdirAll(profilesDir, 0o755); err != nil {
524+ t.Fatal(err)
525+ }
522526 if err := os.MkdirAll(project, 0o755); err != nil {
523527 t.Fatal(err)
524528 }
525529 t.Setenv("HOME", home)
526530 t.Setenv("ZUT_HOME", zutHome)
531+ t.Setenv("ZUT_AGENT_PROFILES", profilesDir)
527532 disabled := false
528533 if err := SaveConfig(Config{Provider: "openai", Model: "gpt-5", AutoSubagentsEnabled: &disabled}); err != nil {
529534 t.Fatal(err)
530535 }
536+ profile := "---\nname: reviewer\ndescription: Read-only code reviewer\n---\nReview without editing.\n"
537+ if err := os.WriteFile(filepath.Join(profilesDir, "reviewer.md"), []byte(profile), 0o600); err != nil {
538+ t.Fatal(err)
539+ }
531540
532541 r, err := Resolve(Args{CWD: project}, false)
533542 if err != nil {
@@ -536,18 +545,21 @@ func TestResolveOmitsAutoSubagentsOrchestratorContractWhenDisabled(t *testing.T)
536545 for _, unwanted := range []string{
537546 AutoSubagentsSystemAddendum,
538547 "primary-agent orchestrator",
539- "[subagents_list]",
540548 } {
541549 if strings.Contains(r.SystemPrompt, unwanted) {
542550 t.Fatalf("disabled system prompt contains auto-subagents contract %q:\n%s", unwanted, r.SystemPrompt)
543551 }
544552 }
545553 for _, required := range []string{
554+ "[subagents_list]",
555+ "reviewer",
556+ "Read-only code reviewer",
557+ "[/subagents_list]",
546558 "user asks you to delegate",
547559 "active skill workflow requires delegation",
548560 } {
549561 if !strings.Contains(r.SystemPrompt, required) {
550- t.Fatalf("disabled system prompt omits permitted delegation path %q:\n%s", required, r.SystemPrompt)
562+ t.Fatalf("disabled system prompt omits available delegation context %q:\n%s", required, r.SystemPrompt)
551563 }
552564 }
553565 }
@@ -514,20 +514,29 @@ func TestAutoSubagentsSystemAddendumRequiresHostCompletionUpdate(t *testing.T) {
514 }514 }
515 }515 }
516 516
517-func TestResolveOmitsAutoSubagentsOrchestratorContractWhenDisabled(t *testing.T) {517+func TestResolveKeepsNamedSubagentsListWithoutOrchestratorContractWhenDisabled(t *testing.T) {
518 root := t.TempDir()518 root := t.TempDir()
519 home := filepath.Join(root, "home")519 home := filepath.Join(root, "home")
520 zutHome := filepath.Join(root, "zut-home")520 zutHome := filepath.Join(root, "zut-home")
521 project := filepath.Join(root, "repo")521 project := filepath.Join(root, "repo")
522+ profilesDir := filepath.Join(home, ".agents", "agents")
523+ if err := os.MkdirAll(profilesDir, 0o755); err != nil {
524+ t.Fatal(err)
525+ }
522 if err := os.MkdirAll(project, 0o755); err != nil {526 if err := os.MkdirAll(project, 0o755); err != nil {
523 t.Fatal(err)527 t.Fatal(err)
524 }528 }
525 t.Setenv("HOME", home)529 t.Setenv("HOME", home)
526 t.Setenv("ZUT_HOME", zutHome)530 t.Setenv("ZUT_HOME", zutHome)
531+ t.Setenv("ZUT_AGENT_PROFILES", profilesDir)
527 disabled := false532 disabled := false
528 if err := SaveConfig(Config{Provider: "openai", Model: "gpt-5", AutoSubagentsEnabled: &disabled}); err != nil {533 if err := SaveConfig(Config{Provider: "openai", Model: "gpt-5", AutoSubagentsEnabled: &disabled}); err != nil {
529 t.Fatal(err)534 t.Fatal(err)
530 }535 }
536+ profile := "---\nname: reviewer\ndescription: Read-only code reviewer\n---\nReview without editing.\n"
537+ if err := os.WriteFile(filepath.Join(profilesDir, "reviewer.md"), []byte(profile), 0o600); err != nil {
538+ t.Fatal(err)
539+ }
531 540
532 r, err := Resolve(Args{CWD: project}, false)541 r, err := Resolve(Args{CWD: project}, false)
533 if err != nil {542 if err != nil {
@@ -536,18 +545,21 @@ func TestResolveOmitsAutoSubagentsOrchestratorContractWhenDisabled(t *testing.T)
536 for _, unwanted := range []string{545 for _, unwanted := range []string{
537 AutoSubagentsSystemAddendum,546 AutoSubagentsSystemAddendum,
538 "primary-agent orchestrator",547 "primary-agent orchestrator",
539- "[subagents_list]",
540 } {548 } {
541 if strings.Contains(r.SystemPrompt, unwanted) {549 if strings.Contains(r.SystemPrompt, unwanted) {
542 t.Fatalf("disabled system prompt contains auto-subagents contract %q:\n%s", unwanted, r.SystemPrompt)550 t.Fatalf("disabled system prompt contains auto-subagents contract %q:\n%s", unwanted, r.SystemPrompt)
543 }551 }
544 }552 }
545 for _, required := range []string{553 for _, required := range []string{
554+ "[subagents_list]",
555+ "reviewer",
556+ "Read-only code reviewer",
557+ "[/subagents_list]",
546 "user asks you to delegate",558 "user asks you to delegate",
547 "active skill workflow requires delegation",559 "active skill workflow requires delegation",
548 } {560 } {
549 if !strings.Contains(r.SystemPrompt, required) {561 if !strings.Contains(r.SystemPrompt, required) {
550- t.Fatalf("disabled system prompt omits permitted delegation path %q:\n%s", required, r.SystemPrompt)562+ t.Fatalf("disabled system prompt omits available delegation context %q:\n%s", required, r.SystemPrompt)
551 }563 }
552 }564 }
553 }565 }
M packages/agent/modes/auto_subagents_test.go +9 -5
@@ -15,7 +15,7 @@ import (
1515
1616 const autoSubagentsTestTimeout = 10 * time.Second
1717
18-func TestAutoSubagentsSystemPromptTogglesProfileManifestWithDelegationGuidance(t *testing.T) {
18+func TestAutoSubagentsSystemPromptKeepsProfileManifestWhileTogglingDelegationGuidance(t *testing.T) {
1919 const onDemand = "delegate only on an explicit user request"
2020 iv := &Interactive{
2121 agent: &core.Agent{System: "base system"},
@@ -38,8 +38,11 @@ func TestAutoSubagentsSystemPromptTogglesProfileManifestWithDelegationGuidance(t
3838 }
3939
4040 iv.applyAutoSubagentsSystemPrompt(false)
41- if strings.Contains(iv.agent.System, "[subagents_list]") || strings.Contains(iv.agent.System, "auto-subagents guidance") {
42- t.Fatalf("disabled system prompt retained subagent blocks: %q", iv.agent.System)
41+ if !strings.Contains(iv.agent.System, "[subagents_list]") {
42+ t.Fatalf("disabled system prompt omitted profile manifest: %q", iv.agent.System)
43+ }
44+ if strings.Contains(iv.agent.System, "auto-subagents guidance") {
45+ t.Fatalf("disabled system prompt retained orchestrator guidance: %q", iv.agent.System)
4346 }
4447 if !strings.Contains(iv.agent.System, onDemand) {
4548 t.Fatalf("disabled system prompt omitted on-demand delegation guidance: %q", iv.agent.System)
@@ -64,8 +67,9 @@ func TestAutoSubagentsTogglePreservesMatchingBasePromptBlocks(t *testing.T) {
6467 t.Fatalf("enabling should append owned duplicate blocks: %q", iv.agent.System)
6568 }
6669 iv.applyAutoSubagentsSystemPrompt(false)
67- if got := strings.TrimSpace(iv.agent.System); got != base {
68- t.Fatalf("disabling changed matching base prompt: %q; want %q", got, base)
70+ want := base + "\n\n" + manifest
71+ if got := strings.TrimSpace(iv.agent.System); got != want {
72+ t.Fatalf("disabling changed matching base prompt blocks: %q; want %q", got, want)
6973 }
7074 }
7175
@@ -15,7 +15,7 @@ import (
15 15
16 const autoSubagentsTestTimeout = 10 * time.Second16 const autoSubagentsTestTimeout = 10 * time.Second
17 17
18-func TestAutoSubagentsSystemPromptTogglesProfileManifestWithDelegationGuidance(t *testing.T) {18+func TestAutoSubagentsSystemPromptKeepsProfileManifestWhileTogglingDelegationGuidance(t *testing.T) {
19 const onDemand = "delegate only on an explicit user request"19 const onDemand = "delegate only on an explicit user request"
20 iv := &Interactive{20 iv := &Interactive{
21 agent: &core.Agent{System: "base system"},21 agent: &core.Agent{System: "base system"},
@@ -38,8 +38,11 @@ func TestAutoSubagentsSystemPromptTogglesProfileManifestWithDelegationGuidance(t
38 }38 }
39 39
40 iv.applyAutoSubagentsSystemPrompt(false)40 iv.applyAutoSubagentsSystemPrompt(false)
41- if strings.Contains(iv.agent.System, "[subagents_list]") || strings.Contains(iv.agent.System, "auto-subagents guidance") {41+ if !strings.Contains(iv.agent.System, "[subagents_list]") {
42- t.Fatalf("disabled system prompt retained subagent blocks: %q", iv.agent.System)42+ t.Fatalf("disabled system prompt omitted profile manifest: %q", iv.agent.System)
43+ }
44+ if strings.Contains(iv.agent.System, "auto-subagents guidance") {
45+ t.Fatalf("disabled system prompt retained orchestrator guidance: %q", iv.agent.System)
43 }46 }
44 if !strings.Contains(iv.agent.System, onDemand) {47 if !strings.Contains(iv.agent.System, onDemand) {
45 t.Fatalf("disabled system prompt omitted on-demand delegation guidance: %q", iv.agent.System)48 t.Fatalf("disabled system prompt omitted on-demand delegation guidance: %q", iv.agent.System)
@@ -64,8 +67,9 @@ func TestAutoSubagentsTogglePreservesMatchingBasePromptBlocks(t *testing.T) {
64 t.Fatalf("enabling should append owned duplicate blocks: %q", iv.agent.System)67 t.Fatalf("enabling should append owned duplicate blocks: %q", iv.agent.System)
65 }68 }
66 iv.applyAutoSubagentsSystemPrompt(false)69 iv.applyAutoSubagentsSystemPrompt(false)
67- if got := strings.TrimSpace(iv.agent.System); got != base {70+ want := base + "\n\n" + manifest
68- t.Fatalf("disabling changed matching base prompt: %q; want %q", got, base)71+ if got := strings.TrimSpace(iv.agent.System); got != want {
72+ t.Fatalf("disabling changed matching base prompt blocks: %q; want %q", got, want)
69 }73 }
70 }74 }
71 75
M packages/agent/modes/interactive.go +17 -16
@@ -189,7 +189,7 @@ type InteractiveConfig struct {
189189 OnDemandSubagentsSystemAddendum string
190190
191191 // SubagentsSystemAddendum is the metadata-only [subagents_list]
192- // block that is added or removed together with auto-subagents.
192+ // block available whenever the primary agent can spawn subagents.
193193 SubagentsSystemAddendum string
194194 SettingsStore SettingsStore
195195
@@ -8853,25 +8853,26 @@ func (i *Interactive) deliverCompletionUpdates() {
88538853 }
88548854 }
88558855
8856-// autoSubagentsAddenda returns the prompt blocks owned by the auto-subagents
8857-// setting, in the same order Resolve appends them to a new agent.
8856+// autoSubagentsAddenda returns the prompt blocks owned by subagent prompting,
8857+// in the same order Resolve appends them to a new agent. The profile manifest
8858+// is independent of strict orchestration and remains available in on-demand
8859+// mode whenever the primary agent can spawn a worker.
88588860 func autoSubagentsAddenda(cfg InteractiveConfig, orchestrating bool) []string {
8859- if !orchestrating {
8860- if cfg.Supervisor == nil || !autoSubagentsAnyToolAllowedConfig(cfg) {
8861- return nil
8862- }
8863- if addendum := strings.TrimSpace(cfg.OnDemandSubagentsSystemAddendum); addendum != "" {
8864- return []string{addendum}
8865- }
8866- return nil
8867- }
8868-
88698861 addenda := make([]string, 0, 2)
88708862 if cfg.Supervisor != nil && autoSubagentsToolAllowedConfig(cfg) {
88718863 if addendum := strings.TrimSpace(cfg.SubagentsSystemAddendum); addendum != "" {
88728864 addenda = append(addenda, addendum)
88738865 }
88748866 }
8867+ if !orchestrating {
8868+ if cfg.Supervisor != nil && autoSubagentsAnyToolAllowedConfig(cfg) {
8869+ if addendum := strings.TrimSpace(cfg.OnDemandSubagentsSystemAddendum); addendum != "" {
8870+ addenda = append(addenda, addendum)
8871+ }
8872+ }
8873+ return addenda
8874+ }
8875+
88758876 if addendum := strings.TrimSpace(cfg.AutoSubagentsSystemAddendum); addendum != "" {
88768877 addenda = append(addenda, addendum)
88778878 }
@@ -8889,9 +8890,9 @@ func removeLastAutoSubagentsAddendum(system, addendum string) (string, bool) {
88898890 return system[:idx] + system[idx+len(addendum):], true
88908891 }
88918892
8892-// applyAutoSubagentsSystemPrompt swaps the prompt blocks owned by the
8893-// auto-subagents setting on the running agent. The profile manifest belongs
8894-// only to strict orchestration; disabled mode keeps on-demand guidance.
8893+// applyAutoSubagentsSystemPrompt swaps the prompt blocks owned by subagent
8894+// prompting on the running agent. Toggling strict orchestration changes the
8895+// delegation guidance while retaining the profile manifest when usable.
88958896 func (i *Interactive) applyAutoSubagentsSystemPrompt(orchestrating bool) {
88968897 // i.mu is sufficient here: this path updates the existing agent's system
88978898 // prompt and managed addenda in place; it does not replace the agent or
@@ -189,7 +189,7 @@ type InteractiveConfig struct {
189 OnDemandSubagentsSystemAddendum string189 OnDemandSubagentsSystemAddendum string
190 190
191 // SubagentsSystemAddendum is the metadata-only [subagents_list]191 // SubagentsSystemAddendum is the metadata-only [subagents_list]
192- // block that is added or removed together with auto-subagents.192+ // block available whenever the primary agent can spawn subagents.
193 SubagentsSystemAddendum string193 SubagentsSystemAddendum string
194 SettingsStore SettingsStore194 SettingsStore SettingsStore
195 195
@@ -8853,25 +8853,26 @@ func (i *Interactive) deliverCompletionUpdates() {
8853 }8853 }
8854 }8854 }
8855 8855
8856-// autoSubagentsAddenda returns the prompt blocks owned by the auto-subagents8856+// autoSubagentsAddenda returns the prompt blocks owned by subagent prompting,
8857-// setting, in the same order Resolve appends them to a new agent.8857+// in the same order Resolve appends them to a new agent. The profile manifest
8858+// is independent of strict orchestration and remains available in on-demand
8859+// mode whenever the primary agent can spawn a worker.
8858 func autoSubagentsAddenda(cfg InteractiveConfig, orchestrating bool) []string {8860 func autoSubagentsAddenda(cfg InteractiveConfig, orchestrating bool) []string {
8859- if !orchestrating {
8860- if cfg.Supervisor == nil || !autoSubagentsAnyToolAllowedConfig(cfg) {
8861- return nil
8862- }
8863- if addendum := strings.TrimSpace(cfg.OnDemandSubagentsSystemAddendum); addendum != "" {
8864- return []string{addendum}
8865- }
8866- return nil
8867- }
8868-
8869 addenda := make([]string, 0, 2)8861 addenda := make([]string, 0, 2)
8870 if cfg.Supervisor != nil && autoSubagentsToolAllowedConfig(cfg) {8862 if cfg.Supervisor != nil && autoSubagentsToolAllowedConfig(cfg) {
8871 if addendum := strings.TrimSpace(cfg.SubagentsSystemAddendum); addendum != "" {8863 if addendum := strings.TrimSpace(cfg.SubagentsSystemAddendum); addendum != "" {
8872 addenda = append(addenda, addendum)8864 addenda = append(addenda, addendum)
8873 }8865 }
8874 }8866 }
8867+ if !orchestrating {
8868+ if cfg.Supervisor != nil && autoSubagentsAnyToolAllowedConfig(cfg) {
8869+ if addendum := strings.TrimSpace(cfg.OnDemandSubagentsSystemAddendum); addendum != "" {
8870+ addenda = append(addenda, addendum)
8871+ }
8872+ }
8873+ return addenda
8874+ }
8875+
8875 if addendum := strings.TrimSpace(cfg.AutoSubagentsSystemAddendum); addendum != "" {8876 if addendum := strings.TrimSpace(cfg.AutoSubagentsSystemAddendum); addendum != "" {
8876 addenda = append(addenda, addendum)8877 addenda = append(addenda, addendum)
8877 }8878 }
@@ -8889,9 +8890,9 @@ func removeLastAutoSubagentsAddendum(system, addendum string) (string, bool) {
8889 return system[:idx] + system[idx+len(addendum):], true8890 return system[:idx] + system[idx+len(addendum):], true
8890 }8891 }
8891 8892
8892-// applyAutoSubagentsSystemPrompt swaps the prompt blocks owned by the8893+// applyAutoSubagentsSystemPrompt swaps the prompt blocks owned by subagent
8893-// auto-subagents setting on the running agent. The profile manifest belongs8894+// prompting on the running agent. Toggling strict orchestration changes the
8894-// only to strict orchestration; disabled mode keeps on-demand guidance.8895+// delegation guidance while retaining the profile manifest when usable.
8895 func (i *Interactive) applyAutoSubagentsSystemPrompt(orchestrating bool) {8896 func (i *Interactive) applyAutoSubagentsSystemPrompt(orchestrating bool) {
8896 // i.mu is sufficient here: this path updates the existing agent's system8897 // i.mu is sufficient here: this path updates the existing agent's system
8897 // prompt and managed addenda in place; it does not replace the agent or8898 // prompt and managed addenda in place; it does not replace the agent or
M packages/agent/subagents/persist.go +13 -5
@@ -1080,6 +1080,7 @@ func (f *Supervisor) resumeWithHook(ctx context.Context, id string, resuming boo
10801080 currentRunTurns := existing.CurrentRunTurnsValue()
10811081 pendingResume := existing.resumePromptState()
10821082 pendingResumePrompt, pendingResumePromptAt, pendingResumePromptID := pendingResume.Prompt, pendingResume.AcceptedAt, pendingResume.CommandID
1083+ hadPendingResumePrompt := pendingResumePrompt != ""
10831084 pendingResumeQueue := existing.resumePromptQueueSnapshot()
10841085 if pendingResumePrompt != "" && pendingResumePromptID == "" {
10851086 pendingResumePromptID = uuid.NewString()
@@ -1287,11 +1288,18 @@ func (f *Supervisor) resumeWithHook(ctx context.Context, id string, resuming boo
12871288 // Refreshing metadata happened before queue admission, so a later
12881289 // supervisor can reconstruct this resumed attempt even if the runner
12891290 // has not reached its first event yet. Register the turn before the
1290- // scheduler can start the worker and emit a fast turn_end.
1291- if before != nil && resumePrompt != "" {
1292- // No rejecting operation remains before scheduling, so there is no path
1293- // that needs the hook's rollback after this point.
1294- _ = before(a, resumePrompt)
1291+ // scheduler can start the worker and emit a fast turn_end. A durable pending
1292+ // prompt is consumed before the newly requested prompt, so tracker
1293+ // registrations must preserve that scheduler order.
1294+ if before != nil {
1295+ if hadPendingResumePrompt {
1296+ _ = before(a, pendingResumePrompt)
1297+ }
1298+ if resumePrompt != "" {
1299+ // No rejecting operation remains before scheduling, so there is no path
1300+ // that needs the hook's rollback after this point.
1301+ _ = before(a, resumePrompt)
1302+ }
12951303 }
12961304 f.armQueueTimeout(a)
12971305 f.schedule()
@@ -1080,6 +1080,7 @@ func (f *Supervisor) resumeWithHook(ctx context.Context, id string, resuming boo
1080 currentRunTurns := existing.CurrentRunTurnsValue()1080 currentRunTurns := existing.CurrentRunTurnsValue()
1081 pendingResume := existing.resumePromptState()1081 pendingResume := existing.resumePromptState()
1082 pendingResumePrompt, pendingResumePromptAt, pendingResumePromptID := pendingResume.Prompt, pendingResume.AcceptedAt, pendingResume.CommandID1082 pendingResumePrompt, pendingResumePromptAt, pendingResumePromptID := pendingResume.Prompt, pendingResume.AcceptedAt, pendingResume.CommandID
1083+ hadPendingResumePrompt := pendingResumePrompt != ""
1083 pendingResumeQueue := existing.resumePromptQueueSnapshot()1084 pendingResumeQueue := existing.resumePromptQueueSnapshot()
1084 if pendingResumePrompt != "" && pendingResumePromptID == "" {1085 if pendingResumePrompt != "" && pendingResumePromptID == "" {
1085 pendingResumePromptID = uuid.NewString()1086 pendingResumePromptID = uuid.NewString()
@@ -1287,11 +1288,18 @@ func (f *Supervisor) resumeWithHook(ctx context.Context, id string, resuming boo
1287 // Refreshing metadata happened before queue admission, so a later1288 // Refreshing metadata happened before queue admission, so a later
1288 // supervisor can reconstruct this resumed attempt even if the runner1289 // supervisor can reconstruct this resumed attempt even if the runner
1289 // has not reached its first event yet. Register the turn before the1290 // has not reached its first event yet. Register the turn before the
1290- // scheduler can start the worker and emit a fast turn_end.1291+ // scheduler can start the worker and emit a fast turn_end. A durable pending
1291- if before != nil && resumePrompt != "" {1292+ // prompt is consumed before the newly requested prompt, so tracker
1292- // No rejecting operation remains before scheduling, so there is no path1293+ // registrations must preserve that scheduler order.
1293- // that needs the hook's rollback after this point.1294+ if before != nil {
1294- _ = before(a, resumePrompt)1295+ if hadPendingResumePrompt {
1296+ _ = before(a, pendingResumePrompt)
1297+ }
1298+ if resumePrompt != "" {
1299+ // No rejecting operation remains before scheduling, so there is no path
1300+ // that needs the hook's rollback after this point.
1301+ _ = before(a, resumePrompt)
1302+ }
1295 }1303 }
1296 f.armQueueTimeout(a)1304 f.armQueueTimeout(a)
1297 f.schedule()1305 f.schedule()
M packages/agent/subagents/resume_prompt_test.go +74 -0
@@ -158,6 +158,80 @@ func TestResumeWithPromptQueuesFollowUpsForActiveWorker(t *testing.T) {
158158 a.Wait()
159159 }
160160
161+func TestResumeWithPromptRestartTracksExistingPendingPromptBeforeNewPrompt(t *testing.T) {
162+ root := t.TempDir()
163+ started := make(chan *Agent, 2)
164+ f := New(Config{
165+ Root: root, RepoRoot: root,
166+ NewRunner: func(a *Agent) Runner {
167+ return RunnerFunc(func(ctx context.Context, _ Sink) error {
168+ started <- a
169+ <-ctx.Done()
170+ return ctx.Err()
171+ })
172+ },
173+ })
174+ t.Cleanup(f.StopAll)
175+
176+ original, err := f.Spawn(context.Background(), "review the implementation")
177+ if err != nil {
178+ t.Fatal(err)
179+ }
180+ select {
181+ case <-started:
182+ case <-time.After(time.Second):
183+ t.Fatal("original runner did not start")
184+ }
185+ if err := f.Stop(original.ID); err != nil {
186+ t.Fatal(err)
187+ }
188+ original.Wait()
189+
190+ const (
191+ pendingPrompt = "finish the queued review"
192+ newPrompt = "then review the follow-up"
193+ )
194+ original.setResumePrompt(pendingPrompt, time.Now())
195+ if err := f.persistAgent(original); err != nil {
196+ t.Fatal(err)
197+ }
198+
199+ tracker := NewCompletionTracker()
200+ resumed, err := f.ResumeWithPromptBefore(context.Background(), original.ID, newPrompt, func(agent *Agent, prompt string) func() {
201+ return tracker.TrackFutureTurn(agent, prompt, true)
202+ })
203+ if err != nil {
204+ t.Fatal(err)
205+ }
206+ select {
207+ case got := <-started:
208+ if got != resumed {
209+ t.Fatalf("started agent = %p, want resumed agent %p", got, resumed)
210+ }
211+ case <-time.After(time.Second):
212+ t.Fatal("resumed runner did not start")
213+ }
214+ if got := resumed.resumePrompt(); got != pendingPrompt {
215+ t.Fatalf("first scheduled prompt = %q, want pending prompt %q", got, pendingPrompt)
216+ }
217+ queue := resumed.resumePromptQueueSnapshot()
218+ if len(queue) != 1 || queue[0].Prompt != newPrompt {
219+ t.Fatalf("resume queue = %+v, want new prompt queued second", queue)
220+ }
221+ if resumed.OnTurnEnd == nil {
222+ t.Fatal("restart did not install completion tracking before scheduling")
223+ }
224+ resumed.OnTurnEnd(1, "")
225+ resumed.OnTurnEnd(2, "")
226+ batch, err := tracker.WaitIdle(context.Background())
227+ if err != nil {
228+ t.Fatal(err)
229+ }
230+ if len(batch) != 2 || batch[0].Task != pendingPrompt || batch[1].Task != newPrompt {
231+ t.Fatalf("completion order = %+v, want pending prompt then new prompt", batch)
232+ }
233+}
234+
161235 func TestResumeWithPromptDeliversFollowUpToIdleWorker(t *testing.T) {
162236 if runtime.GOOS == "windows" {
163237 t.Skip("subagent inbox transport uses Unix-domain sockets")
@@ -158,6 +158,80 @@ func TestResumeWithPromptQueuesFollowUpsForActiveWorker(t *testing.T) {
158 a.Wait()158 a.Wait()
159 }159 }
160 160
161+func TestResumeWithPromptRestartTracksExistingPendingPromptBeforeNewPrompt(t *testing.T) {
162+ root := t.TempDir()
163+ started := make(chan *Agent, 2)
164+ f := New(Config{
165+ Root: root, RepoRoot: root,
166+ NewRunner: func(a *Agent) Runner {
167+ return RunnerFunc(func(ctx context.Context, _ Sink) error {
168+ started <- a
169+ <-ctx.Done()
170+ return ctx.Err()
171+ })
172+ },
173+ })
174+ t.Cleanup(f.StopAll)
175+
176+ original, err := f.Spawn(context.Background(), "review the implementation")
177+ if err != nil {
178+ t.Fatal(err)
179+ }
180+ select {
181+ case <-started:
182+ case <-time.After(time.Second):
183+ t.Fatal("original runner did not start")
184+ }
185+ if err := f.Stop(original.ID); err != nil {
186+ t.Fatal(err)
187+ }
188+ original.Wait()
189+
190+ const (
191+ pendingPrompt = "finish the queued review"
192+ newPrompt = "then review the follow-up"
193+ )
194+ original.setResumePrompt(pendingPrompt, time.Now())
195+ if err := f.persistAgent(original); err != nil {
196+ t.Fatal(err)
197+ }
198+
199+ tracker := NewCompletionTracker()
200+ resumed, err := f.ResumeWithPromptBefore(context.Background(), original.ID, newPrompt, func(agent *Agent, prompt string) func() {
201+ return tracker.TrackFutureTurn(agent, prompt, true)
202+ })
203+ if err != nil {
204+ t.Fatal(err)
205+ }
206+ select {
207+ case got := <-started:
208+ if got != resumed {
209+ t.Fatalf("started agent = %p, want resumed agent %p", got, resumed)
210+ }
211+ case <-time.After(time.Second):
212+ t.Fatal("resumed runner did not start")
213+ }
214+ if got := resumed.resumePrompt(); got != pendingPrompt {
215+ t.Fatalf("first scheduled prompt = %q, want pending prompt %q", got, pendingPrompt)
216+ }
217+ queue := resumed.resumePromptQueueSnapshot()
218+ if len(queue) != 1 || queue[0].Prompt != newPrompt {
219+ t.Fatalf("resume queue = %+v, want new prompt queued second", queue)
220+ }
221+ if resumed.OnTurnEnd == nil {
222+ t.Fatal("restart did not install completion tracking before scheduling")
223+ }
224+ resumed.OnTurnEnd(1, "")
225+ resumed.OnTurnEnd(2, "")
226+ batch, err := tracker.WaitIdle(context.Background())
227+ if err != nil {
228+ t.Fatal(err)
229+ }
230+ if len(batch) != 2 || batch[0].Task != pendingPrompt || batch[1].Task != newPrompt {
231+ t.Fatalf("completion order = %+v, want pending prompt then new prompt", batch)
232+ }
233+}
234+
161 func TestResumeWithPromptDeliversFollowUpToIdleWorker(t *testing.T) {235 func TestResumeWithPromptDeliversFollowUpToIdleWorker(t *testing.T) {
162 if runtime.GOOS == "windows" {236 if runtime.GOOS == "windows" {
163 t.Skip("subagent inbox transport uses Unix-domain sockets")237 t.Skip("subagent inbox transport uses Unix-domain sockets")
M packages/agent/subagents/supervisor_test.go +3 -0
@@ -234,6 +234,9 @@ func TestStopByRootSessionLeavesOtherSessionsRunning(t *testing.T) {
234234 }
235235
236236 func TestSupervisorShutdownCommandsIdentifyOrigin(t *testing.T) {
237+ if runtime.GOOS == "windows" {
238+ t.Skip("subagent inbox transport uses Unix-domain sockets")
239+ }
237240 for _, tc := range []struct {
238241 name string
239242 origin ShutdownOrigin
@@ -234,6 +234,9 @@ func TestStopByRootSessionLeavesOtherSessionsRunning(t *testing.T) {
234 }234 }
235 235
236 func TestSupervisorShutdownCommandsIdentifyOrigin(t *testing.T) {236 func TestSupervisorShutdownCommandsIdentifyOrigin(t *testing.T) {
237+ if runtime.GOOS == "windows" {
238+ t.Skip("subagent inbox transport uses Unix-domain sockets")
239+ }
237 for _, tc := range []struct {240 for _, tc := range []struct {
238 name string241 name string
239 origin ShutdownOrigin242 origin ShutdownOrigin
M packages/agent/tools/subagent_spawn.go +12 -1
@@ -56,6 +56,7 @@ type subagentSpawnArgs struct {
5656 Provider string `json:"provider,omitempty"`
5757 Reasoning string `json:"reasoning,omitempty"`
5858 Thinking string `json:"thinking,omitempty"`
59+ FastMode *bool `json:"fast_mode,omitempty"`
5960 Isolation string `json:"isolation,omitempty"`
6061 Timeout string `json:"timeout,omitempty"`
6162 MaxTurns *int `json:"max_turns,omitempty"`
@@ -90,6 +91,10 @@ const subagentSpawnSchemaTemplate = `{
9091 "enum": ["off", "minimum", "low", "medium", "high", "xhigh", "max"],
9192 "description": "Alias for reasoning, accepted for compatibility with common agent profile terminology. Prefer reasoning when both are available."
9293 },
94+ "fast_mode": {
95+ "type": "boolean",
96+ "description": "Optional fast-mode override for the child. Omit to inherit the selected profile or host setting."
97+ },
9398 "isolation": {
9499 "type": "string",
95100 "enum": ["shared", "worktree"],
@@ -167,6 +172,7 @@ func (t *SubagentSpawnTool) Execute(ctx context.Context, raw json.RawMessage, pr
167172 agentName := strings.TrimSpace(a.Agent)
168173 var profile *subagents.Profile
169174 var fastModeOverride *bool
175+ fastModeFromProfile := false
170176 if agentName != "" {
171177 if t.ResolveSubagent == nil {
172178 return protocolToolError(prefix + ": named subagent profiles are unavailable")
@@ -181,6 +187,11 @@ func (t *SubagentSpawnTool) Execute(ctx context.Context, raw json.RawMessage, pr
181187 }
182188 agentName = profile.Name
183189 fastModeOverride = profile.FastMode
190+ fastModeFromProfile = profile.FastMode != nil
191+ }
192+ if a.FastMode != nil {
193+ fastModeOverride = a.FastMode
194+ fastModeFromProfile = false
184195 }
185196
186197 model := strings.TrimSpace(a.Model)
@@ -271,7 +282,7 @@ func (t *SubagentSpawnTool) Execute(ctx context.Context, raw json.RawMessage, pr
271282 if agent.FastMode {
272283 sb.WriteString("fast mode: enabled\n")
273284 }
274- if fastModeOverride != nil && *fastModeOverride && agent.FastModeOverridesHost() {
285+ if fastModeFromProfile && fastModeOverride != nil && *fastModeOverride && agent.FastModeOverridesHost() {
275286 sb.WriteString("warning: subagent profile has fast mode enabled, overriding global fast mode off\n")
276287 }
277288 sb.WriteString("\nThe sub-agent is running in the background. Completion is host-event-driven: wait for the injected [auto-subagents update], the only completion signal. ")
@@ -56,6 +56,7 @@ type subagentSpawnArgs struct {
56 Provider string `json:"provider,omitempty"`56 Provider string `json:"provider,omitempty"`
57 Reasoning string `json:"reasoning,omitempty"`57 Reasoning string `json:"reasoning,omitempty"`
58 Thinking string `json:"thinking,omitempty"`58 Thinking string `json:"thinking,omitempty"`
59+ FastMode *bool `json:"fast_mode,omitempty"`
59 Isolation string `json:"isolation,omitempty"`60 Isolation string `json:"isolation,omitempty"`
60 Timeout string `json:"timeout,omitempty"`61 Timeout string `json:"timeout,omitempty"`
61 MaxTurns *int `json:"max_turns,omitempty"`62 MaxTurns *int `json:"max_turns,omitempty"`
@@ -90,6 +91,10 @@ const subagentSpawnSchemaTemplate = `{
90 "enum": ["off", "minimum", "low", "medium", "high", "xhigh", "max"],91 "enum": ["off", "minimum", "low", "medium", "high", "xhigh", "max"],
91 "description": "Alias for reasoning, accepted for compatibility with common agent profile terminology. Prefer reasoning when both are available."92 "description": "Alias for reasoning, accepted for compatibility with common agent profile terminology. Prefer reasoning when both are available."
92 },93 },
94+ "fast_mode": {
95+ "type": "boolean",
96+ "description": "Optional fast-mode override for the child. Omit to inherit the selected profile or host setting."
97+ },
93 "isolation": {98 "isolation": {
94 "type": "string",99 "type": "string",
95 "enum": ["shared", "worktree"],100 "enum": ["shared", "worktree"],
@@ -167,6 +172,7 @@ func (t *SubagentSpawnTool) Execute(ctx context.Context, raw json.RawMessage, pr
167 agentName := strings.TrimSpace(a.Agent)172 agentName := strings.TrimSpace(a.Agent)
168 var profile *subagents.Profile173 var profile *subagents.Profile
169 var fastModeOverride *bool174 var fastModeOverride *bool
175+ fastModeFromProfile := false
170 if agentName != "" {176 if agentName != "" {
171 if t.ResolveSubagent == nil {177 if t.ResolveSubagent == nil {
172 return protocolToolError(prefix + ": named subagent profiles are unavailable")178 return protocolToolError(prefix + ": named subagent profiles are unavailable")
@@ -181,6 +187,11 @@ func (t *SubagentSpawnTool) Execute(ctx context.Context, raw json.RawMessage, pr
181 }187 }
182 agentName = profile.Name188 agentName = profile.Name
183 fastModeOverride = profile.FastMode189 fastModeOverride = profile.FastMode
190+ fastModeFromProfile = profile.FastMode != nil
191+ }
192+ if a.FastMode != nil {
193+ fastModeOverride = a.FastMode
194+ fastModeFromProfile = false
184 }195 }
185 196
186 model := strings.TrimSpace(a.Model)197 model := strings.TrimSpace(a.Model)
@@ -271,7 +282,7 @@ func (t *SubagentSpawnTool) Execute(ctx context.Context, raw json.RawMessage, pr
271 if agent.FastMode {282 if agent.FastMode {
272 sb.WriteString("fast mode: enabled\n")283 sb.WriteString("fast mode: enabled\n")
273 }284 }
274- if fastModeOverride != nil && *fastModeOverride && agent.FastModeOverridesHost() {285+ if fastModeFromProfile && fastModeOverride != nil && *fastModeOverride && agent.FastModeOverridesHost() {
275 sb.WriteString("warning: subagent profile has fast mode enabled, overriding global fast mode off\n")286 sb.WriteString("warning: subagent profile has fast mode enabled, overriding global fast mode off\n")
276 }287 }
277 sb.WriteString("\nThe sub-agent is running in the background. Completion is host-event-driven: wait for the injected [auto-subagents update], the only completion signal. ")288 sb.WriteString("\nThe sub-agent is running in the background. Completion is host-event-driven: wait for the injected [auto-subagents update], the only completion signal. ")
M packages/agent/tools/subagent_spawn_test.go +58 -0
@@ -3,6 +3,7 @@ package tools
33 import (
44 "context"
55 "encoding/json"
6+ "fmt"
67 "path/filepath"
78 "strings"
89 "testing"
@@ -349,6 +350,63 @@ func TestSubagentSpawnAppliesProfileFastModeRestriction(t *testing.T) {
349350 }
350351 }
351352
353+func TestSubagentSpawnSchemaAdvertisesFastModeOverride(t *testing.T) {
354+ var schema struct {
355+ Properties map[string]struct {
356+ Type string `json:"type"`
357+ Description string `json:"description"`
358+ } `json:"properties"`
359+ }
360+ if err := json.Unmarshal((&SubagentSpawnTool{}).Schema(), &schema); err != nil {
361+ t.Fatal(err)
362+ }
363+ fastMode, ok := schema.Properties["fast_mode"]
364+ if !ok {
365+ t.Fatal("subagent_spawn schema does not advertise fast_mode")
366+ }
367+ if fastMode.Type != "boolean" {
368+ t.Fatalf("fast_mode type = %q, want boolean", fastMode.Type)
369+ }
370+ if !strings.Contains(strings.ToLower(fastMode.Description), "inherit") {
371+ t.Fatalf("fast_mode description = %q, want inheritance guidance", fastMode.Description)
372+ }
373+}
374+
375+func TestSubagentSpawnExplicitFastModeOverridesProfile(t *testing.T) {
376+ for _, tc := range []struct {
377+ name string
378+ profileFast bool
379+ spawnFast bool
380+ }{
381+ {name: "enable", profileFast: false, spawnFast: true},
382+ {name: "disable", profileFast: true, spawnFast: false},
383+ } {
384+ t.Run(tc.name, func(t *testing.T) {
385+ profileFast := tc.profileFast
386+ tool := &SubagentSpawnTool{
387+ Supervisor: newTestSupervisor(t),
388+ Enabled: func() bool { return true },
389+ ResolveSubagent: func(name string) (*subagents.Profile, error) {
390+ return &subagents.Profile{Name: name, FastMode: &profileFast}, nil
391+ },
392+ }
393+
394+ raw := fmt.Sprintf(`{"task":"review auth","agent":"worker","fast_mode":%t}`, tc.spawnFast)
395+ res, err := tool.Execute(context.Background(), json.RawMessage(raw), nil)
396+ if err != nil {
397+ t.Fatal(err)
398+ }
399+ if res.IsError {
400+ t.Fatalf("unexpected tool error: %s", textResult(res.Content))
401+ }
402+ agents := tool.Supervisor.List()
403+ if len(agents) != 1 || agents[0].FastMode != tc.spawnFast {
404+ t.Fatalf("agent fast mode = %#v, want %t", agents, tc.spawnFast)
405+ }
406+ })
407+ }
408+}
409+
352410 func TestSubagentSpawnProfileFastModeOverridesGlobalOffWithWarning(t *testing.T) {
353411 root := t.TempDir()
354412 profileFastMode := true
@@ -3,6 +3,7 @@ package tools
3 import (3 import (
4 "context"4 "context"
5 "encoding/json"5 "encoding/json"
6+ "fmt"
6 "path/filepath"7 "path/filepath"
7 "strings"8 "strings"
8 "testing"9 "testing"
@@ -349,6 +350,63 @@ func TestSubagentSpawnAppliesProfileFastModeRestriction(t *testing.T) {
349 }350 }
350 }351 }
351 352
353+func TestSubagentSpawnSchemaAdvertisesFastModeOverride(t *testing.T) {
354+ var schema struct {
355+ Properties map[string]struct {
356+ Type string `json:"type"`
357+ Description string `json:"description"`
358+ } `json:"properties"`
359+ }
360+ if err := json.Unmarshal((&SubagentSpawnTool{}).Schema(), &schema); err != nil {
361+ t.Fatal(err)
362+ }
363+ fastMode, ok := schema.Properties["fast_mode"]
364+ if !ok {
365+ t.Fatal("subagent_spawn schema does not advertise fast_mode")
366+ }
367+ if fastMode.Type != "boolean" {
368+ t.Fatalf("fast_mode type = %q, want boolean", fastMode.Type)
369+ }
370+ if !strings.Contains(strings.ToLower(fastMode.Description), "inherit") {
371+ t.Fatalf("fast_mode description = %q, want inheritance guidance", fastMode.Description)
372+ }
373+}
374+
375+func TestSubagentSpawnExplicitFastModeOverridesProfile(t *testing.T) {
376+ for _, tc := range []struct {
377+ name string
378+ profileFast bool
379+ spawnFast bool
380+ }{
381+ {name: "enable", profileFast: false, spawnFast: true},
382+ {name: "disable", profileFast: true, spawnFast: false},
383+ } {
384+ t.Run(tc.name, func(t *testing.T) {
385+ profileFast := tc.profileFast
386+ tool := &SubagentSpawnTool{
387+ Supervisor: newTestSupervisor(t),
388+ Enabled: func() bool { return true },
389+ ResolveSubagent: func(name string) (*subagents.Profile, error) {
390+ return &subagents.Profile{Name: name, FastMode: &profileFast}, nil
391+ },
392+ }
393+
394+ raw := fmt.Sprintf(`{"task":"review auth","agent":"worker","fast_mode":%t}`, tc.spawnFast)
395+ res, err := tool.Execute(context.Background(), json.RawMessage(raw), nil)
396+ if err != nil {
397+ t.Fatal(err)
398+ }
399+ if res.IsError {
400+ t.Fatalf("unexpected tool error: %s", textResult(res.Content))
401+ }
402+ agents := tool.Supervisor.List()
403+ if len(agents) != 1 || agents[0].FastMode != tc.spawnFast {
404+ t.Fatalf("agent fast mode = %#v, want %t", agents, tc.spawnFast)
405+ }
406+ })
407+ }
408+}
409+
352 func TestSubagentSpawnProfileFastModeOverridesGlobalOffWithWarning(t *testing.T) {410 func TestSubagentSpawnProfileFastModeOverridesGlobalOffWithWarning(t *testing.T) {
353 root := t.TempDir()411 root := t.TempDir()
354 profileFastMode := true412 profileFastMode := true

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close