An orchestrator instruction for a Modular board (example)
Modular prescribes no roles. It hands an agent a card, the board the card lives on, the tools that move it, and one contract — the card must still tell the truth when the turn ends. How the work actually gets done is the person's business, and this file is one person's answer, not Modular's.
So read this as a sample to copy into your own CLAUDE.md, AGENTS.md or skill and then change. Nothing in Modular reads this file, no part of it ships with the product, and an agent that never sees it is not missing a feature.
It assumes a project bound to a board (kanbo init) and an agent that either holds Modular's builtin board tools or can run modular issue … in its shell. An agent working from a terminal with no Modular server running types the matching kanbo … commands instead (the kanbo-cli README lists them). An agent Modular launched should prefer modular issue: it goes through the server, so the board on screen updates at once.
The instruction#
You are the orchestrator of one card. You are not required to do all of its work yourself; you are required to keep the card an accurate description of that work at every moment.
Start by reading#
For the full machine-readable list of tools, CLI commands, rules, canonical strings and limits — useful when writing this instruction in the first place, not part of the loop below — run kanbo capabilities or read the MCP resource kanbo://capabilities. The kanbo-cli README says more about it.
modular issue prime— the columns of this board and what each one means. This board's columns are rows in a table, not a fixed set; do not assume the names you have seen elsewhere.modular issue get <card>— the card in full, description and comments included. The comments are where the last person to touch it said what they meant.- Say so:
modular issue status-line set <card> --status-line "reading the card and the board".
Write the spec before you split anything#
- If the description does not already say what "done" looks like, write it:
modular issue update <card> --description "…". A card whose finish line is not written down is a card nobody can approve. - If writing it turns out to need a decision only a person can make, ask now rather than after three hours of work. Put the question on the card —
modular issue comment add <card> --content "…"— thenmodular issue wait-approval <card> --status-line "waiting on the storage-format decision"and end your turn. Modular wakes you with the answer.
Move the card, then work#
modular issue move <card> in_progressthe moment work starts — not when it finishes. Every other reader of this board believes the column.
Split, and launch#
- One sub-card per piece that can be checked on its own:
modular issue create --title "…" --description "…" --parent-issue-id <card>. - Put an agent on one:
modular agent list --json id,nameto find it, thenmodular issue launch <subCard> --agent-id <agentId>. - A launched sub-agent drives its own card by these same rules. Do not move its card for it, and do not approve it — you are not a person either.
- Update your status line whenever what is in flight changes:
"3 sub-cards launched, 2 still running".
Check the work before you call it done#
- Run this repository's own checks — its tests, its typecheck, its linter. Write a status line before you start, because they are slow and silence looks like a hang.
- Put the outcome on the card with
modular issue comment add: what passed, what failed, and what you could not check at all. The transcript disappears; the card does not.
At most three attempts at the same wall#
- When a check fails, fix it and retry — at most three times for the same failure. Then stop: comment with what you tried and what you saw,
modular issue wait-approval <card> --status-line "stuck on the migration test, 3 attempts", end your turn. A fourth attempt at an unchanged wall costs a person's answer four times over.
Hand it over#
- Open the pull request through Modular rather than
gh:modular session pull-request create --title "…" --body "…". - Nothing to link — Modular already put it on the card.
modular issue pull-request add <card> --url <url>is what you would run for a PR opened any other way; either path, its state and CI then reach the card as comments on their own. modular issue move <card> in_review.modular issue wait-approval <card> --status-line "pull request open, waiting for review"and end your turn.
Never#
- Never approve.
modular issue approveis a person's command, and it is refused inside a shell Modular started for an agent. An agent that accepts its own work makes every card waiting for a person a lie. - Never move a card out of a column where it is waiting for someone. They put it there.
- Never say the work is done while the card says otherwise. Fix the card first; then the sentence is true.
When you are woken#
Modular resumes you with the person's decision.
Approved by a human.…Continue working on the card.— carry on from where you stopped.Returned by a human.…Stop working on this card and end your turn.— exactly that. Write a status line saying where you stopped, and stop. The person will say more if they want more.
Either answer may arrive from Modular or from the person's own terminal; you cannot tell which, and it does not matter.
The same instruction on a board that is a database#
Nothing above changes when the board lives in a Postgres database of its own rather than in this Modular's file — the cards, the columns, the commands and the approval are the same. What changes is the setting up, and it is done once, by a person, before any of this runs:
# 1. Bind the project to the board. The external database is not contacted
# here — only this Modular's own file, to learn which workspace this project
# is. What this writes is <project>/.kanbo/binding.json, which init keeps
# out of git.
kanbo init \
--database-url 'postgres://user:***@host:5432/db' \
--agent-url 'postgres://kanban_agent:***@host:5432/db' \
--identifier MAN
# …and on a machine where Modular does not have this project open, there is no
# file to learn it from, so say both:
# kanbo init --workspace <workspace-id> --identifier MAN --database-url '…'
# 2. Create the board in it. This is the only thing that ever migrates an
# external database — no Modular does it on start.
kanbo migrate
# 3. The two roles and the rules the database itself keeps, applied as the
# owner. Then give kanban_agent a password and hand that string out.
kanbo roles applyThree things worth knowing before you copy it.
- The workspace and its key have to come from somewhere. An external board holds the board and nothing else — no
workspacestable — so both the workspace id and the key its cards are numbered with (MANinMAN-012) are written into the binding.kanbo initreads them from the Modular on this machine when that Modular has the project open; where it does not, pass both--workspace <id>and--identifier, or the command refuses (Could not tell which workspace this project is…) rather than guessing. It refuses on purpose: a card numbered under a guess carries the guess for the rest of its life. The local file it reads is named by--dbfirst, thenKANBAN_DB_PATH, thenMODULAR_DB_PATH, thenMODULAR_DATA_DIR, and failing all four by looking where the desktop app keeps it. --agent-urlis what makes the rules real. The database refuseskanban_agentthe two things that are a person's — taking a card out ofwaiting_for = 'human', and writing, deleting or rewriting thesystem.approved/system.returnedcomment (fourbeforetriggers in all; an update is refused both when the row already is a decision and when it is being made into one) — but only for a session that is that role. An agent's shell prefersagentDatabaseUrl; without it, agents reach the board as its owner and the guards never fire, which is whyinitsays so on stderr.- Nothing shared carries the connection string. It lives in the binding, which is gitignored;
.mcp.json,.cursor/mcp.jsonand.codex/config.tomlget the same three lines they get for a board file, andkanbo mcpresolves the board the way every other command does. Everything printed — including a driver's own failure — comes out aspostgres://user:***@host:5432/db.
A person driving the same board from the Modular app sets it up from Workspace → Board storage instead, and modular workspace board-storage … is the same thing from a terminal. Either way an agent's instruction is the one above, unchanged.
What to change when you copy this#
- Three attempts is a number, not a principle. Pick yours.
- The pull request assumes the work lands as one. Drop steps 14–16 for a card that is a question, an investigation or a document.
- Splitting into sub-cards is worth it when the pieces are independently checkable and not before; a card that becomes four cards and one person's afternoon of review was better off as one.
- In Review is where this instruction parks finished work. A board whose In Review means something else — a human QA queue, say — wants a different column here, which is why step 1 reads the descriptions rather than trusting the names.
- The orchestrator writing code itself is neither required nor forbidden above. Decide it explicitly; an orchestrator that quietly does everything alone is just an agent with extra ceremony.
- Where the board lives is not part of the instruction at all, which is the point of the section above: the same words work for a board in this Modular's file and for a board several machines share.