Documentation

team up

Starts the team: the herdr session when it is not up, a workspace and a seat for every seat the file declares and this machine can run, and the watchdog pane that runs team watch. Seats already ready are left as they are; a seat stopped in the file is left out until team add starts it. --dry-run prints the plan and runs nothing.

A session stopped in herdr is refused until its owner clears it — up never deletes a session. A session team down stopped needs no such step: down clears the one it stopped in the same run, so the next up starts from the beginning.

A seat that isn't mode: shared and works in worktrees (workspace.mode: worktree is the default) starts in the lobby, never in the project root, which holds the owner's uncommitted work. The lobby is the folder that holds the worktrees with .lobby beside them — the parent of workspace.path — so it lies in the same trust as the worktrees and outside every protected checkout. up makes it once, before the first such seat waits in it, and refuses when it would fall outside trust or inside a protected checkout; a folder is inside one when it is inside it on disk too, symlinks resolved. A mode: shared seat, and a seat the file gives a cwd of its own outside every protected checkout, starts where the file says.

The lobby is a folder no CLI has seen before, and up reads a trust question and never answers one: the first up in worktree mode leaves each implementer out with <seat>: left out: trust question — its workspace closed without input, nothing run — until the owner trusts the lobby once in that CLI, as they trusted the worktrees. Then it starts.

Synopsis

team up [--dry-run] [--session <name>] [--file <path>]

What it reads and writes

