18 KiB
18 KiB
task
Launch subagents for parallel, optionally isolated work.
Source
- Entry:
packages/coding-agent/src/task/index.ts - Model-facing prompt:
packages/coding-agent/src/prompts/tools/task.md - Key collaborators:
packages/coding-agent/src/task/types.ts— dynamic schema, progress/result types, output caps.packages/coding-agent/src/task/discovery.ts— discover project/user/plugin/bundled agents.packages/coding-agent/src/task/agents.ts— bundled agent definitions and frontmatter parsing.packages/coding-agent/src/task/executor.ts— create child sessions, run subagents, collect output.packages/coding-agent/src/task/parallel.ts— concurrency-limited scheduling and async semaphore.packages/coding-agent/src/task/isolation-backend.ts— isolation backend resolution and platform fallback.packages/coding-agent/src/task/worktree.ts— worktree / FUSE / ProjFS setup, patch capture, branch merge.packages/coding-agent/src/task/output-manager.ts— session-scopedagent://id allocation.packages/coding-agent/src/task/simple-mode.ts—default/schema-free/independentfield gating.packages/coding-agent/src/internal-urls/agent-protocol.ts— resolveagent://<id>to saved subagent output.packages/coding-agent/src/tools/index.ts— tool registration and recursion-depth gating.packages/coding-agent/src/sdk.ts— child-session router/tool wiring and per-subagentAgentOutputManager.docs/task-agent-discovery.md— deeper discovery and precedence notes.docs/handoff-generation-pipeline.md— session artifact/handoff persistence patterns used by the wider session layer.
Inputs
Default mode (task.simple = "default")
| Field | Type | Required | Description |
|---|---|---|---|
agent |
string |
Yes | Exact agent name for every task item. Resolved at execution time through discoverAgents(...). |
tasks |
Array<{ id: string; description: string; assignment: string }> |
Yes | Batch of small, self-contained task items. id max length 48 in schema; duplicate ids are rejected case-insensitively at runtime. |
context |
string |
No | Shared background prepended to every subagent system prompt. Trimmed before use. |
schema |
string |
No | JSON-encoded JTD schema. Overrides agent/session output schema when this mode allows task-level schemas. |
isolated |
boolean |
No | Only present when the tool is created with isolation enabled. Requests isolated execution for the whole batch. |
tasks[].description is UI-only. tasks[].assignment is the actual per-task instruction.
Schema-free mode (task.simple = "schema-free")
Same as default, except schema is rejected by validateTaskModeParams(...) in packages/coding-agent/src/task/index.ts.
Independent mode (task.simple = "independent")
| Field | Type | Required | Description |
|---|---|---|---|
agent |
string |
Yes | Exact agent name. |
tasks |
Array<{ id: string; description: string; assignment: string }> |
Yes | Same item shape, but each assignment must carry all required background because shared context is disabled. |
isolated |
boolean |
No | Same conditional field as above. |
In this mode both context and schema are rejected.
Outputs
The tool returns one text block plus details: TaskToolDetails.
details fields:
projectAgentsDir: string | null— nearest discovered projectagents/dir.results: SingleResult[]— one entry per task in input order for synchronous execution; empty for async-launch responses.totalDurationMs: numberusage?: Usage— sum of per-subagent assistant-message usage.outputPaths?: string[]— written.mdartifact paths for completed subagent outputs.progress?: AgentProgress[]— live or final per-task progress snapshots.async?: { state: "running" | "completed" | "failed"; jobId: string; type: "task" }— present for background execution updates/results.
SingleResult includes:
- identity:
index,id,agent,agentSource,description, optionalassignment - status:
exitCode, optionalerror, optionalaborted, optionalabortReason - output:
output,stderr,truncated,durationMs,tokens - artifact metadata:
outputPath?,patchPath?,branchName?,nestedPatches?,outputMeta? - extracted tool data:
extractedToolData?from registered subprocess tool handlers such asyieldandreport_finding
Artifacts and side channels:
- Every subagent with an artifacts dir writes
<id>.md;agent://<id>resolves to that file. - If the output file is JSON,
agent://<id>/<path>andagent://<id>?q=<query>perform JSON extraction inpackages/coding-agent/src/internal-urls/agent-protocol.ts. - When the parent session persists artifacts, each subagent also gets
<id>.jsonlsession history. - Isolated patch mode writes
<id>.patchper successful task before merge. - Async mode returns immediately after job registration, then emits
onUpdate(...)progress snapshots and later hands completion to the session async-job pipeline.
Flow
TaskTool.create(...)inpackages/coding-agent/src/task/index.tscallsdiscoverAgents(session.cwd)once to build the dynamic prompt description from current agents andtask.simplecapabilities.execute(...)validates mode-gated fields withvalidateTaskModeParams(...).- It decides async vs sync:
- sync when
async.enabledis false - sync when the selected cached agent has
blocking === true - sync when
tasks.length === 0 - otherwise async job scheduling
- sync when
- Async path:
- allocate unique output ids with
AgentOutputManager.allocateBatch(...) - create one async job per task through
session.asyncJobManager.register(...) - limit concurrent job bodies with
Semaphore(task.maxConcurrency)frompackages/coding-agent/src/task/parallel.ts - each job body calls
#executeSync(...)with a one-task batch and the preallocated id onUpdate(...)emits aggregateprogresssnapshots anddetails.async
- allocate unique output ids with
- Sync path (
#executeSync(...)) rediscovers agents from disk viadiscoverAgents(...), so runtime resolution can differ from the earlier prompt description. - It resolves the requested agent with
getAgent(...), rejects unknown or disabled agents, and enforces parent spawn policy plusPI_BLOCKED_AGENTself-recursion prevention. - It derives the effective output schema in priority order: task call
schema(if allowed) → agent frontmatteroutput→ inherited parent session schema. - It validates task ids: missing ids and case-insensitive duplicates are immediate errors.
- If
isolatedwas requested, it requires a git repo (getRepoRoot(...)/captureBaseline(...)) and resolves the actual backend throughresolveIsolationBackendForTaskExecution(...). - It chooses an artifacts dir from the parent session when available, otherwise a temp dir, and writes
context.mdthere whensession.getCompactContext?.()returns content. - It allocates unique ids again if the caller did not preallocate them, then builds
tasksWithUniqueIds. - For each task, it seeds an
AgentProgressentry and runsrunTask(...)throughmapWithConcurrencyLimit(...)usingtask.maxConcurrency. - Non-isolated
runTask(...)callsrunSubprocess(...)directly with parent cwd. - Isolated
runTask(...):
- creates an isolation workspace (
ensureWorktree(...),ensureFuseOverlay(...), orensureProjfsOverlay(...)) - applies the captured baseline for worktrees
- runs
runSubprocess(...)inside that workspace - on success, either commits to a per-task branch (
mergeMode === "branch") or captures a patch withcaptureDeltaPatch(...) - always cleans up the isolation workspace/backend
runSubprocess(...)inpackages/coding-agent/src/task/executor.tscreates a child agent session with:
- isolated settings snapshot via
Settings.isolated(...), forcingasync.enabled = falseandbash.autoBackground.enabled = false - child
agentId/parentTaskPrefixequal to the allocated task id - child internal URL router and
AgentOutputManagerfrompackages/coding-agent/src/sdk.ts - the shared
context, optionalcontext.mdreference, optional isolation worktree path, output schema, and IRC peer roster in the system prompt template
- Child tool availability is derived from the agent definition plus runtime guards:
- explicit
agent.toolsif provided - auto-add
taskwhen the agent hasspawnsand recursion depth allows it - remove
taskat or pasttask.maxRecursionDepth - expand
exectoevalandbash - strip parent-owned
todo_writeafter session creation
runSubprocess(...)subscribes to child agent events, coalesces progress updates every 150 ms, forwards lifecycle/progress events on the parent event bus, and extracts tool data throughsubprocessToolRegistry.- The child must finish through the hidden
yieldtool. If it does not,runSubprocess(...)sends up to 3 reminder prompts; the last reminder forcestoolChoice = yieldwhen supported. - Finalization uses
finalizeSubprocessOutput(...)to reconcile raw assistant text,yieldpayloads, structured schemas,report_findingdata, and abort states. Output is truncated withMAX_OUTPUT_BYTES/MAX_OUTPUT_LINESbefore returning to the parent, but the full raw output is still written to<id>.md. - After all sync tasks finish,
#executeSync(...)aggregates usage, collects artifact paths, and if isolation was used merges results back:
- branch mode: cherry-pick per-task branches with
mergeTaskBranches(...), then delete merged branches withcleanupTaskBranches(...) - patch mode: combine non-empty patch artifacts, dry-check with
git.patch.canApplyText(...), then apply or leave manual artifacts - nested repo patches are applied separately with
applyNestedPatches(...)
- The final text summary is rendered from
packages/coding-agent/src/prompts/tools/task-summary.mdand includesagent://<id>handles for outputs that exist.
Modes / Variants
- Execution mode
- Sync inline execution — default path.
- Async background execution — one async job per task item when
async.enabledis on and the chosen agent is not markedblocking.
- Simple mode
default— accepts sharedcontextand per-callschema.schema-free— acceptscontext, rejectsschema.independent— rejectscontextandschema; each assignment stands alone.
- Isolation backend
none— no isolation.worktree— detached git worktree plus baseline replay.fuse-overlay— Unix FUSE overlay mount.fuse-projfs— Windows ProjFS overlay.
- Isolation merge strategy
- Patch mode — capture/apply root patches, keep patch artifacts when application fails.
- Branch mode — commit each task onto
omp/task/<id>branch, cherry-pick into parent, preserve failed branches for manual resolution.
- Agent source
- Project custom agents — nearest project config/plugin agent directories, first by source-family precedence.
- User custom agents — user config/plugin agent directories after project dirs of the same source family.
- Bundled agents — appended last from
packages/coding-agent/src/task/agents.ts.
- Bundled agent types
explore— read-only scout with structured handoff output.plan— architecture/planning agent; may spawnexplore.designer— UI/UX specialist.reviewer— review agent withreport_findingextraction.task— general-purpose worker with full capabilities.quick_task— low-reasoning mechanical worker using the same task prompt body.librarian— source-grounded external API/library researcher.
Side Effects
- Filesystem
- Writes
context.md,<id>.jsonl, and<id>.mdunder the session artifacts dir or a temp task dir. - In isolated patch mode writes
<id>.patchartifacts. - Creates/removes worktrees or overlay mount directories.
- In branch mode creates temporary worktrees and task branches.
- Writes
- Network
- Child sessions may use whichever networked tools/models their active tool set permits.
- MCP proxy tools can call existing parent MCP connections with a 60_000 ms timeout.
- Subprocesses / native bindings
fuse-overlayfsandfusermount/fusermount3for FUSE isolation.- ProjFS native bindings via
@oh-my-pi/pi-nativeson Windows. - Git operations for baseline capture, patch apply, worktrees, branches, stash, cherry-pick, commits.
- Session state (transcript, memory, jobs, checkpoints, registries)
- Creates child
AgentSessioninstances with isolated settings snapshots. - Registers async jobs in
session.asyncJobManagerfor background task mode. - Emits
task:subagent:event,task:subagent:progress, andtask:subagent:lifecycleon the parent event bus. - Allocates session-scoped output ids through
AgentOutputManagersoagent://remains unique across invocations and resumes. - Shares the parent
local://root with subagents by passinglocalProtocolOptionsthroughcreateAgentSession(...).
- Creates child
- User-visible prompts / interactive UI
- Async mode streams aggregate progress updates.
- Missing-
yieldrecovery sends up to three internal reminder prompts to the child session. - Final summaries include
<system-notification>blocks for isolation fallbacks or merge failures.
- Background work / cancellation
- Parent abort stops scheduling new work, aborts active child sessions, and marks unscheduled tasks as skipped.
- Async jobs keep their own cancellation via
AsyncJobManager.
Limits & Caps
- Per-subagent output truncation:
MAX_OUTPUT_BYTES = 500_000andMAX_OUTPUT_LINES = 5000inpackages/coding-agent/src/task/types.ts. Full raw output is still written to<id>.mdbefore truncation is returned to the caller. - Progress coalescing in child execution:
PROGRESS_COALESCE_MS = 150inpackages/coding-agent/src/task/executor.ts. - Recent output tail for progress:
RECENT_OUTPUT_TAIL_BYTES = 8 * 1024andrecentOutputkeeps the last 8 non-empty lines inpackages/coding-agent/src/task/executor.ts. - Missing-
yieldreminder retries:MAX_YIELD_RETRIES = 3inpackages/coding-agent/src/task/executor.ts. - MCP proxy timeout:
MCP_CALL_TIMEOUT_MS = 60_000inpackages/coding-agent/src/task/executor.ts. - Task id schema cap:
tasks[].idmaxLength: 48inpackages/coding-agent/src/task/types.ts. - Prompt text says ids should be
≤32chars, but the runtime schema allows 48; this mismatch is real. - Async/full sync parallelism both use
task.maxConcurrencyfrom settings:- sync path:
mapWithConcurrencyLimit(...) - async path:
Semaphore(...)around job bodies
- sync path:
- Recursion depth gate:
task.maxRecursionDepthfrom settings;packages/coding-agent/src/tools/index.tshides thetasktool at or beyond the limit, andrunSubprocess(...)also strips childtaskaccess at max depth. - Final inline summary preview per task uses
fullOutputThreshold = 5000chars inpackages/coding-agent/src/task/index.ts; longer outputs are summarized whileagent://<id>points to the full artifact.
Errors
- Most validation failures are returned as normal tool text with empty
results, not thrown:- invalid simple-mode fields
- unknown/disabled agent
- missing tasks
- missing/duplicate task ids
- spawn-policy denial
- requesting
isolatedwhile isolation mode isnone
- Isolated execution without a git repo returns
Isolated task execution requires a git repository. .... - Backend resolution can return a hard error (
ProjFS isolation initialization failed...) or a non-fatal warning with fallback toworktree. mapWithConcurrencyLimit(...)fails fast on non-abort worker exceptions; already completed results are preserved only in the thrown path’s local state, not surfaced unless the caller catches and converts them.- Child-session failures surface as
SingleResult.exitCode = 1withstderr/errorpopulated. - If the child omits
yield,finalizeSubprocessOutput(...)injects warnings such asSYSTEM WARNING: Subagent exited without calling yield tool after 3 reminders. - Async scheduling failures are accumulated per task; if no jobs start, the tool returns
Failed to start background task jobs: .... agent://<id>resolution errors are model-visible when another tool reads them: no session, no artifacts dir, missing id, conflicting extraction syntax, or invalid JSON for extraction.
Notes
- Agent discovery precedence is first-wins by exact name: project dirs before user dirs within a source family, plugin agent dirs after config dirs, bundled agents last. See
packages/coding-agent/src/task/discovery.tsanddocs/task-agent-discovery.md. TaskTool.create(...)caches discovered agents only for description rendering and the async blocking-agent decision.#executeSync(...)rediscovers agents each call.- Custom agent frontmatter can override bundled agents by name. Bundled definitions are embedded at build time in
packages/coding-agent/src/task/agents.ts. - Child sessions do not inherit conversation history automatically. The only built-in carry-over is shared
context, optionalcontext.md, workspace tree/skills/context files, and sharedlocal://root. Settings.isolated(...)gives each child a session-isolated settings snapshot; tool enablement is recomputed inside the child session rather than sharing mutable parent tool state.- When the parent passes
mcpManager, child sessions disable standalone MCP discovery and instead get proxy tools that reuse the parent connections. - Plan mode mutates an
effectiveAgentwith a read-only tool subset and plan-mode prompt text, butrunSubprocess(...)is still invoked withagentrather thaneffectiveAgent. Model/thinking/schema overrides use the effective agent; prompt/tool/spawn restrictions do not fully flow through this call path. - Branch-mode merge temporarily stashes the parent repo before cherry-picking task branches. A stash-pop conflict is treated as merge failure and leaves recovery state behind.
- Patch-mode only applies combined root patches if every successful task produced a patch and
git.patch.canApplyText(...)succeeds. - Nested git repos are handled separately from the root repo. They are copied into isolated worktrees, diffed independently, and merged later with
applyNestedPatches(...)because parent git cannot track their file-level changes. agent://ids are numeric-prefixed (0-Task,1-Task, nested like0-Parent.0-Child) byAgentOutputManager; this is what prevents artifact collisions across repeated or nested task invocations.