Project Board: instructions for AI agents

This repository plans its work in a Project Board file (*.pb): issues, milestones, Kanban columns, labels and requirements in one JSON file that is versioned together with the code. You may read it and you may change it. This sheet tells you how to do both without damaging it.

1. Read the board

  1. Find the file: git ls-files '*.pb'.
  2. Look at pbVersion. This sheet is for "2.1". If the file says anything else, change nothing and tell the user: an older file has to be upgraded first, a newer one needs a newer tool.
  3. Read both parts of the file together:
    • tree is the board as it was when the file was last consolidated;
    • wal is the list of changes made since then, one event per line, oldest first.

    The board is tree with every event of wal applied in order. An issue that an event in wal closed still says "state": "open" in tree. Never answer from tree alone.

  4. If you can run commands, let the tool do the replay: node <extension folder>/out/cli.js show <file> --json.

Where things are in tree:

Member Holds
project, owner name and description of the project; who owns the board
workflow the Kanban columns in board order; each has an id, a name and a category (todo, doing, done)
labels id, name, color, description
milestones id, title, description, dueDate, state (open, closed)
issues id, number, kind (task, requirement, heading), title, body, state (open, closed), assignee, labelIds, milestoneId (null: backlog), status (the id of the Kanban column), parentId, startDate and dueDate (planned dates, YYYY-MM-DD or null), comments, links
activity the history of the board, newest first

People say “#12”; events say iss-…. Events name issues, labels, milestones and columns by id, never by number or by name, so look the id up first. Something that was created in wal has its id in its create event.

2. Change the board with the command line tool

This is the safer way: the tool looks up ids, numbers the events and writes the lines. When the user has the board open in the editor, the tool hands the change to that editor instead of writing the file, so the user sees it at once and nothing they are typing is lost.

pb <command> [file] …            in a VS Code terminal, where the extension puts pb on the PATH
node <extension folder>/out/cli.js <command> [file] …      anywhere else

pb show [--json]
pb issues [--state open|closed|all] [--milestone <title>] [--label <name>] [--search <text>] [--json]
pb issue <number> [--json]
pb add-issue "<title>" [--body <b> | --body-file <path or ->] [--label <name>]… [--milestone <title>] [--kind task|requirement|heading]
pb edit <number> [--title <t>] [--body <b>] [--milestone <title|none>] [--add-label <name>] [--remove-label <name>] [--assignee <name>] [--start <date|none>] [--due <date|none>]
pb comment <number> --body <b> | --body-file <path or ->
pb close <number>… [--comment <text>]
pb reopen <number>…
pb move <number> <column name | todo | doing | done>
pb link <number> <satisfies|derives|verifies|refines|depends|conflicts|relates> <target number>
pb add-milestone "<title>" [--due <yyyy-mm-dd>]
pb apply --actions <json file or ->       a list of { action, payload, ref? }
pb apply --events <json file or ->        raw events as in section 3, without seq
pb validate | upgrade | consolidate | where
pb report --out <dir> [--format <id>]
pb export --format <id> [--out <path>]
pb import --from <path> [--format <id>] [--dry-run]

cli.md has the full reference.

3. Change the board by appending event lines

Use this when you cannot run the tool. An event is one line of JSON at the end of the wal list.

  1. Find the last line of wal, the line above the closing ` ] near the end of the file, and read its seq`. Your first event gets that number plus one, and each further event one more.
  2. Write each event on one line: four spaces, a comma, then the event as compact JSON without line breaks. Put your lines directly above the closing ` ]`. Do not change anything else in the file.
  3. Give every event seq, at (the current time in UTC, written like 2026-03-04T10:00:00.000Z), by (your own name), op, and the members of that operation.
  4. Give everything you create a new id: a prefix, a dash and 12 random hexadecimal digits, for example from openssl rand -hex 6. The prefixes are iss (issue), c (comment), ms (milestone), lbl (label) and lnk (link). Do not copy the ids of the examples below.
  5. Read the board again and check that the file is still valid JSON and that your change is there.

The examples continue a board whose last line has seq 6. It has an issue #2 with the id iss-2b4d6f8a0c1e, a label “bug” with the id lbl-5e001f110423, a milestone “v1.0” with the id ms-1f6a0c93b2d4 and a column “In progress” with the id doing.

Open an issue. Leave number out and the issue gets the next free number, here #3. The body is Markdown; line breaks are written as \n.