Reads the team file (or the one --file names), this machine's approval store, the session's state (.agents/team.state.json, for each seat's stage), herdr (whether the session is up, its agents and workspaces), the doctor's findings, and the machine's load, free memory, free disk and free swap. Writes .agents/team.state.json (each seat's stage, pane, workspace and the CLI it was launched with; the watch's pid and heartbeat), .agents/team.log, the lobby folder a seat that works in worktrees waits in, and, through herdr: the server, one workspace per seat and one for the watchdog, each seat's launch, and the watch.

Who may run it

The owner, from a terminal outside herdr. --dry-run is open to anyone: it reaches nothing and changes nothing, prints the refusals it would hit as ! up would refuse: … above the plan, and exits 0.

Flags

FlagMeaning
--dry-runprint the plan, and the refusals the real run would stop on, and exit 0
--session <name>the herdr session to start, instead of team.session
--file <path>the team file, instead of .agents/team.yaml
--help, -hthe usage, and exit 0

What it prints

claude-keeper: ready
  skip claude-qa: stopped in the file; start it with `team add claude-qa`
watch: started

A seat that reaches its idle prompt with its rules delivered prints <seat>: ready. A seat already ready, stopped in the file, or on a CLI with no launch profile prints one skip line and is left as it is. The watch prints watch: started.

A seat that doesn't get there is printed once with what stopped it, and up exits 1:

LineMeaning
<seat>: left out: trust questionthe CLI asked whether to trust the folder; its workspace was closed without an answer
<seat>: permission; its workspace was closed without input and the seat left outa permission dialog, or a question, was left for its owner to answer
<seat>: timed out waiting for its idle prompt; left at launchedthe prompt never came
<seat>: was not in the agent list in time; left at launchedherdr listed no agent in the pane to name
<seat>: its rules were not delivered; left at namedthe rules did not reach an empty idle prompt
<seat>: its workspace was not created; left at launchedherdr made no workspace for it
<seat>: its lobby folder was not created; left outthe folder a seat that works in worktrees waits in could not be made
<seat>: its launch command did not run; left at launchedthe pane took no command
<seat>: the approval allows 3 seats; 4 would be runningthe approval's ceiling, from the record, not the file
<seat>: refused: <account> <window> left <n>%, inside its <reserve>% reserve, changed <age> ago; accounts with room: <accounts>a counted reading is inside that account's reserve and this run would launch the seat; this seat is not started, and the others still are. accounts with room: none when no other account has room
<seat>: refused: <account> spend <amount> <CUR>, at or below its <floor> <CUR> floor, read <age> ago; accounts with room: <accounts>the money its check counted is at or below the account's floor, and this run would launch the seat; this seat is not started, and the others still are
<seat>: <account> <window> left <n>%, inside its <reserve>% reserve, changed <age> ago; accounts with room: <accounts>the same reading, and the seat is already running; setup continues and the line is only a notice
<seat>: <account> spend <amount> <CUR>, at or below its <floor> <CUR> floor, read <age> ago; accounts with room: <accounts>the same money reading, and the seat is already running; setup continues and the line is only a notice
<seat>: <account> is unknownthe account is in the file and its figure is unknown — a subscription with no counted reading, or a spend account whose money reading is missing, older than budgets.stale_after, or in another currency than the floor's. A subscription figure with no known reset is unknown while it could still matter — inside its reserve, or within the reserve again outside it; further out — more than the reserve again — it counts, the room the figure last held, and the launch decision is clear: no unknown line. The seat still starts
<seat>: <account>: first sight only, not yet countedthe account's only readings are unconfirmed; the seat still starts

<account> in these lines is the seat's own account: its account: when the file names one, its vendor when it doesn't.

The plan a --dry-run prints is also what team down --dry-run prints: + <command> for a command that would run, a (<note>) line under one that carries a note, wait <text> for a wait, skip <text> for a seat left out, and dry run: nothing was run at the end. A seat a stored reading would refuse, and that this run would launch, is skip <seat>: would refuse: … and is not in the commands. The words after would refuse: are the same as the words after refused:. A seat already running keeps its setup, with that reading in a note. A seat whose account is unknown carries (<account> is unknown; would launch) under its first command. A first sight carries (<account>: first sight only, not yet counted; would launch). A launch carries the seat's rules, so its line is long. Rules delivered as a launch option (claude-code) close with These are standing rules, not a task.; rules typed as a first message (codex, cursor and antigravity) close with These are standing rules, not a task: reply ready and wait for your brief.

Refusals

A real run stops before the first step, prints one team up: <reason> per reason and exits 1:

Reason
only the owner runs `up`, from a terminal outside herdr; this call is <caller>
the file was never approved on this machine: run `team approve`
the file is not the approved one (<differences>): run `team approve`
a MISS finding from team doctor — herdr or a CLI not installed, a CLI not logged in, a launch naming another model than the file
the load is 1.2 per core, above 1 / free memory is 8%, below 25% / free disk is 3.0 GB, below 10.0 GB / free swap is 1.0 GB, below 2.0 GB
herdr doesn't answer
session beacon is stopped; clear it with `herdr session delete beacon`
session beacon has 2 agents this file's state doesn't record: `up` never touches a running team
the lobby ../worktrees/beacon/.lobby matches no trust pattern (., ../worktrees/beacon/task): add one that covers it and run `team approve`
seat beacon-qa would start in live, inside the protected checkout live; a seat that isn't `mode: shared` never starts in one — the folder the file gives it, or its lobby, is a protected checkout

A watch that has not run, or whose heartbeat is old, is not a reason to refuse: up starts the watch itself.

Exit codes

  • 0 — every seat is ready and the watch is running; or --dry-run printed its plan.
  • 1 — the run was refused, or a seat was left out, or the server or the watch failed.
  • 2 — the invocation or the team file can't be read.

Examples

.agents/team.yaml
format: 1
project: beacon
coordinator: claude-keeper
operator: claude-keeper

workspace:
  mode: shared

seats:
  - role: coordinator
    name: claude-keeper
    label: coordinator
    cli: claude-code
    vendor: anthropic
    model: Claude Opus
    version: "5.5"
    launch: claude --model claude-opus-5-5

  - role: implementer
    name: claude-beacon
    label: implementer
    cli: claude-code
    vendor: anthropic
    model: Claude Opus
    version: "5.5"
    launch: claude --model claude-opus-5-5

  - role: reviewer
    name: claude-qa
    label: reviewer
    cli: claude-code
    vendor: anthropic
    model: Claude Opus
    version: "5.5"
    launch: claude --model claude-opus-5-5
    stopped: true

Two seats are up and the third is stopped in the file, so the plan is short — the skipped seats, and the watchdog pane this session has never had:

Terminal
 team up --dry-run ; echo "exit $?"
  skip claude-keeper: already ready; left as it is
  skip claude-beacon: already ready; left as it is
  skip claude-qa: stopped in the file; start it with `team add claude-qa`
+ herdr --session beacon workspace create --cwd . --label watchdog --no-focus
+ herdr --session beacon pane run <pane of watchdog> 'team watch --session beacon'
    (a desktop notification follows when the watch exits)
dry run: nothing was run
exit 0

The same command for real runs exactly that:

Terminal
 team up ; echo "exit $?"
  skip claude-keeper: already ready; left as it is
  skip claude-beacon: already ready; left as it is
  skip claude-qa: stopped in the file; start it with `team add claude-qa`
watch: started
exit 0

A seat is not the owner, so it can read a plan but not start a team:

Terminal
 team up ; echo "exit $?"
team up: only the owner runs `up`, from a terminal outside herdr; this call is claude-beacon
exit 1

A session stopped in herdr is never started over — up deletes nothing, and its refusal says the command to run by hand. A session team down stopped never gets here: down clears the one it stopped in the same run, so the next up finds no session and starts it from the beginning:

Terminal
 team up ; echo "exit $?"
team up: session beacon is stopped; clear it with `herdr session delete beacon`
exit 1

An owner section changed in the file needs a new approval, and the plan says so before the run:

.agents/team.yaml
format: 1
project: beacon
coordinator: claude-keeper
operator: claude-keeper

limits:
  seats: 4

workspace:
  mode: shared

seats:
  - role: coordinator
    name: claude-keeper
    label: coordinator
    cli: claude-code
    vendor: anthropic
    model: Claude Opus
    version: "5.5"
    launch: claude --model claude-opus-5-5

  - role: implementer
    name: claude-beacon
    label: implementer
    cli: claude-code
    vendor: anthropic
    model: Claude Opus
    version: "5.5"
    launch: claude --model claude-opus-5-5

  - role: reviewer
    name: claude-qa
    label: reviewer
    cli: claude-code
    vendor: anthropic
    model: Claude Opus
    version: "5.5"
    launch: claude --model claude-opus-5-5
    stopped: true
Terminal
 team up --dry-run ; echo "exit $?"
! up would refuse: the file is not the approved one (`limits` changed): run `team approve`
! up would refuse: run `team approve`: `limits` changed
  skip claude-keeper: already ready; left as it is
  skip claude-beacon: already ready; left as it is
  skip claude-qa: stopped in the file; start it with `team add claude-qa`
dry run: nothing was run
exit 0

The real run goes no further than the refusal — it never touches a team the file doesn't match:

Terminal
 team up ; echo "exit $?"
team up: the file is not the approved one (`limits` changed): run `team approve`
team up: run `team approve`: `limits` changed
exit 1