The team file

A documented subset of YAML, read by the library's own parser: maps, lists, one-line { } and [ ], plain and quoted values, comments. Anchors, aliases, tags, block scalars, several documents in one file and duplicate keys are refused, with the line number. The file starts with format: 1. The package ships the JSON Schema at schema/team.schema.json, and team init writes a # yaml-language-server: $schema=… line at the top of the file so editors validate it. By example:

team.yaml
format: 1                     # the only format this version reads
project: hello
coordinator: coordinator      # the seat that dispatches work
operator: coordinator         # the seat the watch reports to

identity:
  signature:
    commits:
      position: trailer       # last-line | trailer | anywhere
      exempt: [merge]         # merge commits need no signature

rules:                        # lines added to every seat's rules at launch. 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, antigravity) close with
                              # "These are standing rules, not a task: reply ready and wait for your brief."
  - Run the tests your change touches, not the whole suite.

workspace:
  mode: shared                # shared | worktree: the default for every seat

seats:
  - role: coordinator
    name: coordinator
    cli: claude-code          # the launch profile
    vendor: anthropic         # the model's maker
    model: Claude Opus        # the model's name, without its version
    version: "5.5"            # the release alone, quoted
    launch: claude --model claude-opus-5-5   # no approval flags: the profile adds them

  - role: implementer
    name: implementer
    cli: codex
    vendor: openai
    account: openai-hello     # the seat's account, when one lab has two; absent, its lab
    model: GPT Sol
    version: "6"
    display: GPT-6 Sol        # the lab's spelling, for the signature
    launch: codex -m gpt-6-sol -c model_reasoning_effort=high
    parked: true              # running, and not reported while idle

  - role: implementer
    name: implementer-deepseek
    cli: claude-code          # DeepSeek's model, run by Claude Code
    vendor: deepseek
    model: DeepSeek Flash
    version: "V4.1"
    display: DeepSeek V4.1 Flash
    launch: team-deepseek     # a launcher on the PATH, holding the account's key and endpoint
    model_from: launcher      # optional: says outright the launcher picks the model; doctor notes
                              # it and says what checks the model — here nothing can, Claude Code's
                              # screen never names DeepSeek's
    count: 2                  # implementer-deepseek and implementer-deepseek-2

  - role: reviewer
    name: reviewer
    cli: grok
    vendor: xai
    model: Grok
    version: "4.7"
    launch: grok --model grok-4.7
    stopped: true             # kept in the file; `up` doesn't start it

budgets:                      # the owner's: reserve or floor per account, marks, freshness
  accounts:
    openai-hello:             # the account implementer spends
      kind: subscription
      reserve: 10%            # refuse a launch on a figure inside it
      sources: [status_line]  # the figure comes off Codex's status line

The head

examples/team.yaml
format: 1
  • coordinator and operator name seats: the coordinator dispatches work, the operator receives the watch's reports and nudges.
  • session names the herdr session and defaults to project; --session overrides it.

identity

examples/team.yaml
identity:
  signature:
    commits:
      position: trailer       # last-line | trailer | anywhere
      exempt: [merge]
    pull_requests:
      position: last-line
      template: "**Agent:** {display} · {role}"
  • identity.signature is the rule check enforces: a template, where it must stand, and which commits are exempt. Commit signatures read Agent: {display} · {role}, pull request bodies **Agent:** {display} · {role}. Without display, the signature reads "model version"; with it, the lab's own spelling. identity.since skips an older history, identity.humans lists commit authors who don't sign, and identity.forbidden adds to the defaults — ^Claude-Session: lines and session links are always refused.

workspace

examples/team.yaml
workspace:
  mode: shared                # every seat works in the project root
  • workspace.mode is shared (every seat in the project) or worktree (each task in its own checkout, with path, base and setup). Every seat starts in ~/.config/team/lobby, which trust lists as an absolute path along with the project root. up and add refuse a seat whose own folder is a protected checkout, and a legacy trust (project-relative patterns, or none) cannot launch until it is rewritten as those absolute paths and approved.

machine

examples/team.yaml
machine:                      # checked before each seat is launched, and by the watch
  load_start: 2.0             # 1-minute load per core above which `up` and `add` refuse
  load_max: 8.0               # per core, above which the watch reports
  memory_start: 30%           # free memory below which `up` and `add` refuse
  memory_min: 10%             # below which the watch reports
  disk_min: 20GB              # free on the project's volume; both refuse and report

seats

