Coordinate agents
See what your fleet is doing
See every machine, actor and session on a repository, and read what changed since you last looked.
bay is in development. These pages describe bay as it will ship.
bay status --fleet shows what each agent on each machine works on. bay watch lists what changed since you last looked: landings, events and invalidations. Both read Git, so you need no server and no SSH between machines.
The fleet map shows your last fetch, not a live feed. It never contacts the remote itself, and it says how old its fetch is. A notice is a convenience. The executor log and trunk stay the record.
When to use the fleet map
- Before you start: see who holds a change, and on which machine.
- Find a stalled session: see each session's state and the age of its last report.
- Catch up after a break: run
bay watchwith your last cursor and read what landed. - Feed a chat or a dashboard: point the notify adapter at it, and fill gaps with
bay watch.
Show the fleet map
Run bay status first. It fetches trunk, then copies each machine's live ref into refs/bay/fleet/*. Then run bay status --fleet. It reads that copy and fetches nothing.
bay status # fetch trunk and the live refs
bay status --fleet # read the map from that fetchWhere the map comes from
- 01
Record
Each agent hook event writes the session's state to a local work record.
- 02
Summarise
A hook event starts a background
bay live push. It pushes at most once per heartbeat. It replacesrefs/bay/live/<machine>with one commit that holdslive.json, one row per live session. With a key registry, the agent key of that machine signs the commit. - 03
Fetch
bay statusfetches everyrefs/bay/live/*intorefs/bay/fleet/*. It records when that fetch first brought each summary, by this machine's clock. - 04
Read
bay status --fleetjoins those summaries with this machine's own records. With a key registry, a summary that its named agent did not sign lists no sessions.
What the map shows
| Field | Meaning |
|---|---|
machines | Each machine in the fetch, with its age and its number of fresh sessions. An unreadable summary shows readable: false and why. |
actors | Each actor the cache knows, the machines it is on, and its freshest age. |
sessions | One entry per fresh session: machine, actor, agent, change, state, last event, context use, last write and age. |
sessions[].behind | Whether trunk moved past the base of that session's change. null when this cache does not hold the change. |
behind | Whether the fetch is behind the remote: true once the fetch is one takeover window old, else null. Never false. |
fetched_at, fetch_age_s | When the fetch ran, and its age in seconds. |
machines_truncated | true when the map left machines out. |
talks | Always empty for now. |
Untracked and unknown
A held change with no fresh session gets an entry of its own. Its view may be on this machine with no session record here that names it. Then its state is untracked. That change had no hooked session, for example one with hooks off. bay never shows it as idle. Its view may be on another machine. Then bay cannot judge it from here, and its state is unknown. A change whose hooked session here ended gets no entry.
Ages and clocks
- bay never compares the clocks of two machines.
- A row's age starts with the summary's age on your clock, from the fetch that first brought it.
- bay adds the row's age inside the summary, by the clock of the machine that wrote it.
- A row one takeover window old drops out of the map.
- This machine's own sessions come from its local records, not from the remote's copy.
Watch for changes
bay watch fetches trunk and the executor logs, then lists what happened after a cursor. It lists trunk landings, bay.event/1 events from the executor logs, and new invalidations of your change. It returns detail.cursor. Pass it to --since next time to go on from there. The cursor is opaque. Use it as bay returned it.
bay watch # the current state of this view's change
bay watch --since <cursor> # what happened after that cursorWhat watch lists
- In a change's view, it lists only events that touch that change's inputs.
- Those are events about the change itself, about the policy, or about an input it declared.
- Outside a view, it lists every event and judges no change.
- Without
--since, a view gets its state first: landings since its base, its events and its invalidations. - Offline, it reads the local cache, and
freshnesssays so. bay watchonly reads. It never writes the change record and never sends a notice.
Get notices
Set [notify] command in .bay/config.toml to a program in the repository. The executor runs it once after each event it writes, with the event's JSON on stdin. Exit 0 means delivered. Any other exit, or no exit within 5 seconds, means lost. bay never retries a lost notice. bay watch still lists the event. Each event carries an id, so the adapter can drop duplicates. The adapter gets only PATH and HOME from the executor's environment.
[notify]
command = "scripts/notify" # gets one bay.event/1 on stdin, exits 0 when deliveredWhat the map cannot do
- It cannot ask the remote. It is as fresh as your last
bay status. - It cannot see inside a session with hooks off. It shows that session's change as
untracked. - It reads at most 256 machine summaries.
Refusals
bay status --fleet fails closed with REMOTE_NOT_FETCHED until bay status has fetched the live refs. A fetch from another remote URL does not count. The result is blocked, exits 10, and lists no machines, actors or sessions.
| Code | Meaning |
|---|---|
REMOTE_NOT_FETCHED | The local cache has no recorded fetch of the configured remote's live refs, so it is not the fleet. |
Not built yet
bay has the map, bay watch and the notify adapter. It does not have these parts yet:
- Talks in the map.
talksis always empty, and actors come only from live summaries. - Talks in
bay watch. - A byte budget for the one-line notices an agent hook gives: 2,048 bytes per session per hour.
- Waking an idle session when another change supersedes its change, or its inputs move.
Limits
- Machines: the map reads at most 256 summaries, and a host holds at most 256 live refs.
- Summary:
live.jsonis at most 32,768 bytes and keeps the newest 64 rows. - Push rate: one summary push per heartbeat, 10 seconds by default.
- Freshness: a row drops out after one takeover window, 120 seconds by default.
- Watch: one watch lists at most 500 landings and 500 events.
moresays when more follow. - Notice: the adapter has 5 seconds per event.