Files
hq/00-META/process/07-feature-branches.md
T

29 KiB

Playbook 07 — Features

Trigger. A feature: work that changes code in one repository or several (mesh-sdk, mesh-controller, mesh-catalog, mesh-host, mesh-lab, and hq when a design rides along), too large to be one small fix. A small fix (one repository, one issue, about an hour of work) follows playbook 08 instead.

Who runs it. Anyone who writes code, engineers and agents alike. Agents follow it exactly.

The failures it prevents

  • Pull requests as scratchpads. A feature worked as one pull request per unit of thought left sixteen stacked pull requests unmerged, consolidated by hand afterwards. A pull request is the end of a feature, not a step in it.
  • Waiting on checks mid-feature. A feature worked as one pull request per issue into protected branches spent most of a day waiting for merge checks while the code was ready.
  • Context bought again and again. A fresh agent per small issue spends most of its run reading the playbook, the issue and the code before it writes a line. On 2026-10-14 an issue sized for five to ten minutes took forty-one.
  • Finished work that never ships. A feature that kept taking new issues held thirteen landed issues undelivered for a day: an issue done at 21:09 was still not on the machines at 15:00 the next day.
  • Work that waits and nobody notices. Most lost time was not work at all, but a check, a delivery plan or an agent waiting on something that had stopped, unseen.
  • Review that keeps hardening. One fix went through seven review rounds in five and a half hours, each asking for more hardening than its issue asked (issue 704, 2026-10-14). A round is for what makes the change wrong; everything else is an issue of its own.
  • The first check after the whole branch is written. A feature's catalogue branch, pushed once after hours of local work, met its first check on 2026-10-16 and failed on 29 problems, TypeScript errors among them, that a unit suite run continuously while working finds at once.
  • A green pull request nobody marks. An approved, green pull request that was not marked ready waited 12 h 17 min for someone to merge it (mesh-catalog #300, 2026-10-14). There is no ready mark since ADR 0421: the operator's Approve is asked as soon as the checks pass.
  • A module proven only after it reached the machines. The mail modules were delivered with unit tests only, then failed live three times (issues 859, 865, 868) and took five more hours of fixes.

The shape

One feature is one branch name, one worktree per repository, one pull request per repository, opened once, after the review. It runs in five steps, and its time is bounded. A milestone has no phases (the operator, 2026-10-17): these are its steps, and a report names the step it is in:

Step What happens Bound
0. Prepare the lead settles the open questions and writes one context file per repository 15 minutes
1. Develop one fixing agent per repository builds every issue locally, the whole unit suite before every commit the milestone's issues, 30 minutes each
2. Review one reviewer reads all of it; reports go both ways; at most 3 rounds 15 minutes
3. Check push; the forge's merge checks run the integration tests; no further review one check run
4. Deliver merge; the delivery plan builds, publishes and sends the whole feature its delivery plan

One milestone is one release (the operator, 2026-10-17). A milestone is developed whole, reviewed once, pushed once and delivered once. It is never cut into steps for its size or for time: every cut adds a review, a merge check, an Approve and a delivery plan of waiting, so the fewest cuts is the fastest. Milestones are cut by release order when the design is broken up (playbook 04); a release-order dependency found inside a milestone is a fault of that cut, said to the operator, never a silent extra step.

One design is built at a time (the operator, 2026-10-17). While the milestones of one design are being built, no milestone of another design starts: its issues stay open, and nobody moves them to diagnosing or fixing. The next design starts when the last milestone of the one being built has its delivery plan done. A lead who finds another design's milestone already in fixing asks its owner before starting its own. A small fix (playbook 08) and an issue that is in no design's milestones are not held by this rule.

Scope is fixed at the prepare step. An issue found during a feature goes to the next milestone or the next feature, never into the running one, unless it blocks the running one.

Step 0 — Prepare (the lead, larger model)

  1. Name the feature once: feat/<slug>, the same branch name in every repository it touches (but see the exception for a new manifest word in phase 3: a release that must merge first has a name of its own). The branch is never protected. One milestone is one branch per repository (the operator, 2026-10-17), with one folder .work/<slug>/; never a second name for part of it (-2, -step2).
  2. Make the worktrees: one per touched repository under .work/<slug>/<repo>, branched off main (git worktree add .work/<slug>/<repo> -b feat/<slug> origin/main). The lab beds find the other repositories by sibling path, so add the untouched siblings the lab reads as detached worktrees on main (git worktree add --detach .work/<slug>/mesh-tools main), never symlinks. A repository's own clone stays on main (the operator, 2026-10-17): no work is done in it and no branch is checked out there, so a session that opens in it reads what holds now; fetch and fast-forward it before reading.
  3. Settle every open question in the feature's issues before any agent starts: on the issue, through the knowledge base, or by asking the operator. A fixing agent that meets an undecided design question stops, and its run is spent again.
  4. Write one context file per repository, .work/<slug>/<repo>.context.md. It is what a fixing agent reads instead of rediscovering the code, and it is reused by every agent and every round:
    • the issues it builds, in order, each with: the files and functions it changes, the decisions it builds with (quoted, with where each was decided), the test to write first, done-when in one sentence;
    • the repository's map for this feature: the packages and files involved, with one line each on what they hold, and the patterns to copy (file:line);
    • what not to touch, and what lands beside it in other repositories;
    • the local facts: the worktree, the branch, the check commands, a temporary directory for tests outside the home (TMPDIR), and the unit tests to run (never a database server: tests that need one are integration tests, which the forge's checks run). When the reading is large, a read-only agent on the smaller model answers the lead's exact questions with file:line, and the lead writes the file from its answers.
  5. Write the findings into the issues (the operator, 2026-10-17: the better the content of the issues, the better work the agents do, and the less review is needed). Before any agent starts, each issue carries, with issue-tracker.note: every file and test that touches the change, found by search and not from memory (file:line); how the change is checked and delivered end to end (which test proves its done-when, which modules its delivery plan moves, to which machines); and for a group, its merge order. The context file points to these notes; an issue reread by another session or a later agent then holds what was found. When the reading is large, it starts with a read-only survey agent on the smaller model. The survey sizes each issue, and an issue sized over an hour is split before any agent starts (the operator, 2026-10-17), into issues of about 30 minutes each, one change each, in the order they land: the bound of a fixing agent's issue is then a real bound. On 2026-10-17 one issue held four changes sized at four to six hours; it ran past its 30-minute bound for over an hour, and the reports could only say "still on part D".
  6. Move the issues to fixing, by naming the feature's agent for that repository.

Step 1 — Develop (one fixing agent per repository, smaller model)

  • One agent per repository, kept for the whole feature. It reads its context file once and builds the issues one after another, committing each locally with a message naming the issue ("Keep the assignments on the bus (issue 652)"). It is not replaced per issue: the lead continues the same agent (by its id) with the next instruction, so its context is paid for once.
  • Unit tests only, each well under a second (how-we-build §5, ADR 0391). The whole unit suite runs before every commit (the operator, 2026-10-17), not only the tests of what changed: it takes under a second, and a change breaks tests in packages it never touched. Each issue's first test fails before its change and passes after; gofmt, go vet and the whole unit suite stay green. The integration tests (merge-check.sh with the store and the bus) wait for the check step.
  • Nothing is pushed and no pull request is opened in this step. Never land on a red check: run the checks, read the result, then land in a separate step.
  • The fixing agent's brief (the lead gives it with the context file): name your issue in every step and message ("issue 805: landing in mesh-catalog"); reach the mesh only through the mesh MCP server, never ssh; never push, open, merge or close a pull request; never write to a store's database by hand, a schema change is a numbered migration and the consolidated schema; Go only unless the context file says otherwise; never move another issue; never run an integration test locally (no go test -tags integration, no merge-check.sh, no bus or store test run): compile them with go vet -tags integration ./... and leave the running to the forge's merge check. On 2026-10-17 three fixing agents ran them anyway, one for minutes on end, because the brief only said they wait for the check (the operator). Report back in under 150 words: commits, tests added and that they failed first, the check results, anything you stopped on.
  • An agent that finds the code differs from its context file stops and reports (file:line); the lead settles it, updates the context file, and continues the agent.
  • Two repositories that depend on each other keep their order in the context files: the reader before the writer, the controller's new manifest word before the catalogue manifest that uses it.
  • Several agents in one repository (only for a large repository with independent areas): each works on a local branch of its own from the feature branch's head and lands by fast-forward under a lock (flock <feature worktree>.lock git -C <feature worktree> merge --ff-only <its branch>), after its unit tests pass; if another landed first, it merges again and repeats. In a shared clone, fetch the feature branch by name before branching from it. Never resolve a conflict with a blind git merge-file --union.

Step 2 — Review (one reviewer, larger model; at most 3 rounds, about 15 minutes)

  1. Bring every feature branch up to the current trunk (fetch by name, then merge), so the reviewed tree is the tree that is pushed.
  2. One reviewer reads every repository's local commits together, against the context files and the decisions. It is one agent, kept for the whole release like the fixing agents: the lead continues it by its id for every later round, for the extra cycle after a failed check (step 3) and for its review record, so it reads the decisions, the issues, the context files and the commits once (issue 1042). A fresh reviewer per round or per mark pays for all of that again. Its report is per repository: each finding as file:line, the failure it causes, and the fix. It says which round it is in ("round 1 of at most 3").
  3. The report goes to the same fixing agents that wrote the code, continued by their ids, so they fix with the context they already hold. Each answers the reviewer: what it changed, per finding, or why not.
  4. The reviewer reads the answers and the new commits, and approves or reports again (step 5 says what a round may ask). After round 3 the lead stops and brings the operator what is left, with a recommendation.
  5. A finding outside the issues' done-when is never a change asked, in any round. More hardening, a further case, a nicer form: the reviewer opens it with issue-tracker.open and names the number in its report, and the round does not wait for it. What a round asks is only what makes the change wrong against its issues and its decisions.

Step 3 — Check (no review)

  1. Push every feature branch and open one pull request per repository, together. The integration tests run only in the merge check (the operator, 2026-10-16), never before. Pull requests in two or more repositories that share the branch name are one delivery group (ADR 0239 §2). Where a session pushes through the forge tool gitea_commit (one commit on the forge's current base), the reviewer first confirms that commit's tree equals the reviewed one. Each pull request's description carries the review record (ADR 0421 §5), one line the lead writes from the reviewer's approval: Reviewed-by: <who@node> at <full sha> (the issue 1022 note gives its form). mesh-delivery reads it at every new head and puts it in the Approve's ask; a missing line, or one naming another commit, is said first in the ask. It is never a gate.
  2. The forge's merge checks run the integration tests once (mesh/merge-gate, mesh/repo-check). No second review: step 2 reviewed this tree.
  3. A check that fails goes back to the same reviewer, continued by its id, with the failure, for one more cycle of step 2 with the same fixing agents; then push once again. The failure is read from the check's whole log, never reasoned from its summary: mesh-delivery.show names the check's build id, and mesh-controller.builds with log set to that id prints everything the builder said. A check's summary picks only some failing lines; on 2026-10-17 it showed a store test's FAIL without its cause, and a fix was first guessed from the code. A change to merge-check.sh is judged by main's version of it, so a test the change adds must pass under either version: one that runs the new script's behaviour runs a file of its own, never merge-check.sh itself.
  4. The milestone's acceptance is proven by its check. A milestone that adds or changes a module carries an integration test that starts the module whole, its process against the real bus and store, and shows the milestone's acceptance sentence; the forge's checks run it (ADR 0391 §1, §6). Unit tests alone are not that proof; the mesh has no end-to-end tests yet, and the lab is not used (the operator, 2026-10-16).
  5. The operator's Approve is asked as soon as the checks pass (ADR 0421). Nobody marks, labels, comments or rechecks: mesh-delivery asks the Approve on Telegram, the channel that proves who answered today (ADR 0398 §3; an Approve at the terminal is issue 953), once both checks passed, for a delivery group once for all its members, naming the review record. On the Approve it merges the approved heads, a group's in one pass (ADR 0418). A new head ends the Approve and is asked again once its checks pass. A watcher follows the checks, the Approve and the merge, and wakes the lead. Beyond that Approve, the operator's word is waited for only when the milestone changes what the operator sees, removes data, or needs a step only the operator's terminal can take; the lead says which before the milestone starts.

One exception: a catalogue change that uses a new manifest word. The catalogue's merge check reads manifests with the controller the mesh runs, so the controller's pull request is merged and delivered first; then mesh-delivery.recheck the catalogue's and merge it on the new verdict. Such work is two releases, the controller's first, and playbook 04 cuts its milestones that way. A new key the node-engine checks is such a word too (issue 1051): the controller judges every machine with the node-engine's own validator, vendored at the commit go.mod's replace pins, so a key added to the node-engine is refused by the gate until the controller's pin moves to the delivered node-engine (go mod vendor); the feature is then three releases, the node-engine's, the controller's, the catalogue's. Every release that must merge before the catalogue's is on a branch name of its own (feat/<slug>-node-engine, feat/<slug>-controller): on the feature's name it joins the catalogue's delivery group, and a group merges only whole, so neither merges. The exception ends when milestone 3 of design 47 is delivered (issue 977, ADR 0402, ADR 0403): the group's composed check, judged by the group's controller, is then each member's gate, and the feature is one group.

Step 4 — Deliver

The merge starts the delivery plan: build, publish, send. A merge into a branch other than main is not a delivery: no machine receives it (issue 889 while it still starts one). The lead follows it to done (mesh-controller.plans with wait and until: done), then resolves the feature's issues with fixed-by naming its pull requests, and removes what is left: no feat/<slug> branch and no .work/<slug> worktree survive.

Models

Who Model Why
The lead (step 0 decisions, the context files, following and reporting) larger judgement
Read-only questions about the code for the context files smaller reading, answered with file:line
Fixing agents (step 1, and the fixing in steps 2 and 3) smaller building to a written context
The reviewer (step 2, the extra cycle in step 3 and the review record; one agent, continued by its id) larger judgement, once per round
Decision records larger judgement

Every agent the mesh runs draws on one weekly allowance shared by every session, and the larger model spends it many times faster. Up to five agents on the smaller model at once, at most two subagents on the larger one (the lead not counted).

Following and reporting

Most lost time is waiting nobody saw. The lead therefore keeps a wait list: everything the feature waits on, with when it started and its bound.

Waiting on Bound Past the bound
a fixing agent's issue 30 minutes ask it where it stands; split or take over
the review 15 minutes a round ask the reviewer; stop at round 3
a merge check 15 minutes read the build queue (mesh-controller.queue) and the check's log
the Approve's ask 5 minutes after both checks pass read mesh-delivery.show: an ask not raised is a fault, said to the operator
a merge after the Approve 5 minutes read mesh-delivery.show for the delivery
a delivery plan its class's budget (mesh-delivery.times), at most 30 minutes read mesh-controller.plans, then mesh-delivery.stalled

Every wait on the list has a watcher (the operator, 2026-10-17). A session runs only when a message reaches it, so a check, a merge, a delivery plan, a record's review or the operator's Approve on a channel that lands while the lead's turn has ended wakes nobody: on 2026-10-17 an approved group sat half merged for 14 minutes unseen (issue 1032). For each wait the lead starts a watcher, an agent on the smaller model that changes nothing, never the smallest (the operator, 2026-10-17). It loops mesh_wait on the event families of what it watches (mesh-delivery.transition and mesh-delivery.group for a delivery, for example) with a timeout under 60 seconds, since the call itself ends near a minute. After every answer or timeout it reads the state from the verb that owns it (mesh-delivery.show, mesh-controller.plans, knowledge-base.get), because an event only wakes it (ADR 0367). It finishes, and so wakes the lead, when the state changes or the wait passes its bound. The lead never sleeps or polls by hand to wait.

Every ten minutes (the operator, 2026-10-17), every two minutes while a delivery plan runs, and at once when something lands, stops or needs the operator, the lead reports, in one of the two layouts below (the operator, 2026-10-16: an overview of the issues, often). Times are local and absolute, from the clock (date), never guessed; every number has its unit and its bound or baseline; an issue is named by its number and a few words, never by its number alone.

The full layout (in the session) is the whole design as a tree (the operator, 2026-10-17: always the full overview): every milestone of the design with its state, and under the running milestone its repositories and their issues, each issue under the one it waits for, marked ✓ landed or delivered, ▶ fixing, ⏸ waiting, ✗ stopped:

Feature <slug> · milestone <n> of <m> · step <name>, <minutes used> of <bound> min
Issues: <landed> of <total> landed · <fixing> fixing · <waiting> waiting

Design <nn> · <its title>
├── ✓ milestone 1  <its outcome, a few words>                 delivered 12:58
├── ▶ milestone 2  <its outcome, a few words>
│   ├── mesh-controller (agent: fixing, 2 of 3 landed)
│   │   ├── ✓ 652  assignments on the bus                      landed 14:02
│   │   ├── ▶ 736  placement words leave the layers            fixing since 13:51, 23 of 30 min
│   │   │   └── ⏸ 751  config keys named (mesh-catalog)        waits for 736
│   │   └── ✓ 654  a file names its template                   landed 13:20
│   └── mesh-catalog (agent: waiting)
│       └── ⏸ 751  (above, under 736)
└── ⏸ milestone 3  <its outcome, a few words>                 <its issues>

Opened along the way: <each issue opened during the feature, its number and a few words, and where it goes> — or "none"
Past its bound: <the wait, how long, what is being done> — or "none"
Needs you: <one line per question, with my recommendation> — or "nothing"
Allowance: week <n> % (<+d> since the last report), session <n> %
Next: <the next actions up to the delivery, in order>

The short layout (on a channel such as Telegram, or when nothing changed but time):

<slug> milestone <n>/<m> · <step> <used>/<bound> min · <landed>/<total> landed · past bound: <none | what> · needs you: <nothing | what> · week <n> %

A report says what changed since the last one first. In the session every report is the full layout, the whole tree of the milestone's issues included, also when only time passed: the operator reads the state from it without asking. The short layout is for a channel.

The context of the lead's own session

At the end of each step, and whenever the session's context grows large or spread over unrelated work, the lead prepares a /clear as the mesh's instruction "prepare before clear" says: a save point with the feature's step, its branches and heads, the context files' paths, the agents' ids, the wait list and the next action; then the prompt the operator pastes. The context files make the feature resumable after a clear.

The design's state is written back

Other sessions know what is built only from the design's frontmatter and the issue tracker; what the lead knows and does not write back, nobody else knows. A design merged in one delivery with three of its tasks left, its frontmatter still saying designed, read to another session as not built, and a second design was started beside it (issue 1009, 2026-10-17). So the lead writes the design's state back at each of these moments, the same hour, an hq change going through playbook 08:

  • The first milestone starts: the design's status: in-progress (playbook 04 step 7), if it is not already.
  • A milestone's delivery plan is done: its issues are resolved (step 4), with fixed-by naming its pull requests. The design's milestone entry is not changed: it names the milestone's issues, and whether they are delivered, when and by what is read from the issue tracker when it is asked, never copied into the design (issue 1038; AGENTS.md: cross-cutting views are generated when they are read).
  • The building pauses with work left (another design takes its turn, a hand-over, the session's end): the design stays in-progress, and each issue left gets a note with issue-tracker.note saying what it waits for and what comes after it ("built after design N is delivered, before design M"). The first issue of a design that waits its turn gets the same note.
  • The last milestone is delivered: playbook 02, "When something ships": the as-is is written and the design says implemented.

Issues that stop

An issue stays in fixing only while an agent works on it. A milestone that stops, for a wait past its bound, a hand-over, or the session's end, moves each of its issues not delivered back to open with issue-tracker.move and a note saying how far it got (its branch, its last commit, what is left). A list of issues in fixing that nobody works on reads to the operator as stuck work.

How it is checked

  • A feature's issues show their commits on the feature branch (git log --grep 'issue <n>'), and no pull request into a feature branch exists. A pull request per issue, or an agent waiting on a merge check in step 1, is the failure this playbook prevents.
  • A context file exists per touched repository under .work/<slug>/ while the feature runs.
  • No issue of a running milestone is sized over an hour in its prepare note; an issue whose fixing agent passes twice its bound was not split.
  • Every issue of a running milestone has a note from the prepare step naming its files and tests (file:line), how it is checked and delivered, and for a group its merge order (issue-tracker.get); an issue moved to fixing without one is a prepare step skipped.
  • Every fixing agent's report gives the whole unit suite's result and time; a report naming only some packages' tests is the whole-suite rule not kept.
  • The lead's save point names each watcher with its model; a watcher on the smallest model is the failure of "Following and reporting".
  • A report or a save point that names a "phase" of a milestone is the old wording: a milestone has steps.
  • Every repository's own clone says main (git -C <clone> branch --show-current); another branch there is work done outside a worktree (issue 1010).
  • After a feature's delivery, git branch -r | grep feat/<slug> and git worktree list return nothing for it.
  • A design whose milestone issues are partly resolved says in-progress, and one whose issues are all resolved and delivered says implemented: read its Milestones section against issue-tracker.get for each issue. A design that says designed with a resolved milestone issue is a state not written back.
  • A design's Milestones section names each milestone's issues and nothing of their state: a "delivered", a date or a pull request in an entry is a copy of the tracker's state (issue 1038), read with grep -n 'delivered' <design> over its Milestones section.
  • Every open issue of a design that says in-progress and has no agent on it shows, in issue-tracker.get, a note saying what it waits for; one without such a note is a pause not written back.
  • The milestone issues in fixing (issue-tracker.list) belong to one design at most: milestone issues of two designs in fixing at once is the failure of "one design is built at a time".
  • More than one open pull request in a repository that share no feature name, or a milestone whose issues stay in fixing for more than a working day, is the smell: stop and bring it to the operator.
  • A review report that asks a change outside the issues' done-when, or a fourth round, is the failure of the review rules; issue-tracker.list with flag: abandoned lists issues left in fixing with nobody on them.
  • A pull request green for more than 5 minutes with no Approve asked is a fault of mesh-delivery (mesh-delivery.show), said to the operator.
  • Every review record of a release names one reviewer (mesh-delivery.show, the review record), and the lead's save point names one reviewer id per release; a second reviewer within one release is a reviewer not kept (issue 1042). A pull request whose ask says it has no review record at its head is a record not written.
  • A milestone that adds or changes a module whose pull request has no integration test that starts the module whole is the failure of the check step's fourth item.
  • A bed run from .work/<slug>/mesh-lab that fails naming a .work/<slug>/<repo>/… path it cannot find is a missing sibling worktree, not a mesh fault.

When this playbook meets a problem, the lesson is added here, so the next feature avoids it.