,{"seq":7,"at":"2026-03-04T10:00:00.000Z","by":"Claude","op":"issue.create","issue":{"id":"iss-4f7d2c9b1a3e","title":"Reject CSV files larger than 10 MB","body":"Found while reading `src/import.ts`: the whole file is read into memory.\n\n- [ ] check the size before reading\n- [ ] show a clear error","kind":"task"}}

Comment on an issue. issueId is the issue, comment.id is new.

,{"seq":8,"at":"2026-03-04T10:01:00.000Z","by":"Claude","op":"comment.add","issueId":"iss-2b4d6f8a0c1e","comment":{"id":"c-8e1f3a5b7c9d","body":"Proposed layout: `docs/<milestone>.md`, one `##` section per issue."}}

Close an issue, and reopen it. The card moves to the Done column, and back, on its own.

,{"seq":9,"at":"2026-03-04T10:02:00.000Z","by":"Claude","op":"issue.update","id":"iss-2b4d6f8a0c1e","set":{"state":"closed"}}
,{"seq":10,"at":"2026-03-04T10:03:00.000Z","by":"Claude","op":"issue.update","id":"iss-2b4d6f8a0c1e","set":{"state":"open"}}

Put an issue into a milestone and a Kanban column. milestoneId null is the backlog; status is the id of a column of tree.workflow.

,{"seq":11,"at":"2026-03-04T10:04:00.000Z","by":"Claude","op":"issue.update","id":"iss-4f7d2c9b1a3e","set":{"milestoneId":"ms-1f6a0c93b2d4","status":"doing"}}

Add a label to an issue. labelIds replaces the whole list: write every label the issue is to have afterwards, the ones it already has included.

,{"seq":12,"at":"2026-03-04T10:05:00.000Z","by":"Claude","op":"issue.update","id":"iss-4f7d2c9b1a3e","set":{"labelIds":["lbl-5e001f110423"]}}

Create a milestone, and close it. Dates are written as YYYY-MM-DD.

,{"seq":13,"at":"2026-03-04T10:06:00.000Z","by":"Claude","op":"milestone.create","milestone":{"id":"ms-6b0d2f4a8c1e","title":"v1.1","description":"Import and export.","dueDate":"2026-06-30"}}
,{"seq":14,"at":"2026-03-04T10:07:00.000Z","by":"Claude","op":"milestone.update","id":"ms-6b0d2f4a8c1e","set":{"state":"closed"}}

Create a requirement and link a task to it. A requirement is an issue with "kind":"requirement". The link is added to the task (issueId) and points at the requirement (targetId): the task satisfies the requirement. The other link types are derives, verifies, refines, depends, conflicts and relates.

,{"seq":15,"at":"2026-03-04T10:08:00.000Z","by":"Claude","op":"issue.create","issue":{"id":"iss-7a9c1e3b5d2f","kind":"requirement","title":"The importer shall refuse files larger than 10 MB","body":"The limit is checked before the file is read."}}
,{"seq":16,"at":"2026-03-04T10:09:00.000Z","by":"Claude","op":"link.add","issueId":"iss-4f7d2c9b1a3e","link":{"id":"lnk-3c5e7a9b1d4f","type":"satisfies","targetId":"iss-7a9c1e3b5d2f"}}

Other fields that set of an issue.update accepts: title, body, assignee, kind, parentId, attrs. Every other operation (labels, attachments, baselines, deleting things) is in the reference.

4. Rules

  1. Append only. Never rewrite, reorder or delete an existing line of wal. To correct something, append another event that sets it right.
  2. Never edit tree by hand. It changes only through consolidation, and consolidation is the user’s call.
  3. Never reuse an id or a seq. A create event with an id that exists is ignored.
  4. One event per line, one line per event. That keeps the changes of two branches mergeable.
  5. Keep the file valid JSON. A file that does not parse cannot be opened at all. Check it after every edit.
  6. Write Markdown in bodies: issue descriptions, comments and milestone descriptions. Titles are plain text.
  7. Sign as yourself. by is the name of whoever made the change. An agent writes its own name, for example Claude or Copilot, and never the name of the user or of a teammate.
  8. Leave remotes and the connection.… events alone. They are the bookkeeping of the synchronization with remote trackers.

5. When an event is wrong

The reader of the file is tolerant: an event with an unknown op, with a target that does not exist or with an id that is already taken is skipped with a warning, and the rest of the board is read as usual. The file is not damaged, but your change did not happen. So after writing, read the board again (show, or tree plus wal) and make sure the result is what you meant. If it is not, append a corrected event; do not edit the line you wrote once it has been committed.