---
title: "Managing agents like a team"
description: "「Managing agents like a team」：yokoten 使用指南的一章。"
canonical: https://yokoten.app/zh-tw/docs/guide/managing-agents-like-a-team
lang: en
source: https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/guide.md
ref: "v0.1.2"
---

> For AI agents: the documentation index is at https://yokoten.app/llms.txt. To set up yokoten in a project, follow https://yokoten.app/agents.md.

> 這一頁還沒有繁體中文版，下面是英文原文。

# Managing agents like a team

The setup treats agents like staff on a line, each with a clear role and limited rights:

- **The owner** (you) decides what gets built and when it ships. Only you approve a hard
  line, create release tags, and change repo settings. An area lead of another model may
  approve its area (records 0026 and 0047).
- **The lead** is the session you talk to. It plans, splits the work, reviews, and opens
  pull requests through a **bot**, never with your account.
- **Workers** are sessions the lead starts for one task each, in their own worktree
  under `.worktrees/`, with `YOKOTEN_ROLE=worker`. They change code and report back.
  They never push. A fix task also gets `YOKOTEN_FIX_TASK=1`. Then the guard and the
  commit check stop a change to a test file. The stop hook runs the brief's done-when
  command. After three refusals the worker may stop. Its message to the lead starts with
  "NOT DONE: checks failing". In a linked worktree, `.yokoten-agent.json` records `checks`.
  The lead reads that file. Its `/api/state` shows it as the commit check.
- **The bot** is a GitHub App installed on the repo. It opens pull requests. If it has
  the Workflows permission, CODEOWNERS still sends every CI change to you, and a release
  waits for you in the protected `release` environment. A ruleset requires your review,
  so nothing it opens merges without you.

A worker runs its commands under the guard, so write the brief to fit it:

- Start with `cd <worktree> && cmd && cmd`. The `cd` stands on its own, joined by `&&` or
  `;`: not in a subshell, a pipe or the background. Use no pipe after it.
- No `git -C`: `cd` into the worktree first.
- No `VAR=value` before a command, except `TZ`, `CI`, `NODE_ENV`, `NO_COLOR`,
  `FORCE_COLOR`, `DEBUG`, `LANG`, `LC_ALL` and the names `yokoten.json` lists in
  `worker.settings`.
- If the task needs another setting, such as a test's capture file, the lead makes it
  and hands the worker the file.

Seven skills carry the method. `dispatch` briefs a worker. `inspect` reviews one exact
commit in three passes. `rca` finds the cause, adds a check, then spreads it. `record`
writes decisions down. `release` picks the next version and prepares the changelog. The
owner tags. `merge` merges through the bot and stops when the new commit is not UTC.
`yokoten` adopts the practices. Three agents do the work: `worker`, `reviewer` (reads
only), and `verifier` (runs, never fixes).

## Who reviews what

Review follows risk. The tier on a pull request's first line says how hard a mistake
is to undo (record 0004):

| Tier | Kind of change | Who reviews |
|------|----------------|-------------|
| C | Standard: docs, wording, styles | The lead |
| B | Normal: other code, with tests | An agent of another model reviews; the lead approves |
| A | Hard to undo: owned files, or a change that makes a rule looser | The owner, or an area lead of another model for a path that is neither a hard line nor a looser change |

In yokoten's own repo, `.github/CODEOWNERS` lists the owned files: the core rules,
CODEOWNERS, CI that publishes or holds a secret, the bot token, guard config and
`package.json` (record 0037). In a new project, `init` writes one for the
lead-only paths. With "require review from code owners" on, the
host asks the owner to review a pull request that changes one of them.

`check --pr` ties the two together. A pull request that changes an owned file fails
unless its first line is `tier: A`. The paths set the lowest tier. The author can raise
a tier, never lower it. The check reads CODEOWNERS from the base branch, so a pull
request can't edit the file to lower its own tier. Without a base commit, on a local run,
it reads the working tree. The message says which one it read. No CODEOWNERS file, no
check.

