board_read and board_write tools, against the same rows.
Addressing a board
A board is addressed by itsbrd_ id or by the agt_ id of the agent that owns it. That is not a convenience. A board is minted on first use, one per agent, and a board with no cards has never told anyone its brd_ id — so on a brand-new team the agent id is the only reference a caller can have.
One board per agent — unique (org_id, agent_id) — so POST /v1/boards is an ensure, not a create that can conflict. It is also not the only way a board comes into being: the first read or write against the agt_ spelling mints one too.
Every route here is gated by agents:read for reads and agents:write for writes: a board belongs to an agent, and the scope grammar is fixed.
The board object
string
Unique id (
brd_…).string
Always
board.string
The agent that owns the board (
agt_…). One board per agent.object
How many cards stand in each column:
{ "todo": n, "doing": n, "blocked": n, "done": n }. All four keys are always present, so 0 and “absent” never read the same. This is a real aggregate over the board, not the length of a page — which is the one fact a page of cards cannot state, because a page is truncated and a count is not.string
Creation timestamp.
The card object
string
Unique id (
crd_…). Derived from the board, the writing session and the title, so a retried write collides with the row it already made rather than duplicating it.string
Always
board_card.string
The board this card is on (
brd_…).string
Short label, 1–200 characters.
string
The card’s notes — what was done and what is left. A member cannot see another member’s conversation, so this is the hand-off.
string
One of
todo, doing, blocked, done. Fixed — not configurable columns.string | null
A roster member’s name, not an id.
null is unclaimed.string[]
The
crd_ ids this card waits on. A blocked card with an empty list is stuck on the world. When every card in the list reaches done, this card is promoted back to todo on the next read.string
Creation timestamp.
string
Last modification timestamp.
The comment object
string
Unique id (
bcm_…).string
Always
board_comment.string
The card commented on (
crd_…).string
Who wrote it, as an id the server derived — never a name the caller supplied. A person’s comment carries their
usr_ or key_ principal; a member’s, written through board_write, carries the ses_ id of the thread. The prefix is what lets a reader tell an agent’s comment from a person’s.string
The comment text.
string
Creation timestamp.
Ensure a board
POST /v1/boards → the board object. 201 Created when this call minted the row, 200 OK when it found one already there.
string
required
The agent that owns the board (
agt_…). 404 not_found if this organization has no such agent.Get a board
GET /v1/boards/{id} → the board object. {id} is a brd_ id or the owning agt_ id.
This is the read that answers a board’s own id, its owner and how much work stands on it. The cards routes answer cards.
Read an agent’s board
GET /v1/agents/{id}/board → the agent’s cards, in column order (todo, doing, blocked, done) and then oldest first. Mints the board if the agent has none, so a team that has not started answers an empty page rather than a 404.
The response is the standard pagination envelope of card objects.
List a board’s cards
GET /v1/boards/{id}/cards → the same cards, addressed by board reference. {id} is a brd_ id or the owning agt_ id. Same envelope, same column order.
Add a card
POST /v1/boards/{id}/cards → 201 Created with the card object.
string
required
1–200 characters.
string
The card’s notes, up to 8192 characters.
string
todo (default), doing, blocked or done.string | null
A roster member’s name, up to 128 characters. Defaults to
null.string[]
Up to 50
crd_ ids this card waits on.Get one card
GET /v1/boards/{id}/cards/{cid} → the card object. A card that is not on the named board answers 404 not_found, even if the id exists elsewhere in your organization.
Update a card
PATCH /v1/boards/{id}/cards/{cid} → the updated card object. Every field of the create body is accepted and every one is optional, so a move is {"status": "doing"} and nothing else. "assignee": null unclaims the card; omitting the key leaves the current holder alone.
This is an operator’s override, and it is deliberately more powerful than the agents’ own
board_write. A person has the card on screen and outranks the claim guards, which exist only to stop two members stepping on each other — and this is the only way to retitle or reassign a card, because through the tool those are set at create.Read a card’s comments
GET /v1/boards/{id}/cards/{cid}/comments → the card’s comments, oldest first, in the standard pagination envelope. A card’s comments are a conversation, so they are not reversed the way a feed is.
A cid that is not on the named board answers 404 not_found, the same guard the card read sits behind.
Comment on a card
POST /v1/boards/{id}/cards/{cid}/comments → 201 Created with the comment object.
string
required
The comment text, 1–8192 characters.
author field, and sending one is a 400 invalid_request. The server signs the comment with the principal behind your key, so nobody has to invent a name for themselves — or for anyone else. Nothing used to stop an operator signing a comment fact-checker.
The board, in concept
The four statuses, the two tools agents call, and how the board composes with delegation.