Tasks

The routine multi-agent path is one model-visible task tool. Its tasks array accepts one focused child or several independent children for concurrent fan-out. Child context stays isolated from the parent conversation, and only compact results return instead of full transcripts.

The same task tool also carries automatic subagent delegation: when enabled, the runtime plans a task call for high-confidence specialist matches before the model's first decision. Host code that already knows the shape of the work can call session.task(...) / session.tasks(...) directly, or use the programmable Orchestration combinators.

Web clients can present these states as a plan and a separate list of child-agent runs, keyed by task ID.

Built-in Subagents

AgentUse it for
exploreRead-only codebase search, file inspection, and structure discovery.
planRead-only implementation plans and architecture analysis.
general / general-purposeMulti-step implementation work with read/write/command access.
verificationFocused checks, reproductions, regression validation, and adversarial testing.
reviewFindings-first code review for correctness, regressions, security, and maintainability.

You can mention them explicitly, for example @review, @agent-plan, use the verification subagent, or delegate to general-purpose.

Manual Delegation

Ask the parent agent to delegate a bounded job:

Text
Use task to ask an explore agent to inspect the auth module.
Return files inspected, findings, risks, and confidence.

If the host already knows the task boundary, call the same core tool directly:

TypeScript
const task = await session.task({
agent: 'explore',
description: 'Inspect auth module',
prompt: 'Return files inspected, findings, risks, and confidence.',
});
if (task.exitCode !== 0) throw new Error(task.output);
console.log(task.output);
Python
task = session.task({
"agent": "explore",
"description": "Inspect auth module",
"prompt": "Return files inspected, findings, risks, and confidence.",
})
if task.exit_code != 0:
raise RuntimeError(task.output)
Go
task, err := session.Task(ctx, code.DelegateTaskOptions{
Agent: "explore",
Description: "Inspect auth module",
Prompt: "Return files inspected, findings, risks, and confidence.",
})
if err == nil && task.ExitCode != 0 {
err = errors.New(task.Output)
}

Each item takes agent, description, and prompt, plus optional background and maxSteps / max_steps. The Go DelegateTaskOptions has no Background field. The model-facing task schema also accepts output_schema, a JSON Schema the child result is coerced into and validated against; the validated object is returned in the tool metadata.

A child agent should return a compact contract:

  • summary
  • files inspected or changed
  • evidence references
  • risks and unknowns
  • confidence

The parent should not ingest the full child transcript.

Parallel Delegation

Use task with several tasks items, or session.tasks(...), when independent work can run concurrently:

Text
Run one task call with three independent tasks:
1. inspect provider config parsing
2. inspect Node SDK declarations
3. inspect release scripts
Merge the results into one release-readiness report.
TypeScript
const batch = await session.tasks([
{
agent: 'explore',
description: 'Inspect config',
prompt: 'Check provider parsing.',
},
{
agent: 'verification',
description: 'Verify SDK',
prompt: 'Check SDK declarations.',
},
]);
if (batch.exitCode !== 0) throw new Error(batch.output);
console.log(batch.output);

The unified task call accepts 1-32 items. A single item may request background; a multi-item call, or any call that sets fan-out options, collects every branch and therefore rejects background: true. By default every branch must succeed. Set allow_partial_failure only for evidence-gathering work that can use incomplete results; the call then succeeds when at least one child succeeds and keeps failed child results in the output. min_success_count requires allow_partial_failure, must be between 1 and the task count, and returns early once that many children succeed (unfinished children are marked failed). The SDK tasks(...) helpers send only the item list; to set these options, call the tool directly, for example session.tool('task', { tasks, allow_partial_failure: true }).

session.task(...) and session.tasks(...) return ToolResult values from the same task tool. Read output for the compact child summary and check exitCode before treating the result as successful. maxParallelTasks in session options and max_parallel_tasks in ACL bound sibling fan-out. Multi-item task is the only fan-out tool; there is no separate parallel-task tool or SDK helper.

Agent-Wide Priority Scheduler

Every Agent owns one scheduler shared by all sessions created from it. The scheduler limits how many independent operations may execute at once and chooses which queued operation receives the next slot. It is backed by the a3s-lane priority queue and is enabled without extra setup.

This is an admission boundary, not a preemptive executor: work that already owns a slot continues until it completes or is cancelled. Priority determines which pending operation starts when a slot becomes available.

