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:
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 lineThe head
format: 1coordinatorandoperatorname seats: the coordinator dispatches work, the operator receives the watch's reports and nudges.
sessionnames the herdr session and defaults toproject;--sessionoverrides it.
identity
identity:
signature:
commits:
position: trailer # last-line | trailer | anywhere
exempt: [merge]
pull_requests:
position: last-line
template: "**Agent:** {display} · {role}"identity.signatureis the rulecheckenforces: a template, where it must stand, and which commits are exempt. Commit signatures readAgent: {display} · {role}, pull request bodies**Agent:** {display} · {role}. Withoutdisplay, the signature reads "model version"; with it, the lab's own spelling.identity.sinceskips an older history,identity.humanslists commit authors who don't sign, andidentity.forbiddenadds to the defaults —^Claude-Session:lines and session links are always refused.
workspace
workspace:
mode: shared # every seat works in the project rootworkspace.modeisshared(every seat in the project) orworktree(each task in its own checkout, withpath,baseandsetup). Every seat starts in~/.config/team/lobby, whichtrustlists as an absolute path along with the project root.upandaddrefuse a seat whose own folder is a protected checkout, and a legacytrust(project-relative patterns, or none) cannot launch until it is rewritten as those absolute paths and approved.
machine
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 reportseats
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: 3seats[*].clipicks the launch profile;claude-code,codex,cursorandantigravityare available, andteam doctorsays what the others still need.vendor,modelandversionspell one seat's model.accountnames the budget account the seat spends when one lab has two; without it, the seat spends itsvendor, and changing either is an edit the owner re-approves.
launchis the plain command, without approval flags: the profile adds them. It runs in the folder the seat starts in —~/.config/team/lobby— andteamnever rewrites it:team doctorchecks its first word there — one fully quoted literal with its quotes removed, the way a shell would run it — and any relative argument.upandaddleave 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 aszsh -x ../xis a note); every other relative argument, and a line that quotes or substitutes text, is reported as not checked, never refused.count: 2makes the numbered names;parkedkeeps a seat out of idle reports,stoppedkeeps it out ofup.
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.
watch:
interval: 120s
checks:
memory: offbudgets
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:
openai:
kind: subscription
reserve: 10%
sources: [check, status_line]
check: codex-quotaMore 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.