Skip to content

HTTP API: peers

The workers of this repository, named by their own initialize, and what each announced it is working on — gathered from the board of every checkout, because a server serves a checkout and a repository worked on through linked worktrees has one board per worktree. In memory; gone with the processes.

Rendered from docs/generated/openapi.json, which the executable generates from its capability registry and CI refuses stale. The raw document is at /openapi.json; a running server serves the same document at its own /openapi.json and Swagger UI at /swagger.

peers

The workers of this repository, named by their own initialize, and what each announced it is working on — gathered from the board of every checkout, because a server serves a checkout and a repository worked on through linked worktrees has one board per worktree. In memory; gone with the processes.

GET /api/v1/peers peers.list

List peers

Every worker of this repository: id, the client's own name and version from its initialize, transport, when it attached, when it was last seen, what it announced, and which checkout it is attached to. A server serves one checkout, so the board of a repository worked on through linked worktrees is gathered: this checkout's board out of memory, every other checkout's from the server its lease names, asked for its own board alone. 'boards' says which checkouts were covered and 'complete' whether every one of them could be read, so a short board is never mistaken for an empty repository. 'checkouts: this' reads one board and enumerates, probes and asks nothing else. In-memory on every server; gone with the processes.

  • query
  • behaviorally_verified
  • benchmark required
  • cache disabled
  • MCP tool majordomus_peers

Query parameters

nametyperequireddescriptionexamples
checkouts Checkouts no Which checkouts the board covers. Absent means `repository`; `this` is the checkout the call reached and nothing else, and it is what a sibling server is asked so that a gather can never ask back. default: "this"

Try it

The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.

curl -s "http://127.0.0.1:8741/api/v1/peers?checkouts=this"

Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.

Responses

statusbodymeaning
200 PeerList List peers: the result.
400 ErrorBody invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects.
404 ErrorBody not_found: The input names something the repository does not hold.
500 ErrorBody internal: The capability failed for a reason of its own; the message names it.
default ErrorBody Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object.
POST /api/v1/peers/announce peers.announce

Announce what this peer is working on

Tell the other peers of this shared server what the calling session is doing and which paths it expects to touch. A peer may hold several claims at once: name one with 'claim' and it stands beside the others, announce under that name again and it is updated, leave it out and this is the peer's one unnamed claim. Name your claims when one session is doing several things at once — subagents share their parent's session, so an unnamed announcement from each of them would replace the last rather than adding to it. Changes this process's memory only; the repository is never written. Needs an MCP session: over plain HTTP there is no caller.

  • command
  • behaviorally_verified
  • benchmark required
  • cache disabled
  • MCP tool majordomus_announce

Request body — AnnounceInput, JSON

example default

{"intent":"benchmark: announcing","scope":["apps/majordomus-cli"]}

Try it

The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.

curl -s -X POST "http://127.0.0.1:8741/api/v1/peers/announce" -H "content-type: application/json" -d '{"intent":"benchmark: announcing","scope":["apps/majordomus-cli"]}'

Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.

Responses

statusbodymeaning
200 Announced Announce what this peer is working on: the result.
400 ErrorBody invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects.
404 ErrorBody not_found: The input names something the repository does not hold.
422 ErrorBody refused: The command was understood and turned down for the reason the message gives.
500 ErrorBody internal: The capability failed for a reason of its own; the message names it.
default ErrorBody Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object.

Schemas

The components only this tag's operations use. Each row is the type's own field, its doc comment the description; a type another page owns links there.

AnnounceInput

object
The input of peers.announce: what the calling peer is working on.
propertytyperequireddescription
intent string yes One line, in the peer's words: the task, the question, the intent.
scope array of string no Repository-relative paths the peer expects to touch. Informational: other peers read it to avoid a collision; nothing here enforces it.
claim string | null no Which of this peer's claims this is, when the peer holds more than one.

One MCP session is not always one piece of work — a client that fans work out to subagents shares its session with all of them — and without a name every announcement replaces the last, so the board ends up describing whichever worker spoke most recently and the rest of the scope silently stops being claimed. Name a claim and it stands beside the others; announce under that name again and it is updated. Leave it out and this is the peer's one unnamed claim, which is what a single session announcing about itself wants.

Used by peers.announce

Announced