What shares the boundary

The same max_active capacity covers:

  • conversation runs started with send, run, or stream
  • trusted or governed direct-tool calls made by the host
  • detached background children
  • workflows started by the host

This prevents several sessions from each consuming an independent concurrency budget. A busy background session cannot bypass interactive work by entering through a different execution API.

Three nearby controls solve different problems:

ControlScope
task_scheduler.max_activeGlobal admission across every session owned by one Agent
max_parallel_tasksSibling fan-out inside one delegated task or workflow
Lane queueOptional external or hybrid worker dispatch

A session's single-flight rule is separate too: two transcript-changing calls on the same session fail fast instead of waiting in this scheduler.

Configure capacity and aging

ACL
task_scheduler {
max_active = 4
aging_interval_ms = 30000
}

Both values must be greater than zero. Defaults are four active operations and a 30-second aging interval.

Choose a priority

PriorityIntended useAging
urgentExplicit host control work that must run nextNever ages
interactiveUser-facing turnsDefault and maximum aged priority
foregroundVisible work that is not blocking direct interactionAges toward interactive
backgroundDetached or asynchronous workAges toward interactive
maintenanceLowest-priority housekeepingAges toward interactive

Lower classes run after higher classes. Equal effective priorities remain FIFO. Every full aging_interval_ms promotes waiting non-urgent work by one level, capped at interactive, so continuous user traffic cannot permanently starve background or maintenance work. urgent remains reserved above aged work.

Set the priority when creating a session:

Rust
use a3s_code_core::{SessionOptions, TaskPriority};
let options = SessionOptions::new()
.with_task_priority(TaskPriority::Background);
let session = agent
.session_builder("/repo")
.options(options)
.build()
.await?;
TypeScript
const session = await agent.sessionAsync('/repo', {
taskPriority: 'background',
});
Python
from a3s_code import SessionOptions
options = SessionOptions()
options.task_priority = "background"
session = agent.session("/repo", options)
Go
session, err := agent.Session(ctx, "/repo", &code.SessionOptions{
TaskPriority: code.TaskPriorityBackground,
})

Accepted names are urgent, interactive (alias user), foreground, background, and maintenance. Invalid names fail session-option validation. Sessions default to interactive.

Observe occupancy

Hosts can read the same point-in-time snapshot through either the Agent or one of its sessions:

Rust
let stats = agent.task_scheduler_stats().await?;
let same_scheduler = session.task_scheduler_stats().await?;
println!("active={} pending={}", stats.active, stats.pending);
TypeScript
const stats = await agent.taskSchedulerStats();
const sameScheduler = await session.taskSchedulerStats();
console.log(stats.active, stats.pendingByPriority.background);
Python
stats = agent.task_scheduler_stats()
same_scheduler = session.task_scheduler_stats()
print(stats["active"], stats["pendingByPriority"]["background"])
Go
stats, err := agent.TaskSchedulerStats(ctx)
sameScheduler, err := session.TaskSchedulerStats(ctx)
fmt.Println(stats.Active, stats.PendingByPriority.Background)
ValueMeaning
maxActiveConfigured global capacity
activeOperations currently holding a slot
pendingOperations waiting for admission
activeByPriorityActive operations grouped by their requested priority
pendingByPriorityWaiting operations grouped by their requested priority
closedThe scheduler is shutting down

Rust exposes snake-case struct fields; Node.js and the Python dictionaries use the camel-case wire names; Go exposes exported struct fields. The snapshot is diagnostic state, not a reservation—values can change immediately after it is read.

For cumulative diagnostics, taskSchedulerHealth() / task_scheduler_health() / TaskSchedulerHealth (on the Agent or a session) adds admitted, released, cancelled, and rejected counts, aging promotions, peak active, and total, average, and maximum wait time.

Cancellation and shutdown

Cancellation removes pending work before it can acquire a slot. Cancelling an active operation releases its slot when that operation settles. Closing the Agent rejects queued and new admissions, then waits for already-admitted work to finish before scheduler shutdown completes.

Automatic Delegation

