Agent teams & background agents
Three ways to spend a second agent — a synchronous sub-agent, a durable /bg task, and a coordinated /team.
On this page
Arcturn has three ways to spend a second agent, and they solve different problems. The
subagent tool is one-shot and synchronous — the parent asks a
question and waits for an answer. /bg is fire-and-forget and durable — a whole task
runs off-thread, and you check back later. /team is orchestration — several agents move
one goal forward at once, and something is responsible for reconciling their work back
into a single tree. This page covers the latter two.
Background agents: /bg
/bg <task> starts a full child agent running your task as its sole prompt, off the
foreground thread, and returns immediately:
/bg fix the flaky retry test and open a summary of what was wrong
Started background agent bg-a1b2c3d4 (session <sessionId>)./bg(no arguments) — list every background agent this process knows about./bg logs <id>— print the transcript so far./bg cancel <id>— abort it./bg adopt <id>— pull its final result into the live conversation: if the main agent is currently running, the result is queued withsteer(); if idle, it's injected as a fresh prompt viaagent.prompt().
A background agent's first word being literally logs, cancel, or adopt gets parsed
as that sub-verb rather than the start of a new task — a known, accepted ambiguity, the
same shape /cost limit//cost preview already have.
Lifecycle states: "running" | "done" | "failed" | "cancelled" | "interrupted". There
is no separate "queued" state in the type — a queued agent still reports "running";
whether it's actually started or waiting is inferred from startedAt being unset.
"interrupted" specifically means the record was still "running" when a fresh manager
process loaded it from disk: the process that owned it died mid-run.
Where it runs: rooted at the manager's configured cwd (the runtime's cwd by
default). Permission mode defaults to "default", never "yolo" — there is nowhere to
send a permission prompt for an unattended agent, so a non-default mode fails closed by
construction. The default tool set is the read-only tools plus fetch; subagent is
always excluded, so a background agent cannot recursively spawn more background agents or
sub-agents.
Persistence: one JSON record per agent under <paths.home>/background-agents/records/,
written atomically (temp file, then rename), plus a normal session store under
<paths.home>/background-agents/sessions/ — an ordinary JsonlSessionStore, so a
background agent's conversation can be resumed the same way any session can. Up to
3 agents run concurrently by default; the rest queue FIFO.
Agent teams: /team
A team decomposes one goal into disjoint subtasks, dispatches one agent per subtask into
its own throwaway git worktree, and reconciles their work by applying patches back to
your tree — the orchestration case neither subagent nor /bg covers.
The shape of a run
- Decompose. One supervisor turn splits the goal into 2–5 subtasks
(
MIN_TEAM_MEMBERS–MAX_TEAM_MEMBERS), each with an explicit, disjoint file scope. - Validate. This is the load-bearing step: a plan is never dispatched until its file scopes are provably disjoint. Overlapping subtasks trigger exactly one re-ask with the conflicts spelled out; if the second plan still overlaps, the colliding subtasks are merged into one member rather than dispatched — merging loses parallelism, dispatching an overlapping plan loses work.
- Dispatch. One agent per subtask, each rooted in its own
git worktree add --detachcheckout (reusing scouts.ts'screateWorktree— the same isolation primitive scouts use), bounded by a concurrency cap and a per-team cost/turn ceiling. - Capture. When a member settles, its
git diffis staged (git add --all) and written to a patch file on disk, and its worktree is destroyed in afinally— the patch, not the worktree, is the durable work product, so a crash, a cancel, or a conflict can never lose a member's output. - Reconcile.
/team mergereplays those patches into your tree withgit apply, one member at a time, checking each (git apply --check) before applying.
Conflict policy: surface, never guess
git apply runs without --3way and without --force — it refuses a patch whose
context doesn't match rather than overwriting anything. The first refusal stops the
merge: it reports which members landed and which didn't, and leaves the offending
patch file on disk. Arcturn does not attempt a clever auto-merge and does not write
conflict markers into your tree. You choose: git apply --3way <patch>, re-run the
member, or /team discard. Members already applied stay applied and are recorded as
merged, so re-running /team merge resumes rather than double-applying.
Roles
Four built-in roles, resolved case-insensitively, a host-defined role of the same name (e.g. from a markdown named agent) winning over the built-in:
| Role | Tools | What it does |
|---|---|---|
implementer |
unrestricted | Writes the production change for one file scope. |
tester |
unrestricted | Writes and runs tests; told not to edit production code to make a test pass. |
reviewer |
read-only | Reads and critiques; cannot modify anything. |
documenter |
unrestricted | Updates prose/doc comments; told not to change behavior. |
reviewer is deliberately read-only: a reviewer that can edit stops being a second
opinion and becomes a fourth author racing the others for the same lines. The default
role, when a plan names none or names one nobody can resolve, is implementer.
Command syntax
/team <goal> decompose, dispatch, show the report
/team --roles implementer,tester,reviewer <goal> pin the specialists, in order
/team status [id] list teams, or one team's report
/team cancel [id] abort every member, clean up worktrees
/team merge [id] apply members' patches to your tree
/team discard [id] throw the members' patches awayFlags accepted before a goal or sub-command: --roles a,b,c, --members <n>,
--max-cost <usd> (also $<usd>), --parallel <n> (or --concurrency <n>). Omitting
[id] on a sub-command targets the most recently created team.
Defaults
| Constant | Value | Meaning |
|---|---|---|
DEFAULT_TEAM_CONCURRENCY |
3 | Members running at once; the rest queue. |
DEFAULT_TEAM_MAX_COST_USD |
5 | Per-team spend ceiling; exceeding it cancels every remaining member. 0 disables it. |
DEFAULT_TEAM_MAX_TURNS |
40 | Turn ceiling for one member. |
MIN_TEAM_MEMBERS / MAX_TEAM_MEMBERS |
2 / 5 | Bounds on a decomposition; beyond either, the plan is re-asked. |
Hitting the cost ceiling mid-run cuts off every remaining member but does not discard work already captured — patches already on disk from members that finished first are still mergeable, and the team's status/report says so.
Team status values
"planning" | "running" | "review" | "merged" | "discarded" | "cancelled" | "failed" | "interrupted". "review" is the interesting one: every member has settled and its
patch is on disk, but nothing has touched your tree yet — a team sits in "review"
until you run /team merge or /team discard.
Durability and recovery
Every record is written synchronously to <dir>/records on each status change, mirroring
/bg's durability model. A record still "running" when a fresh manager loads it
belongs to a process that is gone — TeamManager.recover() salvages it: each member's
surviving worktree has its diff captured to a patch (so a crash costs no work) before the
worktree is removed, and git worktree prune clears any leftover administrative entry.
Recovery is idempotent and runs automatically at the start of every /team command.
Cost accounting
A team's spend happens outside the main agent's own event stream — without explicit
accounting, /cost and --max-cost would silently under-report a whole team's spend.
/team <goal> folds each member's summed cost into the parent session's running total
via runtime.recordExternalCost(status.costUsd) once the run settles.
Choosing between them
subagent— you need an answer to fold back into the current turn, right now, and the work is small enough for one focused pass./bg— the work is bigger, doesn't need supervision, and you want to keep working on something else while it runs; check back with/bg logs//bg adoptwhen it's done./team— the goal genuinely decomposes into independent pieces (separate files, separate concerns) that benefit from running in parallel, and you're willing to review and merge patches rather than get one linear diff.
Related
- Sub-agents, plan mode & todos — the synchronous
subagenttool and named agents from markdown, which teams reuse for role definitions. - Scouts — the throwaway-worktree exploration primitive
/teamreuses for member isolation; scouts never produce a merge-ready patch, teams always do. - Model providers — the
subagentroute both background agents and team members resolve their model through by default.