Board
A shared to-do list for agents and people. Cards wait their turn, respect dependencies, and survive a restart instead of being lost.
What it is
A board is a shared to-do list for agents and people: you put work on it as cards, and each card gets picked up, worked, and finished. It is a durable, resumable queue of work items, not a sales/CRM pipeline: a card is a piece of work, not a contact or a lead. Where a scheduled task fires the same prompt on a repeating clock, a board card is a one-off piece of work that moves through a status pipeline, can depend on other cards finishing first, and survives a crash or a restart instead of just being lost.
todo → ready → running → done | blocked → archived
A card is promoted from todo to ready once every card it depends on is done. From
ready it’s claimed (by a human, an agent, or automatically) and moves to
running. It ends at done, or at blocked with a reason if something stopped it,
including a claim that stalled or a run that ended without ever saying it was finished.
A blocked card always needs an explicit unblock before it can run again: nothing here
silently retries on its own, because a card is a real agent turn, not a script.
Create a board from the CLI
pepe board add --name "Engineering" --project acme
--auto-dispatch turns on unattended firing: a ready card with an assignee starts on
its own once the board notices it, instead of waiting for someone to claim it. It’s off
by default: see the security note below before turning it on. --claim-timeout-s
controls how long a claim may run before it’s treated as stalled and blocked (default
1800; 0 means never).
pepe board card add acme/eng \
--title "Fix the checkout timeout" \
--body "Everything the assignee needs: this is all it gets, no chat memory." \
--assignee acme/support \
--priority 5 \
--depends-on c_ab12,c_cd34
A card can override its board’s own auto_dispatch, in either direction: --auto- dispatch / --no-auto-dispatch on card add, or pepe board card auto-dispatch ID on|off|inherit on an existing one. A manual claim always works regardless of any of
this: it only decides whether the scheduler’s own tick fires the card unasked.
The full command set:
pepe board list # every board
pepe board add --name N [...] # create a board
pepe board remove ID [--force] # remove (--force drops its cards too)
pepe board card list BOARD_ID [--status S]
pepe board card show ID
pepe board card add BOARD_ID --title T [...] [--auto-dispatch|--no-auto-dispatch]
pepe board card link ID DEP_ID # add a dependency
pepe board card force-ready ID # skip the dependency check
pepe board card auto-dispatch ID on|off|inherit # override this card's own dispatch
pepe board card claim ID [--as NAME]
pepe board card complete ID [--text NOTE]
pepe board card block ID --text REASON
pepe board card heartbeat ID [--as NAME] # reset the stall-timeout clock on a running claim
pepe board card unblock ID
pepe board card comment ID --text NOTE # a note, no status change
pepe board card archive ID [--force] # --force also archives a running card
pepe board card unarchive ID
Do it in the dashboard
Run pepe serve and open the Board page. Pick a board (or create one) to see its
cards grouped into columns by status. From there you can create a card, claim a ready
one, unblock a blocked one, or archive one, including force-archiving something still
running, which is the one action deliberately not available to an agent (see
below). The page updates live as cards change, whether that change came from the
dashboard, the CLI, or an agent working the board.
Do it by chat
An agent manages boards and cards with the board tool, if it’s in its toolset:
Create a board called “Support escalations” and put a card on it for the login bug Sarah reported, assigned to the on-call agent.
When an agent is dispatched to work a card itself (an auto_dispatch board claiming and
running its assignee), it doesn’t need to pass a card id to complete, block, or
comment: Pepe infers it from that session automatically.
auto_approve for board. A card dispatched by an auto-dispatch board has no human attached to approve anything, the same as a scheduled task's unattended run. Without board in the assignee agent's auto_approve list, every complete/block/comment call it makes is silently denied, and the card just sits until the board's claim timeout blocks it.Dependencies and cycles
depends_on names other cards on the same board that must be done first: a
cross-board dependency, an unknown id, or anything that would create a cycle is
rejected when you try to add it. An archived card never satisfies a dependency, only
done does: if something a card is waiting on gets cancelled, the waiting card stays
visibly stuck in todo rather than silently promoting past an abandoned decision.
Claims are race-free
Two callers (a human clicking “Claim” and an agent’s tool call, or two auto-dispatch
ticks) can never both win a claim on the same card. The first one through wins; the
other gets a clean “not ready” error. This holds without any extra locking step on your
part: it’s just how claim is built.
Auto-dispatch and the claim timeout
With auto_dispatch off (the default), a ready card just waits: nothing fires it but
an explicit claim, from the dashboard, the CLI, or an agent. With it on, the board’s
own ticker (about every 30 seconds) claims and dispatches any ready card that has an
assignee, running that agent in a fresh session built around the card. An unassigned
ready card never auto-fires either way.
Any single card can override its board’s own setting: force one card to fire on its
own inside an otherwise manual board, or force one card to stay manual on an otherwise
automatic board. Set it when creating the card, change it later on the dashboard (a
small select on the card itself), the CLI (card auto-dispatch ID on|off|inherit), or
by chat (board set_auto_dispatch).
claim_timeout_s is the safety net for a dispatched run that goes quiet: if a claim
outlives it, the card is blocked with “claim timed out” rather than left claimed
forever. The same thing happens if the dispatched session ends (normally or by
crashing) without ever calling complete or block: that’s treated as a protocol
violation, not silently retried.
For work that genuinely takes longer than claim_timeout_s, call board heartbeat
periodically (or pepe board card heartbeat ID from outside the session). It resets
the stall clock without changing status, so the card isn’t force-blocked while it’s
still actually being worked. It’s a liveness signal, not progress logging: use
comment for updates you want in the card’s history.