Automatic delegation is opt-in. At the start of a turn, the runtime scores the request locally against every visible agent's name and description (built-in, directory-loaded, and worker agents; general is only chosen when named). This is a deterministic match, not a model call. When the request names an agent, only that agent is planned. Otherwise agents whose confidence reaches minConfidence become items of one task call that takes the place of the model's first decision in the turn. The fact log records and runs it like any other tool call, including the confirmation policy. The children's compact results return to the parent model as the tool result, and the parent still makes the final decision.

  • At most min(maxTasks, maxParallelTasks) children start per request.
  • autoParallel: false / auto_parallel = false keeps automatic delegation but limits it to the single best match.
  • Defaults: disabled, minConfidence 0.72, maxTasks 4, autoParallel true.
TypeScript
const session = agent.session('/repo', {
autoDelegation: { enabled: true, minConfidence: 0.72, maxTasks: 4 },
maxParallelTasks: 8,
autoParallel: false,
});
ACL
auto_delegation {
enabled = true
auto_parallel = false
min_confidence = 0.72
max_tasks = 4
}

autoParallel never affects manual task fan-out or session.tasks(...).

With automatic delegation disabled, naming an agent in the request (for example @review or "use the verification subagent") still delegates to exactly that agent. To remove the model-visible task tool entirely, set manualDelegationEnabled: false (Node), manual_delegation_enabled = False (Python), ManualDelegationEnabled (Go), or allow_manual_delegation = false inside the ACL auto_delegation block. This also turns off automatic delegation, because both paths run through the task tool.

Agent Directories

Load custom agent definitions through agentDirs, agent_dirs, or the built-in A3S directories:

TypeScript
const session = agent.session('/repo', { agentDirs: ['./.a3s/agents'] });
const loaded = session.registerAgentDir('./more-agents');

Definitions are registered in this order, and a later definition with the same name replaces an earlier one: ACL agent_dirs, ~/.claude/agents, ~/.a3s/agents, <workspace>/.claude/agents, <workspace>/.a3s/agents, session agentDirs, then workerAgents. Directories are scanned recursively for .md, .yaml, and .yml files. Prefer .a3s/agents for new projects; .claude/agents is read for compatibility.

Markdown agent files support frontmatter:

Markdown
---
name: docs-auditor
description: Use proactively after documentation changes
tools: Read, Grep, Glob
disallowedTools:
- Write
- Bash(rm:*)
---
Audit docs for drift, broken examples, and unclear migration notes.

The tools field is an allowlist. disallowedTools is a denylist and wins over allowed tools. Frontmatter may also set hidden, prompt (otherwise the body is the prompt), max_steps, and confirmation_inheritance. Adding a kind field parses the file as a worker spec with that role's default permissions (see Worker Agents). Symlinks that resolve outside the scanned directory are skipped, and files that fail to parse are logged and skipped.

Worker Agents

Register disposable worker agents with workerAgents or registerWorkerAgent():

TypeScript
const session = agent.session('/repo', {
workerAgents: [
{
name: 'frontend-worker',
description: 'Small verified frontend fixes',
kind: 'implementer',
model: 'provider/model-id',
maxSteps: 24,
confirmationInheritance: 'auto_approve',
},
],
});

Confirmation Inheritance

Control how child runs resolve Ask decisions with confirmationInheritance:

  • 'auto_approve': the child's own Ask decisions are approved automatically. This is the default for agents that define permission rules, including worker agents and agent files with tools or disallowedTools.
  • 'deny_on_ask': the child's own Ask decisions are denied, so only explicitly allowed tools run. This is the default for agents without permission rules.
  • 'inherit_parent': the child's Ask decisions go to the parent session's confirmation provider.

The parent session's permission boundary is still composed into every child run.

To track child runs, read subagentTasks() / pendingSubagentTasks() (subagent_tasks() / pending_subagent_tasks() in Python, SubagentTasks / PendingSubagentTasks in Go) and cancel one with cancelSubagentTask(taskId) / cancel_subagent_task(task_id) / CancelSubagentTask.

Programmable orchestration

Everything on this page is model-driven: task, session.task(...) / session.tasks(...), and auto-delegation let the LLM decide when and how to fan out. When the host already knows the shape of the work and wants it to be deterministic and reproducible, express it programmatically instead with session.parallel(...), session.pipeline(...), and session.parallelResumable(...). See Orchestration for developer-expressed fan-out, barrier-free pipelines, and resumable/migratable workflows.