An approval is for one commit, and it must come from a code owner of the pull
request's changed files: the owner's login approves as a collaborator, so the account
never counts, only CODEOWNERS membership does. When the changed files name no owner,
any owner named anywhere in CODEOWNERS counts. `yokoten bot` refuses `gh pr merge` of
a tier A pull request whose newest code-owner review is not an Approve on the head. A
newer changes-requested or dismissed review refuses too, while other reviews never
count. When the head is newer, the merge goes on only for a clean merge of the base
branch. The pull request's own diff must be the same as at the Approve. Otherwise it
refuses: the Approve is on <old commit>; <n> commits with changes came after it; ask
the owner to approve again. `gh pr status` through the bot shows the same check for
tier A pull requests. Keep GitHub's dismiss-stale-approvals setting on too, but
yokoten never depends on a host setting a project may not have.

## Area leads approve in their area

Record [0026](https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/records/0026-the-owner-is-the-board.md) moves the lock for paths that are
not a hard line from your code-owner review to yokoten's merge check. An area lead
approves with a GitHub review. The last line of the review body names the lead, its
vendor and its model:

```text
Approved by area lead Sonnet (Anthropic, Claude Sonnet 5.5)
```

GitHub records the commit the review was given on. A later commit needs a new review.
The lead must be another model than every writer on the `Made with` line (record
[0047](https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/records/0047-tier-a-by-another-model.md)). Claude Sonnet may approve work by
Claude Opus, and a lead from another vendor may too. The check compares names, not
spellings: `claude-opus-5-5` and `Claude Opus 5.5` are one model. When the `Made with`
line names no writer, the check refuses. A file that `.github/CODEOWNERS` owns, and a
looser change, still needs your approval. The release pull request changes
`package.json`, so it always needs you. Until the diff tier check (#697) lands, a change to a test that already
exists counts as looser; a new test does not.

Nothing here changes your repository settings. When you are ready, follow these steps.

1. **Check that `.github/CODEOWNERS` lists only the hard lines.** In this repo it already
   does (record 0037): the core rules, `.github/CODEOWNERS` itself, CI that publishes or
   holds a secret, the bot token, guard config and `package.json`. If a path there is not
   a hard line, remove it: a path you remove becomes lead-approvable.
2. **Keep one required approval.** A lead's review is a real GitHub review, so keep
   "Required approvals" at 1. Do not let GitHub Actions approve a pull request.
3. **Keep the CI check required.** The branch ruleset should still require the CI check
   that runs `yokoten check --pr`. The bot's merge check is the second lock: it reads the
   lead approval before `gh pr merge`.
4. **Turn off "Require review from Code Owners" when the hard lines are safe.** After
   step 1 the setting names only hard lines, so leaving it on still asks you only for
   those. Turning it off lets the bot's check decide the rest.

`yokoten doctor` still expects code-owner review today. Expect that one step to note the
change until it learns the new lock. When a merge is refused, the message names the
reason and the next step: a lead of another model, a new review on the head, or the owner
for a hard line or a looser change.

A reviewer finds the tier through labels, so `check --pr` also reads them. A pull request
needs one type label and one tier label. A type label is a title type (`feat`, `fix`,
`docs`, `refactor`, `ci` and the others) or a common name for one: `bug`, `hotfix` or
`incident` for `fix`, `feature` or `enhancement` for `feat`, `documentation` for `docs`.
The tier label is `tier A`, `tier B` or `tier C` (`tier: B` also counts). It must match
the `tier:` line in the body. Each missing or wrong label gets its own message.

Server setup, step by step: [`docs/server.md`](https://github.com/yokoten-labs/yokoten/blob/v0.1.2/docs/server.md).

## Review bots you will see on pull requests

A review bot reads a pull request after you open it. It posts comments on the pull
request page. You may see a walkthrough of the change, a risk level, a list of
pre-merge checks, and a suggested fix. Some bots can push that fix.

Two kinds exist:

- **Commercial.** The bot is closed source. The company runs it, and your code goes to
  the company's servers. Many are free for public repos and paid for private ones.
- **Open source.** You run the bot yourself, with your own model API key. You pay the
  model vendor for the tokens it uses, and the code stays on your machine.

Prices, licences and data handling change. This guide names no bot as a recommendation.
Read the bot's own page before you turn it on. A bot's comment usually links to it.

yokoten works before the pull request, on your machine:

- the guard stops a risky command, a secret or a private detail;
- the `inspect` skill reviews the change in your agent session, at no extra cost;
- the checks and the merge rules stop a merge or release you did not approve.

A review bot works after the pull request. The two fit together. The bot sees the
pull request; yokoten sees the session that wrote it.

You do not need a review bot to start. yokoten's own review is enough. Add a bot when
your team wants a second reader on the pull request page. `doctor` never asks you to
add one: it names yokoten's own next step.

yokoten's findings on the pull request page come from its own checks only. It does not
repeat another bot's findings or review the code a second time (#91).

## Keeping pull requests up to date

Every merge leaves the other open pull requests behind. A conflict waits for nobody, so
the lead runs `yokoten pr sync` after every merge. It merges each branch's base branch in,
runs typecheck and the tests of the touched files, and pushes as the bot. This runs after
every merge, clean or resolved, and in UTC so the merge commit is too. It resolves only
two mechanical conflicts: two added blocks at one spot, and two changed import lists.
Anything else stops with the files and the hunks, and the lead hands them to a worker with
the dispatch brief. Each report line names the base it merged. The list stops at 100 open
pull requests, and the report says when it did.

The owner never resolves a conflict on GitHub. The web editor would commit as the
owner, and the bot must be the one that pushes. Related: issue #320, the merge train.

## The desk

A desk is a fenced place for one AI to do one task, the same for every vendor (#637).
Start one with an issue number or a task in quotes:

```sh
yokoten desk 42
yokoten desk "fix the login bug" --agent codex
```

`yokoten desk` makes the desk `../<repo>-desks/<n>/` (its own clone, branch `desk/<n>`),
writes a brief, and starts the AI there inside the fence. It never pushes; `--pr` pushes
the branch and opens the pull request. When the AI exits, it runs the check and the tests
in the fence and shows the evidence. With no words, it lists your desks.

The older form, for scripts, starts any command in a fenced clone you made yourself:

```sh
git clone ../yokoten ../desks/42
yokoten desk claude-code ../desks/42 -- claude -p "$(cat brief.md)"
```

The fence is one contract. Each system builds it its own way:

| Rule | In the desk |
|---|---|
| Writes | Only in the desk folder (`.git` too, so the worker commits) and the desk's own state folder for the agent. `.git/config` and `.git/hooks` stay read-only |
| Reads | Everything else the agent can see is read-only |
| Other projects | Not visible. Your home and the desk's parent folder are hidden. On Linux, `/run` is hidden too: its sockets lead out, and on WSL2 one of them starts Windows programs. Node, the agent program and yokoten are shown again, read-only |
| Your agent state | Not visible. Your `~/.claude`, `~/.codex`, `~/.grok` and Muse folders stay hidden: no settings, hooks, memory, MCP config, history or sessions |
| Child processes | Work, with pipes, so tests can start programs |
| Network | Commands get no network of their own. The proxy outside lets through only the agent's API hosts. Any command inside can use the proxy too, so a command can reach those hosts |
| Environment | `YOKOTEN_ROLE=worker` and `TZ=UTC`. No token or key from your shell (see [What an agent's environment holds](https://yokoten.app/zh-tw/docs/guide/privacy.md#what-an-agents-environment-holds)) |
| Team | The team folder is read-only. Only the agent's own outbox in it is writable (see [Messages between agents](https://yokoten.app/zh-tw/docs/guide/managing-agents-like-a-team.md#messages-between-agents)) |

The desk must be its own `git clone`. A worktree's git folder lives in another checkout,
which the desk hides, so a worktree is refused.

**Who the agent is.** The desk reads the model and provider from the agent's own config:
for Codex, `model` and `model_provider` in `config.toml`. The kanban shows that model. When
its vendor is not the harness's own, the session starts with one line that says who the
agent is, and that the harness is its tool. A DeepSeek model runs through Codex, whose
prompt names OpenAI; without the line, the worker signs its work as OpenAI.

**The desk's state folder.** Each desk gets its own state folder for the agent, next to
it: `<desk>.state/<agent>`. The desk points the agent there with the agent's own variable.
Only the login file is copied in from yours. A newer login of yours is copied again on the
next start. Remove the state folder with the desk.

| Agent | `<agent>` | Points the agent at its state with | Login file copied | API hosts | Command inside the desk |
|---|---|---|---|---|---|
| Claude Code | `claude-code` | `CLAUDE_CONFIG_DIR` | `.credentials.json` | `api.anthropic.com`, and `platform.claude.com` to renew a login | `claude -p "<brief>"` |
| Codex, and DeepSeek through Codex | `codex` | `CODEX_HOME` | `auth.json` | `api.openai.com`, `api.deepseek.com` | `codex exec --sandbox danger-full-access "<brief>"` |
| Grok | `grok` | `GROK_HOME` | `auth.json` | `api.x.ai` | `grok -p "<brief>"` |
| Muse Code | `muse` | `XDG_CONFIG_HOME`, `XDG_DATA_HOME` | `muse/auth.json` | `api.meta.ai` | `muse exec "<brief>"` |

- **Claude Code** renews a login at `platform.claude.com`. A token from `claude setup-token`
  in `CLAUDE_CODE_OAUTH_TOKEN`, or an API key, needs no renewal and passes in as a login.
- **Codex** runs with `--sandbox danger-full-access` inside the desk. Its own
  `workspace-write` sandbox makes `.git` read-only and blocks the pipes that tests use. The
  desk is the fence instead. Your `config.toml` stays out: give a provider with `-c` on the
  command. A ChatGPT login also needs `--host chatgpt.com,auth.openai.com`.
- **Grok**: turn off `--sandbox` inside the desk if it stops a commit or a test.
- **More hosts**: `--host <host>` lets the agent reach another API host on port 443.
  Separate several with commas.
- **More files**: `--read <path>` shows another path, read-only. A path that would open
  your home, a hidden folder or your own agent state is refused.

**The proxy.** Each of the four agents names `HTTPS_PROXY` in its program, and the desk sets
`HTTPS_PROXY`, `HTTP_PROXY` and `ALL_PROXY` in both cases. It also sets
`NODE_USE_ENV_PROXY=1`, so Node's own `fetch` uses the proxy too. The proxy takes `CONNECT`
only, to a listed host, and never reads the traffic. It keeps at most 64 tunnels open, and
closes a tunnel that is still for 10 minutes. When it refuses a host, the desk names the
host at the end.

**Review from outside.** The worker writes in the desk, so treat the desk as untrusted. Do
not run git or any other command inside the desk before you check the work. Fetch the
branch into your own checkout, then review it there:

```sh
git fetch ../desks/42 HEAD:refs/desks/42
git diff main refs/desks/42
```

`.git/config` and `.git/hooks` are read-only in the desk, so a worker can't plant a hook or
an alias there. Set an upstream or other config from outside, before you start the desk.

**The commit check and your private marks.** The commit check reads your private-marks
file. An agent that can read the file can also print it. So the desk hides it by default,
and the check in the desk does not see your marks. Until marks are kept as hashes (#665),
run the check yourself, outside the desk. Check out the fetched branch in your own
checkout, then run `yokoten check` there; it checks the whole tree. `--marks` shows the file in the
desk, read-only. Use it only for an agent you trust with the words.

**Systems.**

The fence is Anthropic's
[sandbox-runtime](https://github.com/anthropics/sandbox-runtime) (`srt`), the sandbox
Claude Code uses. yokoten runs `srt` from PATH when it is the pinned version, 0.0.81.
Otherwise `npx` gets that version once and keeps it in npm's cache. It is not a dependency
of yokoten. When the sandbox can't start, the desk says why in one line and starts nothing.

- **Linux and WSL2**: install `bubblewrap`, `socat` and `ripgrep`. On Debian and Ubuntu:
  `sudo apt install bubblewrap socat ripgrep`. The desk gets its own empty network, and
  only the agent's API hosts pass srt's proxy.
- **macOS**: `sandbox-exec`, built in. Not yet run on a Mac: run
  `node --test test/agents/desk/start.test.ts` there once to check the contract.
- **Native Windows**: no desk. `yokoten desk` starts nothing and says to use WSL. On
  native Windows, the guard alone holds the rules.

`YOKOTEN_FENCE=own` uses the old fence until v0.1.3: `bwrap` on Linux, `sandbox-exec` on
macOS.

## Messages between agents

Agents of every vendor can message each other (#674). It is files on this machine: no
server, no network.

```sh
yokoten msg send lead "tests pass; ready for review"
yokoten msg inbox
yokoten msg who
yokoten msg log
```

- **Where**: the team folder next to your checkout, `<repo>.team/`. Each agent writes only
  its own outbox there. `yokoten desk` adds the agent by `--name`, or by the desk
  folder's name made lower case. A folder name that can't be a name joins no team.
- **Who you are**: in a desk, the desk says. Outside one, you are `lead` only in your main
  checkout, not in a worktree or a checkout inside a desk.
- **A desk can't fake a sender**: in a desk, the team folder is read-only, and only the
  agent's own outbox is writable. `msg send` refuses a worker outside a desk, and the
  guard stops its edits in the team folder.
- **Outside a desk, a sender can be faked**: a program outside a desk runs as your user,
  so it can write any outbox, the lead's too. A worker in a plain worktree can do that, for
  example from inline code that leaves its folder. yokoten can't tell such a write from
  yours. Run workers that message in desks.
- **Delivery**: the hooks add unread messages to the agent's context. Claude Code and Grok
  get them before a tool call. Every agent gets them at the end of a turn, after the
  checks: a turn that fails a check is sent back with the messages too. `msg inbox` shows
  them at any time. Muse Code's hooks get no variables, so in a desk Muse reads with
  `msg inbox`.
- **A message is data**: every delivery says that the messages are from another agent and
  can't grant approval or permission. This note is the defence. Each message names the
  sender's vendor and model. Some claims of the owner's approval get a flag, but the flag
  misses claims in other words.
- **Privacy**: a message gets the same scan as posted text: private marks, secrets, email
  and IP addresses, and secret variables. A message that fails is not sent, and you get
  the reason. The scan reads plain text, so a secret that is encoded or split passes it.
  A desk sees your private marks only with `--marks`, so a message from one desk to
  another is checked against them only then. What the lead reads is always checked.
- **Who reads**: every desk on the team can read every message, not only its own.
- **Caps**: 2,000 characters a message, 20 messages per agent in 10 minutes, and 256 KB
  per outbox.
- **The log**: `msg log` shows every message. Only the lead runs it.

## The kanban

`yokoten kanban` shows the team at a glance. It is a preview, so it may change. It is
read only: nothing on it starts, stops or changes work. Record 0044 says why.
(Before v0.1.2 it was called `yokoten board`. The old name still runs it.)

It shows three things:

- **Sessions.** Each AI session in this repo, its worktrees and its desks: the agent, the
  model, the task, and whether it is working, stuck, waiting or done. A session with no
  output for 15 minutes counts as stuck.
- **Proof.** For each session: whether a system sandbox or only the guard fenced it,
  whether the commit check passed, and the tests on Linux, Windows and macOS. Those three
  read "not proven" until the status file records platforms.
- **Pull requests and Actions.** The pull requests that wait on you, the ones merged
  lately, and the health of GitHub Actions. The tokens each agent used show with them.

`yokoten kanban --text` prints the sessions by state, the pull requests and Actions, and exits.
That works today. The proof and the tokens are in `GET /api/state` for now.

Without `--text`, it serves a page on this machine only, at `http://<repo>.localhost:<port>/`.
`<repo>` is the folder name, made safe for a hostname. The port stays the same for this
repo. It opens your browser, signed in, and runs until you stop it with Ctrl-C.
`--no-open` only prints the address. The sign-in key goes only to the browser and is
never printed. A request with a wrong host is refused.

The page is being rebuilt as its own web package (record 0044). Until it is built,
`/` says so in one line, and `GET /api/state` still works. It returns one snapshot of
the sessions, pull requests and Actions as JSON. `GET /api/sessions/<id>/stream` sends one
session's output as server-sent events. The id is a hash of the session's folder, never
the path. Any request but GET or HEAD gets a 405.

The kanban reads GitHub through `gh`, in the background, one read at a time. When a read
fails, it keeps the last lists. The guard checks each agent, and pushes and merges
still go only through the bot.

Agents show by themselves. Start an agent in this repo, in one of its worktrees,
or in a desk (`yokoten desk`), and yokoten's hooks keep its status in
`.yokoten-agent.json` at that checkout's root. This works for every vendor:

- The session start writes `vendor`, `model`, `role` (`lead` in the main
  checkout, `engineer` for a worker, by the guard's own rule), `worktree`,
  `state: working` and `started`. `started` is written once.
  Grok has no session-start hook: its first call writes them.
- Each allowed tool call sets `doing` and `updated`, at most once every 5 seconds.
- A refused call sets `blocked`. The next allowed call clears it.
- The stop sets `state`: `done` with `checks` for a worker, `waiting` for the lead.

The hooks write the file only in a repo with `yokoten.json`. A worker never writes the
main checkout's file: that one is the lead's. The first write adds the file to the
repo's own `.git/info/exclude`, so `git status` never shows it. A desk is a plain
clone, so `git worktree list` misses it: `yokoten desk` notes it in the repo's git
folder, and the kanban shows it too.

An agent may also write the file itself, for `task`; the hooks keep the fields they do
not set. It leaves by removing the file. The format is open: other tools may read it
too. One file per worktree.

| Field | Means |
|-------|-------|
| `name` | The agent's name. Without it, the worktree's folder name is used |
| `vendor` | Which vendor it runs on, such as `claude-code`, `codex` or `grok` |
| `model`, `effort` | The model and effort, such as `opus` and `medium` |
| `role` | `lead`, `engineer`, `reviewer`, `verifier` or `helper` |
| `task` | What it works on, such as `PR #615 Run rules locally` |
| `worktree` | The worktree it works in |
| `doing` | What it does now, in a few words |
| `state` | `working`, `waiting` (it needs you) or `done` |
| `checks` | `passing` or `failing`, from its last test run. It is in `GET /api/state` as the commit check |
| `updated` | When it last wrote, such as `2026-10-22T10:00:00Z`. Quiet for 15 minutes shows as stuck |
| `started` | When this work started, such as `2026-10-22T08:00:00Z`. Without it, the file's birth time is used, then `updated` |
| `blocked` | A guard stop that needs a decision, in one line |

Every field is text. Every field may be missing. A file that is not a JSON object is
skipped. A `state` or `checks` outside the words above is ignored.

What agents write shows only as text, never as page code, and is masked like a session.
The kanban loads nothing from the internet. Its interface is `/api/state`; the page is
only one view of it.

---

上一章: [Naming](https://yokoten.app/zh-tw/docs/guide/naming.md)
下一章: [Privacy](https://yokoten.app/zh-tw/docs/guide/privacy.md)