object
What announce answers: the peer as recorded, and who else is on that ground.
propertytyperequireddescription
id PeerId → PeerId yes p1, p2, ...
client ClientInfo → ClientInfo yes The client behind it.
transport Transport → Transport yes How it is attached.
connected_at string yes When it attached, RFC 3339, UTC.
last_seen_seconds_ago integer (uint64) yes Seconds since its last message.
attached boolean yes Whether the session is still attached. A peer that announced something and then went away is kept and listed with attached: false: what it said it was working on outlives the connection that said it, because the work does.
claims array of Announcement → Announcement no Every claim this peer holds, in name order, the unnamed one first. Empty for a peer that has announced nothing.

One connection may be doing several things at once — a session that fans work out to subagents shares its MCP session with all of them — and before this field the board could hold only the last thing said, so each announcement silently erased the one before it and the other peers stopped seeing the rest of the scope.

announcement Announcement | null → Announcement no The most recently made of Peer::claims, for a reader that wants one line rather than all of them. Derived from claims and never a second account of it: a peer holding one claim reports the same thing in both, which is what every peer that does not name its claims reports.
checkout PeerCheckout | null → PeerCheckout no Which checkout of the repository this peer is attached to; see PeerCheckout.

The board itself never sets it — a board does not know where it is — and it is stamped by the reader that gathered this peer, which is the one thing that knows which checkout's server it asked.

overlaps array of Overlap → Overlap no Every other peer whose claimed scope meets this one. Empty is the ordinary case.
released array of string no Paths the claim this one replaced had, and this one does not.

Announcing under a name a peer already used replaces that claim, which is what an update is. When the replacement is narrower, the peer has just stopped claiming ground it held a moment ago, and the other peers stop being warned off it. That is occasionally what was meant and is otherwise a mistake nobody would ever see, so it is said here, at the moment it happens, rather than left to be discovered in a collision later. Empty is the ordinary case.

Used by peers.announce

Announcement

object
One claim a peer has made: what it is working on, and where.

A peer may hold several. The name is what tells them apart and what makes announcing again an update rather than a second claim: announce under the same name and the claim is replaced, announce under a new one and the peer now holds both. A peer that never names anything holds exactly one claim, the unnamed one, which is what a single session announcing about itself has always had.

propertytyperequireddescription
name string | null no What this claim is called, when the peer named it. Absent for a peer's unnamed claim.
intent string yes One line: the task or intent, in the peer's words.
scope array of string yes Repository-relative paths the peer expects to touch; informational, never enforced here.
at string yes When it was announced, RFC 3339, UTC.

Used by Announced Peer

BoardView

object
One checkout's board, as the gather found it.

Present for every checkout the answer covers, whether or not its board could be read, because a listing that silently drops the checkouts it could not reach is the shape of answer that made this capability wrong in the first place: a reader would see a short board and conclude nobody else is working here.

propertytyperequireddescription
id string yes The checkout's identity digest, the same value server.status reports as checkout_id. One per checkout, never a path, and stable across a restart.
worktree string yes Where the checkout is. The one thing on this board that a human can act on directly, and the one thing that survives a reconnect.
branch string | null no The branch checked out there, when it could be read.
this_checkout boolean yes Whether this is the checkout of the server answering the call.
standing ServerStanding → ServerStanding yes Where its server stands, by the same reading server.status reports.
url string | null no The address its lease names, when it has one.
attached integer (uint) yes How many of this board's peers are attached.
reason string | null no Why this board is not in the answer. None when it is.

Used by PeerList

ClientInfo

object
What a client said about itself in initialize.
propertytyperequireddescription
name string yes clientInfo.name: claude-code, codex, gemini-cli, whatever the client sends.
version string yes clientInfo.version.
title string | null no clientInfo.title, when the client sends one.

Used by Announced Peer

Overlap

object
Two peers that claimed the same ground.

Reported when the second of them announces, so that a collision is known at the moment it is created rather than discovered afterwards in the history of a branch.

propertytyperequireddescription
peer PeerId → PeerId yes The other peer.
attached boolean yes Whether that peer is still attached. An overlap with a departed peer is a weaker signal than one with a live session, and the reader is told which it is.
intent string yes What it said it was doing.
paths array of OverlapPath → OverlapPath yes The claims that meet: one line per pair, yours and theirs.
checkout PeerCheckout | null → PeerCheckout no Which checkout the other peer is attached to, when the reader knew.

The overlap that matters most is the one nobody could see before: two workers in two worktrees of one repository, each reading a board that held only itself, both claiming apps/majordomus-cli/src. Naming the checkout is what turns "p1 is on your ground" into something a reader can act on when p1 is not on this board.

Used by Announced PeerList

OverlapPath

object
One pair of claims that contain one another.
propertytyperequireddescription
yours string yes The path the announcing peer claimed.
theirs string yes The path the other peer claimed.

