mesh/merge-gate head 7b5f3582be90 merged into trunk 6dcfa2226e63 beside mesh-controller#357@114c99e0 +1 more — pass: the change touches no module of the me…
mesh/repo-check head 7b5f3582be90 merged into trunk 6dcfa2226e63 beside mesh-controller#357@114c99e0 +1 more — pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group feat/913-mark-ready delivered: every member is delivered
278 lines
19 KiB
Markdown
278 lines
19 KiB
Markdown
# 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](08-small-fixes.md) 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).
|
|
- **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 phases, and its time is bounded:
|
|
|
|
| Phase | 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, unit tests only | 1 to 2 hours |
|
|
| 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 |
|
|
|
|
A feature that cannot be developed in about two hours is split into **steps**. Each step runs all five
|
|
phases on its own; the next step starts only when the previous step's delivery plan is done. A step is the
|
|
unit that ships, so nothing finished waits for work that is not.
|
|
|
|
**Scope is fixed at phase 0.** An issue found during a feature goes to the next step or the next feature,
|
|
never into the running one, unless it blocks the running one.
|
|
|
|
## Phase 0 — Prepare (the lead, larger model)
|
|
|
|
1. **Name the feature once:** `feat/<slug>`, the same branch name in every repository it touches. The
|
|
branch is never protected.
|
|
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.
|
|
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. **Move the issues to `fixing`**, `by` naming the feature's agent for that repository.
|
|
|
|
## Phase 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, of what changed, each well under a second** (how-we-build §5, ADR 0391). Each issue's
|
|
first test fails before its change and passes after; `gofmt`, `go vet`
|
|
and the repository's unit tests stay green. The integration tests (`merge-check.sh` with the store and the
|
|
bus) wait for phase 3.
|
|
- **Nothing is pushed** and no pull request is opened in this phase. 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. 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`.
|
|
|
|
## Phase 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. 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.
|
|
|
|
## Is the coast clear?
|
|
|
|
Before the push of phase 3 (and of a small fix, playbook [08](08-small-fixes.md) step 5), the lead checks, for every
|
|
repository the change touches, whether a **release branch** is in flight there: a branch other than `main` that
|
|
collects a feature's merged work to bring it into `main` as one delivery.
|
|
|
|
1. **Look for one.** A pull request into `main` whose head is a release branch (`gitea.gitea_open_pulls`), a
|
|
release branch holding merged work that `main` does not have yet (`git log origin/main..origin/<branch>`), or
|
|
a delivery plan of that release branch still running (`mesh-controller.plans`).
|
|
2. **Compare the files.** `git diff --name-only origin/main...origin/<release branch>` against the files the
|
|
change touches.
|
|
3. **Overlap: hold.** If the release branch touches a file the change touches, nothing is pushed into that repository
|
|
until the release branch's delivery plan is done. Then the feature branch is brought up to the new `main`, the unit
|
|
tests run again, and the push follows. The hold is on the wait list (its bound: the release branch's delivery plan),
|
|
and is said to the operator with what the change waits for.
|
|
4. **No overlap: go on.** A release branch in another repository, or one that shares no file with the change, does not
|
|
hold it; its merge joins the merge window as any other (ADR 0276).
|
|
|
|
Merging beside a release branch still in flight would make its lead resolve someone else's conflicts in the
|
|
middle of it, and could send part of the change with its delivery (2026-10-16: the file module's
|
|
controller part touched four files feature 57's release branch also changed).
|
|
|
|
## Phase 3 — Check (no review)
|
|
|
|
1. **Push every feature branch and open one pull request per repository, together, once the coast is clear**
|
|
(above). 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.
|
|
2. **The forge's merge checks run the integration tests once** (`mesh/merge-gate`, `mesh/repo-check`). No
|
|
second review: phase 2 reviewed this tree.
|
|
3. **A check that fails** goes back to the reviewer with the failure, for **one more cycle** of phase 2 with
|
|
the same fixing agents; then push once again.
|
|
4. **The step's acceptance is proven before it is marked ready.** A step 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 step'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 reviewer marks each pull request ready as soon as the acceptance is shown** with
|
|
`mesh-delivery.mark-ready` (the delivery of the head it approved, who reviewed it and why), which writes the
|
|
label `mesh/ready` and then `ready at <full sha>`, the order the forge counts (issue 913); mesh-delivery merges it when its checks pass
|
|
(ADR 0364). The operator's word is waited for only when the step changes what the operator sees, removes
|
|
data, or needs a step only the operator's terminal can take; the lead says which before the step 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. Plan such a feature as two steps.
|
|
|
|
## Phase 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 (phase 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 (phase 1, and the fixing in phases 2 and 3) | smaller | building to a written context |
|
|
| The reviewer (phase 2, and the extra cycle in phase 3) | 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 ready mark | 15 minutes after the acceptance is shown | the reviewer marks it, or the lead does |
|
|
| a release branch in flight that shares files | its delivery plan done | report it every ten minutes; bring the branch up to `main` after |
|
|
| a merge after ready | 5 minutes after the checks pass | 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 ten minutes, and at once when something lands, stops or needs the operator,** the lead reports, in
|
|
one of the two layouts below. Times are local and absolute; 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), the issues as a tree: grouped by repository, each issue under the one it
|
|
waits for, marked ✓ landed, ▶ fixing, ⏸ waiting, ✗ stopped:
|
|
|
|
```
|
|
Feature <slug> · step <n> of <m> · phase <name>, <minutes used> of <bound> min
|
|
Issues: <landed> of <total> landed · <fixing> fixing · <waiting> waiting
|
|
|
|
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)
|
|
|
|
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 action, one line>
|
|
```
|
|
|
|
**The short layout** (on a channel such as Telegram, or when nothing changed but time):
|
|
|
|
```
|
|
<slug> step <n>/<m> · <phase> <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; an unchanged table is not repeated, the short layout
|
|
says it instead.
|
|
|
|
## The context of the lead's own session
|
|
|
|
At the end of each phase, 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 phase, 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.
|
|
|
|
## Issues that stop
|
|
|
|
An issue stays in `fixing` only while an agent works on it. A step 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
|
|
phase 1, is the failure this playbook prevents.
|
|
- A context file exists per touched repository under `.work/<slug>/` while the feature runs.
|
|
- After a feature's delivery, `git branch -r | grep feat/<slug>` and `git worktree list` return nothing for it.
|
|
- More than one open pull request in a repository that share no feature name, or a feature step 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 reviewed and green but not marked ready for more than 15 minutes is a lead that did not
|
|
mark it.
|
|
- A pull request opened into a repository whose release branch was in flight and shared a file with it is the failure of
|
|
*Is the coast clear?*; the lead's report names the check it made before the push.
|
|
- A step that adds or changes a module marked ready with no integration test that starts the module whole is
|
|
the failure of phase 3's fourth step.
|
|
- 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.
|