# How we keep parallel agents working toward the same plan

A commit in August staged one agent session's uncommitted work into another session's change. How we responded with Git worktrees, a guard hook, shared planning documents, a GitHub issue tree, and handovers between sessions.

In August, one of my coding sessions was updating a program plan while a second session finished an unrelated task in the same Git checkout. When it committed, it staged everything in the working tree: my uncommitted plan edits went into its commit, under a message about closing an issue that had already been fixed. Nothing was lost, but the history now credited my work to an unrelated change.

Until then my Git habits came from maintaining [Hinode](https://gethinode.com), an open-source theme for the Hugo site generator: as a solo developer I kept `main` matching the published site and `develop` carrying my changes, testing cross-module changes by pointing one module at a local copy of another. At Amfelt those habits met multiple developers and coding agents running parallel sessions in one checkout, all tripping over changes they had not made. I needed to coordinate the work more deliberately.

---

## Give each implementation its own workspace

The first change was to give each agent's implementation work a separate [Git worktree](https://git-scm.com/docs/git-worktree): a second checkout with its own working files, branch, and `HEAD`, so no session changes files underneath another. In [Effective Strategies for Asynchronous Software Engineering Agents](https://arxiv.org/html/2603.21489v2), worktree isolation beat a shared workspace where agents were only told not to interfere on both benchmarks tested, and on one the shared workspace did worse than a single agent; more agents also cost more without much reducing elapsed time, so we make no promise of speed.

An instruction only works when every agent follows it, so at the end of August we added a hook that runs before every file edit and shell command. It refuses file edits in a main checkout and the Git commands that would move its branch or rewrite its working files, such as `checkout`, `reset`, `rebase` and `stash`; commits and pushes are still allowed. It misses files written through the shell and can be switched off, which we accept in a guard meant to catch the common mistake.

Isolation created problems of its own. Agents created worktrees wherever they happened to be working, so we settled on a dedicated folder with a cleanup procedure; after a relative-path worktree twice landed inside a main checkout, and a later commit recorded one as an orphaned submodule, the instruction now spells the folder as an absolute path. A worktree also isolates only files and branch state. Ports, databases, and caches remain shared: a test run from one checkout once stopped the local databases another was using.

---

## A shared place for designs and plans

Our development lifecycle has explicit phases: brainstorming, design, planning, execution, validation, release, and cleanup. Validation includes a blind review: the reviewer sees the specification and the change, but not the implementer's reasoning. Two questions kept coming back: how a session would discover work happening elsewhere, and how we would know what had progressed. Separate worktrees answered neither: a session can be perfectly isolated and still work from an outdated assumption.

For designs and plans we kept one shared local checkout of our documentation repository, the deliberate exception to implementation isolation. The hook leaves it writable; it is also exactly the kind of checkout where the August commit happened, so the exception has a cost. Each job writes its own documents there rather than sessions rewriting one shared file, so parallel sessions can find them. Markdown was already our knowledge base; now it also carried the context needed to continue active work.

Reading them reliably takes a rule of its own. A parallel session can move the shared checkout to another branch between two of your own commands, so agents read documents from the branch with `git show develop:<path>`, never from the working tree. No hook enforces that, nor the list of documents to consult and when to reread them. Until we synchronize the checkout with the Git server, the newest context also remains local.

---

## Turn the plan into work people can discuss

To track execution, we instruct agents to turn the plan into a GitHub issue tree: [sub-issues](https://docs.github.com/en/issues/tracking-your-work-with-issues/learning-about-issues/about-issues) group related work, and each issue states what it depends on and what it blocks. Progress appears on a central project board, not buried in an agent conversation.

A single coordinating session owns the conversation attached to that work. It moves the board and writes the comments, posting each worker's evidence and each blind review's verdict; an item passes on that verdict, and workers report to the coordinator without writing in the thread. Nothing pushes a new comment into a running agent, so the coordinator rereads an item's thread when a session starts or resumes and before handing the item to a worker; when findings conflict in a way it cannot settle, work stops and a person decides. [Cursor's account of coordinating parallel agents](https://cursor.com/blog/scaling-agents) describes a similar split at far larger scale.

One program's plan became an epic with 186 issues. Before the first wave started, a review session read the plan against the current code: the specifications had been written against a version of the application more than 250 commits behind main, three planned tasks duplicated work that had already shipped, and one early task removed a page that every project card still linked to before its replacement existed. Eleven minutes later, the duplicates were closed with evidence and the task moved behind its replacement; that evening the plan and both specifications were updated, with a new lesson: ratify decisions before a worker writes a specification that depends on them.

The next day, a gap check found one task that made connecting a Git repository a mandatory onboarding step, while a decision recorded that morning on another issue had the repository created automatically. The comment marked the task blocked until a person decided; one of my colleagues made the call that evening, and the issue and page specification were rewritten to match.

---

## Make coordination transferable

The coordinator accumulates context: why tasks were divided as they were, which dependencies matter, what reviewers found, what remains unresolved. So that continuity does not depend on one long conversation, a handover convention lets a fresh session recover the decisions from the handover, shared documents, and issue threads. Anthropic's [harness for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) works similarly; each new session reads progress notes, a feature list, and Git history before continuing.

A morning handover in August reported that 16 of the 18 items in one bug-sweep batch needed a shared browser or test stack; the next session checked each item against the main branch before acting on it and found the block half real: three of the eight application items needed no browser at all and two needed no code. Across that sweep, roughly one item in four was already fixed by the time the program reached it.

---

An agent session is temporary; the plan has to outlast it. When intent lives in files and issues any session can read, a fresh agent or a colleague picks up the work where it stands. Agents now do much of our implementation, but deciding what that intent is, and keeping it current, remains our job.

If you want to see where those shared documents began, we wrote about [the folder of Markdown files](/blog/markdown-knowledge-base-for-ai-development/) that became our knowledge base for AI development.
