The landing layer for agent fleets. Your coding agents submit once; bay lands exactly what was checked, on plain Git.
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗Git is fine. The workflow around it was built for people. Run several coding agents in one codebase, often in the same files, and branches go stale, merges collide and main breaks. Agents that finish in minutes then wait hours to merge.
bay is one free, MIT-licensed command that takes that work off your agents and your team. An agent opens a change, edits, and submits it once. bay combines it with the latest main, runs your checks on that exact result, and merges only what passed. A runner finishes the merge, so the agent can start its next task. Your repository stays an ordinary Git repository, on the host you already have.
bay start CARD-123 --paths src/api # open a change: a view with only the paths it needs
# … the agent edits in that view …
bay merge # submit once and return; a runner checks and merges it
bay save "fix docs" --paths docs/ # from any checkout: merge only the edits under docs/
bay status # what each change is doing, what passed, and why it waits
bay status --since # what changed that affects my work
bay start c7k2m4qa # pick a change up again, on the newest main
bay start c7k2m4qa --adopt # take a change and its view over from their actor
bay abandon # give the change up; another agent may adopt it
bay undo c7k2m4qa # a new change that takes one merge back; history stays
bay help # every command and flag on one page
People who are not developers use four words, usually through their agent: bay save, bay waiting,
bay approve and bay undo. Whoever runs bay for the team uses bay enrol, bay prune and
bay doctor. Agents can also ask each other for one thing: bay talk <who> "<what>" (and
--to <a> --to <b>, or --to-live, asks several at once). Talk is off until .bay/policy.toml on main
sets [talk] enabled = true. See talk and the
CLI reference.
bay serves one branch, and you name it: main, dev, or any other. In bay's words that branch is
"main", whatever its name. It is the remote's default branch, as Git's own clone takes it: to serve dev,
make dev the default on the host, and every clone follows with no step of its own.
Agents create no branches. A change is bay's own record under refs/bay/changes/, and the agent edits it
in a view: a folder bay makes. So there is nothing to clean up after a merge.
Branches after main are promotion targets. A repository with dev → staging → production and no
main branch works as it is: serve dev, and declare the rest in .bay/config.toml on dev:
main = "dev" # the branch bay serves: the remote's default branch
[[target]]
name = "staging" # takes checked commits from dev
[[target]]
name = "production"
from = "staging" # takes them from staging
soak_s = 3600 # once a commit is an hour old
No agent pushes to staging or production; bay's hook refuses it. Work that must never merge, such as
research, goes in a sandbox lane ([[lane]]). See Promotion and
Sandbox lanes.
Every command prints one bay.result/1 JSON document on stdout. Its say is one plain sentence: what
happened, and what to do next. At a terminal, a person sees that sentence; --json or a pipe gets the
document. There is never a prompt to answer. This is bay merge in bay 0.6.14:
{"schema":"bay.result/1","op_id":"yq3o22fx.1","decision":"queued","state":"submitted","say":"Saved. bay checks it and adds it to main next.","change":"yq3o22fx","waiting_for":"runner","elapsed_s":0,"evidence":[],"freshness":{"main":"50cc53984ec78920b0e79947b9678a76c542fd72","fetched_at":"2026-10-30T21:16:03Z"}}
queued means the merge has not happened yet; only merged means it has. A refusal names a stable
reason, the actions still allowed, when a retry can succeed (retry_when), and its next_step.
Results and refusals lists every reason.
bay is free for any team: no paid tier, no account, no sign-up. Version numbers are 0.x, so a minor release can still change a command or a format; the changelog names every change.
Agents now write much of the code in minutes. They still merge it through steps built for a person who reviews and then clicks merge: branches, pull requests, merge queues, and scripts. The merge itself takes seconds. The wait around it takes hours.
One team of about twenty agents on one repository measured a single day:
Each agent folder also cost 27 seconds and 3.4–4.0 GB of disk to create, even for a task that read a few files.
¹ 64 GB of it was captures and corpora in five folders. bay leaves large data outside Git and does not store it.
git config --global bay.updateAuto false turns that off.clap, serde, serde_json,
sha2, toml), and unsafe_code = "forbid". Ask your coding agent to audit it:
grep -rn 'Command::new' src lists every program bay starts, and grep -rn 'https://' src every
address it reads. Only git (to your remote) and curl (for bay's releases) reach the network; the
rest, such as ssh-keygen, tar and the check sandbox, run locally.bwrap on Linux and sandbox-exec on macOS. A check gets no network unless
its entry in the policy on main says network = true.install.sh and bay update refuse a release that does not verify.See Security and privacy for everything bay sends, and where.
macOS and Linux, on ARM and Intel:
curl -fsSL https://getbay.io/install.sh | sh
The script downloads the newest release from this repository's GitHub Releases. It checks SHA256SUMS
against the bay release keys, and the archive against SHA256SUMS. It installs one
binary to ~/.local/bin/bay. When that folder is on your PATH, it then runs bay install, and
bay doctor inside a repository; otherwise it tells you the two lines to run. You can also
verify a release by hand.
There is no ready Windows binary. Run the install command inside WSL, the tested path, or build bay from
source on Windows. Windows has no check sandbox, so a check there does not run unless the machine's owner
chooses git config --global bay.runnerSandbox none.
bay needs Git 2.40 or newer in an agent view (2.50 or newer is recommended), 2.50 or newer on a machine
that runs a runner, and 2.54 or newer on a machine that hosts main with bay host. bay doctor
names any floor a machine is below. See Install for every option.
.bay/config.toml and .bay/policy.toml to main and push them. bay reads both only from
main, so no change sets the rules it is judged by. The policy lists the required checks; a repository
that wants none says unchecked = true. See Configuration.bay install, then merge the agent settings file it writes. Every other clone needs
nothing: a plain git clone saves and merges, as Git needs only its remote.bay runner run, or bay runner install). Without
one, bay merge --wait and bay save merge right where they run.The Quickstart walks through each step.
bay install copies the bay skill, skills/bay/SKILL.md, where Claude Code reads
it. For other agents, or before bay is installed, add the skill with the skills CLI:
npx skills add gudjon/bay --skill bay -g
The skill tells the agent how to install bay when it is missing. See the skill page.
| Promise | In plain words |
|---|---|
| What merges is what was checked | bay builds the merged result first, checks that result, and merges it only if main has not moved. If main moved, bay builds and checks again. With batching on, a batch's last commit is the one that passed; a check with footprint = true may also cover a later tree that reads the same files. |
| Unknown is never a pass | A check that crashed, timed out, or found fewer tests than its minimum blocks the merge. Checks that finished keep their results. |
| Agents can leave | bay merge returns at once. While a runner runs (bay runner run, or your CI through the same contract), it finishes the merge, recovers it, or asks for a precise repair. |
| A retry never merges twice | Every operation has an id. A lost reply is reconciled, never sent again as a second merge. |
| Stale work is stopped, and repair stays open | When an input that your change declared merges, bay tells you what changed, and states how much it could see. You can still edit; you cannot merge until you check again. |
| Every refusal says what to do next | A stable reason, what you can still do, and when to retry. |
| A view costs what the task reads | A view is a sparse worktree of your clone that holds only the paths you named, so views share the clone's objects. Build outputs, fixtures and broad searches can still cost more. |
| Git stays the only store | Changes, evidence and events are Git commits, notes and refs. You can read and recover them with ordinary Git. |
Each promise has a failure test that must pass before bay calls it done.
bay start opens a change: bay's record of it, and a sparse view of the declared paths. The
view is its actor's: another actor's merge, save or commit there is refused (VIEW_NOT_YOURS) until
it takes the change over with bay start <change> --adopt.bay merge records a request to merge and returns. The agent may leave..bay/policy.toml names on that exact candidate, and records each result as evidence in Git notes.bay status shows every change, its evidence, and why it waits. bay start <change> picks a
change up again, and bay abandon gives it up.Checks run in a sandbox that sees only the candidate, the check's own files and system files, with no network unless the policy allows it.
bay guards three things, and each holds only under its own condition:
| What bay guards | When it holds |
|---|---|
| Its own merges: what bay merges into main is exactly the candidate it checked. | Always, unless you turn on batching or footprint reuse (see above). |
An agent's Git commands: the hook refuses a push to main or a target, a force push, git reset --hard and the like, and an edit or a git commit in another actor's view. | Only with the hook: [agent] product = "claude" (or "codex", which only senses) in .bay/config.toml on main, and bay's hook entries in the project's .claude/settings.json. One clone runs bay install and merges that file; every clone gets the hooks from main. |
| Everything that reaches main, such as a merge in a forge's web page or a script's push. | Only on a main from bay host with a key registry in the default signing mode, or on a forge main that only the runner's identity may push. Otherwise a bay-hosted main stays append-only, and anyone who can push may add commits. |
bay sees what went around it. bay status names each such commit under detail.unattributed. Without
the hook, bay doctor's hooks row reads off, and doctor turns red when main holds such commits beside
bay's merges. See Security and privacy.
| Command | Writes | Never writes |
|---|---|---|
bay install --machine | the bay program, and its guide (the skill) for your agent | hook entries, a project's settings, its Git config |
bay install in a clone | the above, plus bay's hook entries in the project's agent settings file and this clone's bay.* Git config | anything on main: you commit and merge the settings file yourself |
bay update | a newer bay program, verified first | anything in a project |
bay status, bay waiting, bay help | only fetched copies of the remote's refs, and bay's own cache under .git/bay/ | your files, your branches, main |
bay doctor | the above, and this machine's readiness on its presence record on the remote, so bay status --all shows it | your files, your branches, main, any settings or hook entries |
bay start, bay merge, bay save, bay approve, bay undo | the change's record on the remote, its view (a separate folder), and evidence | your checkout's files, except bay save, which updates the main checkout to the new main (bay save --paths never does) |
bay help says the same for every command. A hook entry that bay installs first checks that bay is on
the machine; where it is not, the agent's action goes ahead. Roll bay out
gives the safe order for many machines.
bay works without keys: with no key registry, it runs unsigned, every evidence record counts, and results
say trust: unsigned. Add a registry, one key at a time, with bay enrol <class> <name> <key.pub>, when
only enrolled keys may sign. By default a registry signs everything: an agent signs a request to merge,
only a runner (with its runner key) moves main, a producer signs check evidence, a reviewer signs a
review, an approver signs an approval, and a registry key adds and revokes keys. No one holds another
actor's key, so an agent can leave. A small team can set [policy] signing = "approvals" instead: then
only approvals are signed, and results say trust: approvals. See Keys and the registry
and Approvals.
Most teams compare bay with what they run now: agents open pull requests, and a merge queue merges them. That can merge, and a good merge queue keeps going after the author leaves. What it does not give is a record that the merged commit is the exact one that passed, partial results kept when a worker dies, recovery when the runner dies, or a stop on work whose inputs moved.
[agent] product = "claude" in the config, bay hook refuses a write it
already knows is stale and a fixed list of dangerous Git commands. It tells the agent once when a merge
of its own is refused or its check fails. bay install adds the bay skill, so the agent knows the commands.[agent] product = "codex": bay reads its hooks as a sensor. Codex runs a tool when its
hook fails, so main stays the guard; bay doctor says so.bay help --json declares what each agent product's hooks can block. See Agents and hooks.bay host on any machine you can
reach, or a bundle on a USB stick.bay.check.result/1 JSON document. Test one first with
bay plugin test check -- <command>.Against an agent session that starts from a full clone, and against a tuned setup with a good merge queue, on real repositories, with the scripts and raw results published so anyone can rerun them:
These are targets until the published results say otherwise.
.bay/config.toml and .bay/policy.tomlEvery docs page also reads as markdown: add .md to its address, or see llms.txt.
bay has no hosted CI. Every push to main is gated: the commit at its tip carries signed
bay.evidence/1 records in Git notes, one per check (fmt, clippy, and the tests under SHA-1/files and
SHA-256/reftable), made on that exact tree before it was pushed. Each promise above has a failure test
in tests/it, named for what it proves. Check it yourself (needs ssh-keygen):
git fetch origin 'refs/notes/bay/*:refs/notes/bay/*'
scripts/gate.sh --verify origin/main
cargo build --release
./target/release/bay install
cargo build --release --target <target> builds bay for other targets too.
bay is built in the open, and contributions are welcome. Read CONTRIBUTING.md to build, test and send a pull request, and the code of conduct. Found a security problem? Report it privately: see SECURITY.md.
To join bay's development, email the founders at [email protected]. Questions and ideas: open an issue or write to the same address. If bay helps your team, a star helps others find it.
MIT.