Recipe: trunk-based PR flow
One task, one branch, one squash-merged pull request into a protected default branch. This is the workflow tuhdoo's own repository is built with — dozens of agent-authored PRs have landed through it — and it is the recipe to start from if your team has no strong prior. It is a recommendation, not protocol: tuhdoo works identically with any git workflow (see the recipes overview for the boundary).
The recipe assumes a repo with a single long-lived default branch (main
below), a CI check that runs on pull requests, and a host that supports
squash merges and branch protection — GitHub, GitLab, and their peers all
qualify. Everything below is written for your repo: your task IDs, your
default branch, your CI.
Why this shape
Trunk-based flow and agent fleets fit unusually well:
- One PR per task gives every ledger entry a single durable artifact. The task says what was asked; the PR says what was done; the squash commit on the default branch carries both forward. Nothing about the work lives only in an agent's session.
- Squash merges make intermediate commits cost-free. Agents can commit as often as they like — checkpoints, dead ends, reverts — without anyone curating history. The default branch records one commit per task, titled after the task.
- Branch protection turns convention into enforcement. An agent cannot be talked out of a rule the host refuses to bypass. Direct pushes blocked, a green check required, squash the only merge method: the workflow holds even when an individual session goes off-script.
The flow
Steps 1 and 6–7 are tuhdoo protocol (linked); steps 2–5 are this recipe.
-
Claim the task. Agents take work through tuhdoo's
claim_next(orclaim_taskfor a specific task), never by just starting. A claim is a time-boxed lease the daemon renews automatically while the agent's session is connected — it is what stops two agents from building the same thing. Protocol, not recipe: see the loop in the agent protocol. -
Branch off fresh
main. Name the branch after the task:tuh-<short-id>/<slug>e.g.
tuh-d83w/retry-on-stale-leasefor tasktuh-…d83w. Every tuhdoo task ID ends in a four-character tail (the short ID, shown throughout tuhdoo's CLI and TUI); putting it in the branch name means anyone — human or successor agent — can trace a branch to its task with one glance, and an interrupted agent's work can be found by searching branches for the short ID. Branch from freshly-pulledmain, not from another task branch: stacked branches die badly under squash merges. -
Work and commit freely. Ordinary commits on the task branch, as many as useful. Run the repo's tests locally before opening the PR — CI is the gate, not the first line of defense.
-
Open the PR as the durable record.
- Title = task title. The squash merge reuses the PR title as the commit subject, so the default branch's history reads as a list of completed tasks.
- Body opens with the tuhdoo task ID, then an honest summary of what
was done — including what was not done, and anything surprising. The
PR body is what a human reviews and what a future reader finds from
git log; write it for them, not for the merge button.
-
Enable auto-merge (squash). Once CI is green the PR lands without a human round-trip — e.g.
gh pr merge --auto --squashon GitHub. If your team wants human review before merge, require a review in branch protection instead of skipping auto-merge; keep the mechanics automatic and put the human gate in the host's rules. -
Confirm before the merge lands. A claim is provisional — on a multi-machine team, an earlier claim elsewhere can void it. tuhdoo's
confirm_claimsettles ownership irrevocably; agents call it before merging and merge only on a confirmed verdict. Protocol: confirm before you merge. -
Finish only after the merge lands.
finish_run(done)means the acceptance criteria hold on the default branch — so agents wait for the merge, then report the squash commit inmerged_as(under squash merges the branch commits never reachmain; the squash commit is the one that carries the work). If CI goes red: fix it, or finish honestly asblocked/failed. Work sitting unmerged on a branch is not done. Protocol: finish honestly.
Repo settings that enforce it
Set once by the repo admin, on the host:
- Block direct pushes to the default branch — changes arrive by pull request only.
- Require the CI check to pass before merging.
- Allow squash merge only — with one method available, agents cannot pick the wrong one.
- Auto-delete merged branches — task branches are disposable by design; deleting them on merge keeps the branch list meaning "work in flight".
- Exempt the tuhdoo data branch (
tuhdooby default — the branch the coordination ledger lives on) from all of the above, and exclude it from CI triggers. The daemon pushes it directly, fast-forward only. Details in joining.md.
With these in place, the recipe is self-enforcing: an agent that skips the PR, the green check, or the squash merge simply cannot land its work.
Agent instructions block
Paste into your repo's agent-instructions file (CLAUDE.md, AGENTS.md, or
whatever your harness reads) and adapt the bracketed parts. It assumes the
harness is already connected to tuhdoo and follows the
agent protocol.
## Git workflow (trunk-based PR flow)
- Claim a task through tuhdoo before working; never start unclaimed work.
- One PR per task. Branch `tuh-<short-id>/<slug>` off freshly-pulled
[main]; commit freely on the branch — squash-merge makes intermediate
commits cost-free.
- Run [test command] locally; open the PR only when it is green.
- PR title = task title (it becomes the squash-commit subject). Open the
body with the tuhdoo task ID and an honest summary — the PR is the
durable record of the work.
- Enable auto-merge with squash (e.g. `gh pr merge --auto --squash`).
- Call `confirm_claim` before the merge can land; merge only on a
confirmed verdict.
- `finish_run(done)` only after the merge lands, reporting the squash
commit in `merged_as`. If CI goes red, fix it or finish as `blocked` —
work sitting unmerged on a branch is not done.
- Direct pushes to [main] are blocked by branch protection. That is
enforcement, not convention: don't fight it, don't ask for a bypass.
Adapting it
The load-bearing parts are: one PR per task, the task ID in the branch name,
the PR title/body convention, confirm-before-merge, and finish-after-merge.
Everything else bends — merge queues instead of auto-merge, required human
review, a develop branch instead of main, rebase merges if your team
insists (then merged_as reports the rebased commits, plural). If you drop
one of the load-bearing parts, know what you are giving up: each one exists
to keep the ledger, the git history, and reality telling the same story.