In one month on the web portal, the agent opened 125 pull requests and merged none of them. That split is deliberate. Everything below is built around it.
One worktree per change
Every change gets its own git worktree, branched from the integration branch, under .worktrees/<type>/<slug>. The agent created more than 120 worktrees on the portal in a month, so parallel sessions never edit the same checkout.
Worktrees have traps, so the setup recipe lives in the instruction file:
- Copy the gitignored environment files. Without them the app can silently show the wrong role's screens.
- Run a real dependency install. A fresh worktree has no git hooks until you do, and git won't warn you that pre-commit checks aren't running.
- Never symlink the dependency folder into a worktree. The worktree then silently builds and tests the main checkout's code.
- Respect the fixed dev ports. Only one checkout can run the dev servers at a time. Before killing a process, check that it belongs to your own worktree.
The approval rhythm
Need Help Implementing This?
Our team can help you put these ideas into action. Get in touch for a free consultation.
- The agent proposes a change and the human says "go". If "go" could mean more than one thing, the agent says which reading it's acting on.
- The agent writes a failing test, implements the change and runs the checks.
- The agent commits, pushes and opens the pull request. The description lists open questions and the Not checked items.
- The agent starts a dev server so the human can look. The usual answer: "Tested. Teardown dev server."
- The human merges.
- The human writes two words: "Merged. Cleanup."
What "Merged. Cleanup" means
On the consumer app alone, the developer sent that phrase 48 times in a month. Each time, it sets off a fixed sequence:
- Confirm the merge on the remote, with
gh pr vieworgit branch -r --contains. The built-in worktree tool compares against the local main branch, which may be out of date, so it can't be trusted on its own. - Tidy git. Remove the worktree, fast-forward main and delete the branch.
- Stop only the processes this session started, such as dev servers, bundlers and log watchers.
- Update the session notes and memory.
Two words from the human, about six steps from the agent, done the same way every time. Often the phrase was "Merged. Cleanup. Next."
Conventions that help people later
- Conventional commits with a scope. Subjects describe what the user sees, not which function changed.
- Commits cite the ticket they address.
- Probe-only commits. Some
docscommits exist only to record a live-probe result in an API spec. - A version bump in every pull request, so a deployed build can always be told apart from the one before.
- Never skip the hooks.
Feature flags per environment
"I don't want half-baked frontend or backend in the app." That became a merge gate.
Anything whose backend isn't finished ships behind a flag that is on in the test environment and off in production. Before a release, the question is: "Is this flow in the release branch? Then flag it, off for production." The agent confirms the off state on a real phone before merging.
One correction refined this further: "Make it available to development now, since the API isn't in the next environment yet." Flags became per-environment, not just on or off.
Releasing into the client's repository
Our repositories live on our own Git host. The client's delivery repository is separate, and their build pipeline builds from it. A release script handles the hand-over:
- Sync. It syncs each app's source into the client's repository and commits each app separately. Commit messages must follow the conventional format, and the script checks.
- Branch. It cuts a dated release-candidate branch, with a counter that goes up for each candidate cut that day.
- Merge staging first. It merges the client's staging branch into the candidate before anything else, so the merge request can't conflict later. If that merge conflicts, the script aborts and deletes the candidate.
- Open the merge request. It pushes the branch and opens the merge request. A dry-run mode shows what would happen without doing it.
One warning in memory: the sync deletes anything that exists only on the client's side, so nobody works there directly.
A conflict rescue in three minutes
Once, the merge-first step did conflict. The developer's prompt: "Mirror to the client repo and the staging merge faced a conflict, resolve it." The agent:
- Simulated the merge with
git merge-tree --write-tree, without touching the working tree. - Compared both sides. For the one conflicting file, a feature-flag config, it diffed the common base against main and against staging.
- Proved the resolution. Main already contained every change from staging, so it resolved to main's side. It then showed that the merged candidate was file-for-file identical to main.
- Rebuilt the candidate the way the script would have, reusing the previous merge request's title and description format.
- Waited, then stopped. It polled until the merge request reported as mergeable, and stopped there: "I have not merged it."
Checking what's actually in the release
Before one production release, the developer gave the agent a release-notes spreadsheet. Comparing it against the actual branches showed several fixes missing from the release branch.
They were cherry-picked onto a fresh branch. The developer's correction "don't reuse that release branch, create a new one" went into memory.
When production disagrees
After a deploy that went green, users saw "failed to load module script". The agent diagnosed it with curl alone:
- a cached entry page still pointed at hashed asset files that the deploy had deleted
- the web server's fallback rule answered those missing files with HTML instead of a 404
- the entry page had no
Cache-Controlheader
The fix merged the same day, and the diagnosis went into memory as a runbook.
What to copy
- Use one worktree per change, and write its traps into the instruction file.
- Let agents open pull requests, and keep merging human.
- Make cleanup a fixed ritual, including checking the remote before deleting anything.
- Gate unfinished backends with per-environment flags, and check the off state on a device.
- Script the release, merge staging into the candidate first, and fail clean.