Many agents share the repo, so the main checkout belongs to nobody: each task gets its own worktree and branch, pushes as it goes, and ends as a PR/MR.
0. Fetch and find the integration branch
Run git fetch origin --prune. <int> is integrationBranch in .agents/environments.json, the branch the user chose for PRs/MRs. Not there but the dev environment has a branch: use that (legacy) and say once that integrationBranch should be recorded. Neither: stop and ask which branch PRs/MRs go into, showing your guess (dev when git ls-remote --exit-code --heads origin refs/heads/dev succeeds, else the remote default branch); never assume it, not dev, main, master, or a staging or prod branch. Record the answer as integrationBranch (a registry may hold only project, integrationBranch and an empty environments) in its own PR/MR into that branch, with the user's yes per environments section 1. When the harness-map setup batch (its section 3), project-init section 2 or environments section 10 already got that answer in this run, use it as <int> without asking again, and record integrationBranch in that skill's own PR/MR, not a separate one. main is fine as <int> only when the user chose it.
1. Start or resume: one task = one worktree = one branch
Repo opted in to feature-status (its section 0): for ticket work and for no-ticket features other agents could collide on, its pick, run before any worktree exists, replaces steps 1-3 (in a harness's detached worktree: pick --no-worktree, then git switch -c <branch> --track origin/<branch>); on pick --resume still read the progress file (section 3). A small no-ticket change follows the steps below alone; feature-status section 1 owns who picks.
git rev-parse --path-format=absolute --git-dir --git-common-dirprints two different paths in a linked worktree.- Linked, on this task's branch: use it, read its progress file, go to section 2.
- Linked, detached HEAD, clean tree (the harness's): steps 1-2, then in step 3
git switch --no-track -c <branch> origin/<int>in place. - Another task's branch, or the main checkout: never work there; step 1.
- Look for existing work:
git worktree list
git branch -a --list '*/<ticket>-*' '<ticket>-*'
gh pr list --state open --search '<ticket>' # GitLab: glab mr list --search '<ticket>' (no glab: references/gitlab.md)Nothing: step 2. Found and the user asked to resume it: reuse it, read its progress file first, never create a second. Found otherwise: another agent may be on it; stop and ask.
- Branch
<type>/<ticket>-<slug>(fix/WEST-12-paid-invoice-lock), or<type>/<slug>without a ticket; type isfeat,fix,refactor,chore,docs, ortest. - Create it beside the main checkout, so the watchers, linters, and test runners there never scan it, without tracking
<int>, using these commands rather than a harness feature (which may nest it in the repo or use a stale local HEAD):
MAIN="$(git worktree list --porcelain | sed -n 's/^worktree //p' | head -n 1)"
WT="$(dirname "$MAIN")/$(basename "$MAIN").worktrees/<branch-with-slashes-as-dashes>"
git worktree add --no-track -b <branch> "$WT" origin/<int>- Every command from now on runs in
$WT.
2. Run without touching other agents
- Install dependencies (
pnpm install --frozen-lockfileor equivalent); docs- or config-only changes may skip install and E2E, not the linters for those files. - Symlink secrets-only gitignored files (
.agents/secrets.env) from the main checkout; copy and edit per-worktree ones (.env.local: port, database, callback URLs); never commit either. - Never kill processes you did not start; run yours on a free port and fix the URLs that depend on it.
- Docker Compose:
-p <repo>-<slug>; with fixedports:orcontainer_name:, say so and use the running stack read-only, or ask. - Never reset or migrate another worktree's database; schema changes and destructive tests get your own database or schema; if the app cannot run isolated, say so instead of running them on the shared one.
- Run the touched packages' tests once after your change; only a failing test is rerun on
origin/<int>(git worktree add --detach "$WT.base" origin/<int>, removed after) and the result goes in Decisions. - A test failing only on a shared test database is a flake: confirm in isolation, fix the isolation in its own branch per
root-cause.
3. Commit, push, and keep the progress file as you go
Context resets lose chat, so each worktree keeps a resume file in its private git dir (never committed, no .gitignore, removed with the worktree; never info/exclude, which all worktrees share):
P="$(git rev-parse --path-format=absolute --git-path agent-progress.md)" # inside $WT- Under 30 lines, rewritten not appended, stale lines dropped:
# <branch>and the ticket,## Done(steps, short hashes),## In progress(one step, its state),## Next,## Decisions(<what> - <why> - <cost if wrong>, pre-existing test failures). - Read it first on resume and after a context summary; trust it and
git log origin/<int>..HEADover memory; update it with every commit. - Small commits; check
git statusand stage only what you meant; never secrets, build output, or local config. - Conventional Commit with the ticket id:
fix(billing): [WEST-12] keep paid invoices unlocked, body wrapped at 72 (commitlint rejects body lines over 100). - Helper files a tool needs in the repo are named in your notes and deleted before committing; the rest goes in the scratchpad.
git push -u origin HEADafter the first commit, then keep pushing.- Rewrite history only on your own branch, then
git push --force-with-lease; never force-push a branch you do not own. - Unrelated findings: per
AGENTS.md.
4. Done means a PR/MR into <int>
All of these hold:
git fetch origin && git rebase origin/<int>, conflicts resolved keeping others' work,git push --force-with-lease.verify-looppassed all its gates on the final head (includingfresh-reviewbeyond docs or config,e2e-verifywhen user-facing).- Its table is in the body, with head equal to
git rev-parse HEAD. - A PR/MR is open into
<int>(forge pergit remote get-url origin):
gh pr create --base <int> --head <branch> --title "<conventional title>" --body-file <file>
glab mr create --source-branch <branch> --target-branch <int> --title "<conventional title>" --description "$(cat <file>)" --yesMissing or unauthenticated gh is a blocker to report, never a reason to skip; GitLab without glab follows references/gitlab.md. Never read a stored git credential to call a forge API.
- The body: ticket or request, what changed and why, verification (table, evidence), risks, what is left out.
- CI on the head is green (
verify-loopsection 3).
- Never merge your own PR/MR unless asked.
feature-statusrepos: states, merge, release, and recovery per its section 3.- Blocked or partial work: a draft (
--draft) listing what remains, as soon as you know you will stop. - Copy the progress file's Decisions and Next into the body; it dies with the worktree.
5. Promote to staging and prod
Only on the user's explicit word, per AGENTS.md.
- "Lên staging" means a PR/MR from
<int>into staging, listing every commit it carries (others' work too); a single feature branch only when asked for exactly that. - Merge it only if asked; if CI deploys on merge, that is a deploy under
environments. - Prod: the same from staging, under its
confirm-each-steppolicy.
6. Clean up
Session start: [gone] branches only; "dọn worktree": also every worktree under <repo>.worktrees/ (repos keeping merged branches never show [gone]).
git for-each-ref --format='%(refname:short) %(upstream:track)' refs/heads | awk '$2 == "[gone]" {print $1}'
git worktree list --porcelain | awk -v b="refs/heads/<branch>" '/^worktree /{p=substr($0,10)} $0=="branch "b{print p}' # its $WT
gh pr view <branch> --json state,headRefOid -q '.state + " " + .headRefOid' # GitLab: glab mr view <branch> --output json | jq -r '.state + " " + .sha' (no glab: references/gitlab.md)Remove only when its PR/MR is merged or closed ([gone] proves nothing), git -C "$WT" status --porcelain is empty, git rev-parse <branch> equals the PR/MR head, and it is neither your worktree nor the main checkout. feature-status repos, merged feature: status.py release <ID> first; when it names the recovery, do it in this worktree before removing anything. Then git worktree remove "$WT" (if any) and git branch -D <branch> (needed after squash merges, safe only after these checks).
- Never
git worktree remove --force; on refusal, keep it and report. - Open PR/MR or no PR/MR: never remove, report.
- Stop the servers and containers you started.
Report
Branch, worktree path, PR/MR URL, verification results, and anything the user must decide.