examples/team.yaml
seats:
  - role: coordinator
    name: claude-keeper
    label: coordinator
    cli: claude-code
    vendor: anthropic
    model: Claude Nova
    version: "2"
    launch: claude

  - role: operator
    name: claude-signal
    label: operator
    cli: claude-code
    vendor: anthropic
    model: Claude Nova
    version: "2"
    launch: claude

  - role: implementer
    name: codex-beacon
    label: codex
    cli: codex
    vendor: openai
    account: openai-team       # the seat's account, when one vendor has two; absent, its vendor
    model: GPT Comet
    version: "3"
    display: GPT-3 Comet       # the vendor's spelling, for the signature
    launch: codex -m gpt-comet-3
    parked: true               # running, and not reported while idle

  - role: implementer
    name: cursor-beacon
    label: cursor
    cli: cursor
    vendor: meridian
    model: Meridian
    version: "1"
    launch: cursor-agent       # the model is chosen inside Cursor

  # three seats from one entry: nimbus-beacon, nimbus-beacon-2 and -3
  - role: implementer
    name: nimbus-beacon
    label: nimbus
    cli: claude-code           # the vendor's model, run by Claude Code
    vendor: nimbus
    model: Nimbus
    version: "1"
    launch: nimbus-claude      # a launcher on the PATH, holding the account's key and endpoint
    count: 3
  • seats[*].cli picks the launch profile; claude-code, codex, cursor and antigravity are available, and team doctor says what the others still need. vendor, model and version spell one seat's model. account names the budget account the seat spends when one lab has two; without it, the seat spends its vendor, and changing either is an edit the owner re-approves.
  • launch is the plain command, without approval flags: the profile adds them. It runs in the folder the seat starts in — ~/.config/team/lobby — and team never rewrites it: team doctor checks its first word there — one fully quoted literal with its quotes removed, the way a shell would run it — and any relative argument. up and add leave a seat out, saying the same words, when the first word is missing there, or when the first word is a shell (sh, bash, zsh, by name or by path) whose first argument is the script, not an option, and that relative script path resolves from the project root and not from that folder (an option-bearing line such as zsh -x ../x is a note); every other relative argument, and a line that quotes or substitutes text, is reported as not checked, never refused. count: 2 makes the numbered names; parked keeps a seat out of idle reports, stopped keeps it out of up.

watch

The watch's own timings are the owner's too: interval, idle_first, idle_repeat, team_idle, nudge_wait and unsent_after are approved with the file, and an edit to one of them changes nothing until the owner approves it — the passes keep running with the values of the approved copy, or with the defaults when nothing was approved, and the difference is reported. By default an idle seat is reported once per idle period (idle_repeat is off). A team that wants repeated idle reports turns them on by setting watch.idle_repeat to the repeat interval (such as idle_repeat: 20m), repeating every 20 minutes while the seat stays quiet.

A check the team doesn't want is turned off in watch.checks — a section only the owner changes, so it is approved like the rest of them.

.agents/team.yaml
watch:
  interval: 120s
  checks:
    memory: off

budgets

budgets is the owner's: marks (percent used), how long a figure stays fresh, and each account's reserve or floor. An account's shared key is informational; team does not act on it. A seat spends its own account: when the file names one, its vendor when it doesn't, so one lab's two accounts are two buckets; a pattern names the account it measures, not the seat's. A check command is resolved to a file and hashed when the owner approves. A change to that file leaves that account's check unapproved: it is not run, and the account reads unknown, until the owner approves again. The rest of the file still runs. watch.quota_marks is still read, with a warning, until you move it to budgets.marks. A figure first seen on one seat does not count until it changes or a second seat shows the same number. It goes stale from the moment it last changed, and the last readings are kept in the state file beside the team file. team status prints them, one row per account and window, when there is an account or a stored reading.

examples/checks/codex-quota is a check for an openai account. It ships with the package: with a global install it is at $(npm root -g)/team/examples/checks/codex-quota, and it is examples/checks/codex-quota in the repository. It is a Bun script — the check needs Bun on PATH, whatever runs team — and it uses only built-in file modules, so there is no package to install beside it. Copy it onto PATH and name that command:

team.yaml
openai:
  kind: subscription
  reserve: 10%
  sources: [check, status_line]
  check: codex-quota

More fields

More fields exist — dialogs, tools, trust, machine, limits, watch, visibility and releases — and the comments team init writes name them; validation refuses what it cannot check, and this build acts on what the commands below read.