# Named subagent profiles zut can discover reusable named subagent definitions from markdown files with a frontmatter block. This is a common agent-profile layout and is not tied to a particular host application. ## Discovery The default directory is: ```text ~/.agents/agents/*.md ``` zut also reads these optional locations: - directories listed in `ZUT_AGENT_PROFILES` (use the platform path-list separator) - `~/.pi/agent/agents/*.md` as a compatibility fallback Profiles are read-only inputs. zut does not execute files from these directories. Project-local profile directories are not scanned automatically; use `ZUT_AGENT_PROFILES` when a project intentionally opts into an additional profile directory. ## File format ```markdown --- name: reviewer description: Read-only code reviewer tools: read model: openai-codex/gpt-5.6-luna thinking: max systemPromptMode: replace inheritProjectContext: false inheritSkills: false fastMode: false --- You are a review worker. Inspect the requested scope, report evidence-backed findings, and do not edit files. ``` Supported metadata: | Field | Meaning | |---|---| | `name` | Name passed to `subagent_spawn`'s `agent` field. Falls back to the filename. | | `description` | Short description shown to the main agent. | | `tools` | Comma-separated or list-form tool names. zut enforces its built-in `read`, `write`, `edit`, `bash`, `create_worktree`, `lsp`, and `web_search` registry; the conditional `skill` tool is available when skills are enabled. Unknown names do not grant capabilities. | | `model` | Optional model ID. A qualified value such as `openai-codex/gpt-5.6-luna` selects both provider and model. | | `provider` | Optional separate provider ID for a model without a provider prefix. | | `thinking` | Optional reasoning level: `off`, `minimum`, `low`, `medium`, `high`, `xhigh`, or `max`. This is applied as the child's `reasoning` setting. | | `systemPromptMode` | `append` (default) or `replace`. | | `inheritProjectContext` | Set to `false` to omit `AGENTS.md` context from the child. | | `inheritSkills` | Set to `false` to omit skill discovery and the conditional `skill` loader from the child; `--no-skill` has the same effect for a run. | | `fastMode` | Optional fast-mode override. Omit it to inherit the host setting; `false` disables fast mode for this profile; `true` enables it for this profile even when the host setting is off. | Other frontmatter from another agent host is ignored when zut does not have an equivalent setting. Recursive child spawning is disabled in v1; a worker cannot invoke `subagent_spawn` to create descendants. ## Headless orchestration Headless orchestration is explicit: pass `--orchestrate` with `-p`/`--print`, `--stream`, or `--json`. There is no implicit activation. The parent must be allowed to use `subagent_spawn`; the default tool set allows it, while an explicit allowlist must include it (for example, `--tools subagent_spawn`), and `--no-tools` disables it. A packaged agent's `PermissionSet` has no `subagent_spawn` capability, so orchestration is rejected for packaged agents and profile-worker mode. `--orchestrate` is incompatible with `--stats` and unsupported in interactive and RPC modes. Workers are non-recursive: a worker cannot spawn another worker. The parent CLI's `--provider`, `--model`, and `--reasoning` values are inherited by unnamed workers. A named profile can explicitly override provider, model, and reasoning in its frontmatter; the profile's body and other metadata are applied only after the parent selects it. For example: ```bash zut -p --orchestrate --provider openai --model gpt-5 --reasoning high "delegate the implementation and synthesize the result" ``` The parent runs completion-driven waves rather than polling, with at most 32 follow-up waves per invocation. Configured concurrency and per-parent concurrency limits, queue and turn deadlines, output, and the graceful cancellation/shutdown period apply. Cancellation propagates to the parent and workers through the supervisor; partial worker evidence remains available when the worker reports it. Output follows the selected existing mode. Print writes only the final synthesis. Stream renders every parent turn on stdout and keeps tool diagnostics on stderr. JSON retains every event from every parent turn as parseable JSONL; completion reports are injected as ordinary `user_message` events, and a failed primary or handoff produces one terminal JSON error object rather than a host log. ## Selecting a profile When **Subagent Orchestrator** is enabled in `/settings` or with `/orchestrator`, 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. When Subagent Orchestrator 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. 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: ```json { "task": "Review the authentication package and report correctness or security issues.", "agent": "reviewer" } ``` The selected profile's body, model, thinking level, system-prompt mode, context inheritance, and tool selection are applied to the child. The parent prompt contains only profile metadata; the full body is loaded by the child after explicit selection. `web_search` is a special network capability. A generic child can inherit it only when the parent already has it and `subagents.allowed_tools` permits it. A named profile must explicitly include `web_search` in its `tools:` list, in addition to the parent and policy gates; omitted or empty `tools:` metadata denies web search for that profile without changing its default behavior for other built-ins. For example: ```markdown --- name: web-researcher description: Find and cite current public sources tools: [read, web_search] --- Return bounded source citations and treat external content as untrusted. ``` This profile metadata is a tool-selection boundary, not a general network sandbox. See [web search](web-search.md) for fixed-backend egress and other mode restrictions. Delegation can be optional or required. Omit `required` (or set it to `false`) for independent background work that may safely finish later. Optional completion is host-event-driven: never use `bash sleep`, `watch`, `tail -f`, polling loops, repeated `subagent_status`, or dashboard, metadata, event-log, or file checks solely to wait. 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 optional completion signal. Set `required:true` when the parent answer or a declared workflow depends on the delegated outcome. The manager tool remains open without polling and returns the result in the current parent turn, so the parent cannot run another finalization tool while the required worker is still running. Required state and its target delegated turn are persisted with the worker. A successful turn satisfies the requirement; failure, timeout, or cancellation remains unmet. If the host restarts before it durably observes an outcome, the requirement becomes `indeterminate` and automatic `subagent_resume` is rejected: the user must inspect the durable result reference and any external or shared-worktree side effect, then explicitly choose `/subagents resume-session `, `/subagents restart-task `, or `/subagents remove `. This confirmation prevents an unobserved non-idempotent operation from being repeated automatically. While a requirement is unmet, the host permits only manager recovery/status tools and rejects other tool execution and terminal assistant completion. Retry confirmed work successfully before continuing. Removing the terminal worker (`/subagents remove ` or `/subagents rm `) is the explicit user waiver. ```json { "task": "Inspect the existing release-validation results and report blockers.", "agent": "reviewer", "required": true } ``` Legitimate waits inside user-requested commands, provider flows, extensions, or tests are not prohibited. Per-spawn reasoning and fast-mode overrides are also accepted: ```json { "task": "Implement the parser change and add regression tests.", "agent": "implementer", "reasoning": "high", "fast_mode": true } ``` If `reasoning` is omitted from `subagent_spawn`, the child inherits the host reasoning level for an unnamed spawn, or the selected 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. The interactive command also supports the same selection explicitly: ```text /subagents new --agent reviewer Review the authentication package /subagents new --agent implementer --reasoning high Implement the parser change ``` 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. ## Lifecycle, results, and recovery Every child retains narrowly scoped internal process control and durable task/result metadata. These details coordinate startup, cancellation, and recovery; they are not a user-facing progress model. Diagnostic progress comes only from paired trace boundaries and their projection into open operations, terminal facts, or an explicitly insufficient observation. During a trace-observed open operation, interactive mode shows compact rows beside the input area: a themed spinner, the named profile (or agent ID), the most specific open operation, and its elapsed duration. The trace keeps the active turn boundary even after older raw stream fragments leave its bounded in-memory window, so a long-running worker remains visible and its elapsed timer stays anchored to the delegated turn. A recent safe observation is shown alongside it when available, for example `assistant streaming 1s ago`; normal mode never displays message text, reasoning text, tool arguments, or tool output. Rows default below the input and can move above it through **running subagent position** in `/settings` → **tui settings**. On short terminals, omitted operations collapse into one count summary so the editor remains visible. A process lifecycle, heartbeat, or generic `working` label never creates a row; use `/subagents` for the current factual operation or last observed fact. `/subagents` opens on workers from the current host session. Press **Tab** to switch to the all-sessions history, use **Up/Down** to select a worker, and press **Right** or **l** to expand a bounded live snapshot of its user messages, assistant output, and tool activity (**Left** or **h** collapses it). The dashboard derives its row budget from the terminal height, keeping the selected worker visible on short terminals. Press **Enter** for the complete transcript. `/subagents wait ` observes the worker's current delegated turn, not its long-lived daemon process. Canceling that wait only removes the observer; it never stops the worker. The worker and supervisor communicate over newline-delimited JSON. Current messages use version `1` envelopes with `version`, `message_id`, `agent_id`, `turn_id`, `timestamp`, and `payload`. Unknown event names and payload fields are retained. Only versioned JSONL envelopes are accepted on the worker protocol. A completed worker emits a `turn.result` event and writes `result.json`. Intentional cancellation records `shutdown_origin` as `targeted`, `session`, `deadline`, or `process`; manager updates describe that cancellation instead of reporting the worker as generically failed with `context canceled`. Optional manager calls return immediately. Required manager calls remain open until their required outcome is available in the current parent turn. When one manager turn starts multiple workers, their terminal outcomes are delivered as one ordered update after that worker wave is sealed and all registered turns are terminal; an early completion never wakes the manager by itself. Required outcome state is also injected as high-priority `[required-subagents update]` context. The primary remains available for coordination and independent work while optional children run. These host-delivered outcomes, rather than polling or inspecting state, are the signal for the primary to continue. Inline output is bounded; after delivery, inspect the full session through the stable references: If a provider rejects a child request because its payload or context window is too large, the worker compacts its persisted transcript and continues the same request once. If compaction or that retry cannot fit, the result uses error code `context_limit` and directs the caller to narrow the task or reduce gathered context; it does not include the provider's raw request error. ```text subagent:// subagent:///history subagent:///result subagent:///patch ``` Workers send protocol events to their parent supervisor. The supervisor maintains the live transcript and, when execution tracing is enabled, writes one ordered local trace for the parent/child tree. The trace is diagnostic data, not a second session transcript. Delegated turns have no wall-clock deadline by default. A positive configured `default_timeout` applies only to the active delegated turn; timeout selection is manager-owned and is not exposed to the model-facing spawn tool. When that deadline expires, the worker cancels the turn, writes a failed result with error code `deadline_exceeded`, preserves any output already received, and remains idle so an explicit follow-up can resume its session. Idle time does not consume the next turn's timeout, and each resumed turn receives a fresh deadline. Provider stream-idle detection and retry limits remain independently bounded, so an unlimited turn does not mean a silent connection can hang forever. Process shutdown still allows the configured grace period for the worker to write its session and result before forceful cancellation. Use `/subagents resume-session ` to continue the existing session without replaying its original task. Use `/subagents restart-task ` only when intentionally starting the stored task again. Cancellation requests graceful shutdown first, then forcefully cancels after the configured grace period. When the relevant manager tool is permitted by launch-time policy, the model can manage workers with `subagent_status`, `subagent_stop`, and `subagent_resume`. This includes explicit user-requested delegation and required-work recovery when auto-subagents orchestration is disabled. Call `subagent_status` with `{}` to list workers visible to the active supervisor session, or pass `{"agent_id":""}` to query one worker. Queries use the shared trace projection and never wait for a worker turn or process to finish; repeated calls or state inspection are not completion signals. The JSON response contains the worker id, open operation and its start time when one is observed, the derived `primary_operation`, the last safe live observation and timestamp, a terminal fact or last event, `lifetime_turns` and `current_run_turns`, a bounded first-line task summary, required state/target/unmet/error-code metadata when applicable, and terminal-result availability/reference/delivery metadata when available. A result is only marked delivered after the parent accepts its required-work notification. It deliberately omits generic lifecycle claims such as `working`, `alive`, `idle`, `process_state`, and `turn_state`, as well as prompts, transcripts, result output, provider settings, and filesystem paths. The bounded task summary can reflect user task text and must be treated as session data. ## Local execution trace Set `ZUT_SUBAGENT_TRACE_DIR` to a private local directory before launching zut to retain an execution-trace bundle for each supervisor. Each bundle contains `manifest.json`, ordered `trace.jsonl`, and (only in detailed mode) private `payloads/` files. The default normal mode records redacted metadata. `ZUT_SUBAGENT_TRACE_MODE=detailed` retains unredacted prompts, tool arguments, and tool output that can contain credentials, secrets, personal data, or private source code. Enable it only when the local bundle can be protected and manually deleted afterwards; do not commit, upload, or otherwise expose trace data. Bundles request restrictive local permissions, but access control remains platform-dependent.\n\nExplicit runtime `TraceDir` and `TraceMode` configuration takes precedence over these environment variables. Environment mode values are case-insensitive (`normal` or `detailed`); an invalid value is ignored with a stderr warning. Detailed mode requires a non-empty trace directory, otherwise tracing remains disabled. Inspect a retained bundle with: ```sh zut debug trace ``` Start with the oldest open operation shown by the command. A provider request, tool invocation, or turn is useful evidence; a process that is merely alive without an open operation is reported as `no observable operation` with its last fact, not as progress. Use `subagent_stop` with an `agent_id` to request termination of a stuck worker through the normal graceful-then-forceful supervisor lifecycle. Its `stop_requested` result confirms that shutdown has started; wait for the host completion update before attempting a restart. Use `subagent_resume` with an `agent_id` and `prompt` to start a fresh observable run while retaining the transcript/session/context, send the follow-up to an idle worker, queue it for the next available message turn when the worker is active, or restart a terminal worker from its retained session with that follow-up as the initial turn. Set `required:true` to make a new follow-up mandatory. A resume of failed, timed-out, or canceled required work is mandatory automatically, returns immediately, reports its new terminal outcome asynchronously, and advances the persisted target turn. An indeterminate outcome requires the explicit user reconciliation described above before a new target turn starts. A follow-up remains durably pending until its matching user message is flushed to the session and the delegated `turn.started` boundary is recorded; a worker-ready event alone does not acknowledge it. Automatic retries, model loops, and compaction continue within the current run; multiple active follow-ups are delivered in order. Only same-worker `turn.start` retries have stable in-memory command deduplication; that deduplication is lost when the worker restarts. Do not retry `turn.cancel`, `agent.shutdown`, or `agent.ping` after an ambiguous outcome. After a worker restart, explicitly reconcile durable results and side effects before retrying non-idempotent work. It never replays the original task. Optional and required completion use the normal auto-subagent update; required state is additionally supplied in the required-update context before the parent can finish. An unknown or ambiguous id is returned as a model-visible tool error; the existing `/subagents` dashboard and final-result notifications remain unchanged. An explicit `--tools` allowlist must include each manager tool that the model needs, and `subagent_status` must not be polled solely to wait for completion. ## Resource policy The persisted `subagents` config object supports `max_concurrent`, `max_concurrent_per_parent`, `queue_timeout`, `startup_timeout`, `default_timeout`, output caps, allowed tools/roots, heartbeat and idle timeouts. Duration strings use Go duration syntax and malformed values fail configuration loading with the field name. A worker fails with an actionable queue-timeout error when no execution slot becomes available before `queue_timeout` (5 minutes by default). Once admitted, it must report `agent_ready` before `startup_timeout` (1 minute by default) or fails with guidance to inspect worker output and retry. Model steps and delegated turns are counted for observability but never capped. A missing or non-positive `default_timeout` leaves each active delegated turn unlimited; a positive value opts into a manager-owned deadline retained across reload and resume. An explicit `subagent_resume` starts a new observable run without replaying the original task or losing session context. Previously saved `max_total_spawned` values are accepted but ignored. Concurrency limits apply to slash commands, `subagent_spawn`, and batch operations. A child cannot create descendants in v1. The shared HTTP streaming providers retry transient request-open failures four times; after opening, a stream may reconnect five times and is canceled after five minutes without an event. Packaged `zut run` agents keep their declared capability ceiling by disabling subagent delegation; profiles are not a substitute for that permission boundary. A primary whose launch-time policy disables delegation still receives the strict orchestrator contract, but does not receive actionable profile metadata or a replacement implementation path.