Used by Overlap

Peer

object
One peer as the board lists it.
propertytyperequireddescription
id PeerId → PeerId yes p1, p2, ...
client ClientInfo → ClientInfo yes The client behind it.
transport Transport → Transport yes How it is attached.
connected_at string yes When it attached, RFC 3339, UTC.
last_seen_seconds_ago integer (uint64) yes Seconds since its last message.
attached boolean yes Whether the session is still attached. A peer that announced something and then went away is kept and listed with attached: false: what it said it was working on outlives the connection that said it, because the work does.
claims array of Announcement → Announcement no Every claim this peer holds, in name order, the unnamed one first. Empty for a peer that has announced nothing.

One connection may be doing several things at once — a session that fans work out to subagents shares its MCP session with all of them — and before this field the board could hold only the last thing said, so each announcement silently erased the one before it and the other peers stopped seeing the rest of the scope.

announcement Announcement | null → Announcement no The most recently made of Peer::claims, for a reader that wants one line rather than all of them. Derived from claims and never a second account of it: a peer holding one claim reports the same thing in both, which is what every peer that does not name its claims reports.
checkout PeerCheckout | null → PeerCheckout no Which checkout of the repository this peer is attached to; see PeerCheckout.

The board itself never sets it — a board does not know where it is — and it is stamped by the reader that gathered this peer, which is the one thing that knows which checkout's server it asked.

Used by PeerList

PeerCheckout

object
Which checkout of the repository a peer is attached to.

A board is one process's memory and a server serves one checkout, so a peer read from a sibling checkout's server and a peer of this one are otherwise indistinguishable — and worse than indistinguishable, because the ids collide: p1 is the first session of every board, so a repository-wide listing without this field would hold several peers called p1 and no way to tell them apart. This is the qualification. The worktree path is the durable half of a worker's identity: the peer id is a position on one board and is handed out again after a reconnect (p3 this morning, p1 after the bridge re-attached), while the checkout a worker is working in outlives its connection.

Absent on a peer a server reports about its own board without being asked which checkout that is — peers.announce answers about the announcing session and the answer is this server's by construction. The repository-wide listing stamps every peer it gathers, its own included, so that no entry of that answer is unqualified.

use majordomus_cli::peers::PeerCheckout;
let c = PeerCheckout {
    id: "b8293f11".into(),
    worktree: "/repo-wt/feature/x".into(),
    branch: Some("feature/x".into()),
    this_checkout: false,
};
let v = serde_json::to_value(&c).unwrap();
assert_eq!(v["branch"], "feature/x");
assert_eq!(v["this_checkout"], false);
propertytyperequireddescription
id string yes The checkout's identity digest, the same value server.status reports as checkout_id. One per checkout, never a path, and stable across a restart.
worktree string yes Where the checkout is. The one thing on this board that a human can act on directly, and the one thing that survives a reconnect.
branch string | null no The branch checked out there, when it could be read.
this_checkout boolean yes Whether this is the checkout of the server answering the call.

Used by Announced Overlap Peer

PeerList

object
The answer of peers.list: every worker of this repository, from every checkout's board.
propertytyperequireddescription
count integer (uint) yes How many peers are attached across every board that was read, the caller included.
caller PeerId | null → PeerId no The caller's own peer id, when the call came through an MCP session. A position on this checkout's board, not a durable identity: it is handed out again after a reconnect, and Peer::checkout is the half of a worker's identity that is not.
peers array of Peer → Peer yes The peers, this checkout's board first and then the siblings in the order git registers them, each in its own board's attachment order. Every one carries the checkout it belongs to, because p1 is the first session of every board. A peer that announced something and then went away is still here, with attached: false.
overlaps array of Overlap → Overlap no Every pair of peers whose claimed scope meets, each pair once — now across checkouts as well as within one. Empty is the ordinary case, and a reader who sees an entry here is looking at two workers about to do the same work.
boards array of BoardView → BoardView yes One entry per checkout the answer covers, reached or not.
complete boolean yes Whether every board the answer covers was read. false when a checkout's server could not be asked, and BoardView::reason says which and why — so that "nobody else is here" is never reported when the truth is "I could not ask".

Used by peers.list

Transport

"stdio" | "http"
How a peer reached the server.
  • stdioThe owner's own client, on the process's stdin and stdout.
  • httpMCP over HTTP at /mcp: a majordomus mcp bridge, or a client speaking it directly.

Used by Announced Peer

Searches the titles and descriptions of every page this site publishes.