Notes Repos as Shared Project Context

The first thing most people put in a new repository is code. That is fine. It is also incomplete.

When I start a real project these days, something I expect Cursor, Claude, Codex, and I to grind on together for more than an afternoon, I create a notes repo early. Sometimes it sits beside the product repo. Sometimes it lives inside the product repo as a deliberate notes/ or docs/project/ tree. Either way, the job is the same: give the humans and the agents one durable place to put the stuff that does not belong in source files but absolutely belongs in the work.

This is not documentation for show. It is shared working memory.

Why a notes repo, not a pile of chat

Chat is ephemeral. Agent transcripts are useful, but they are not a system of record. Plans get buried. Decisions get restated wrong three sessions later. Somebody new (human or model) shows up and has to rediscover who owns what, what “done” means, and which external systems are in play.

A notes repo fixes that by making the context something you can open and search. Cursor can open it. Claude can read it. Codex can search it. I can edit it when the plan changes. Everyone is looking at the same markdown, not reconstructing the project from memory and half-remembered Slack threads.

I treat it as the base layer under the code. The product repo holds the build. The notes repo holds the thinking that keeps the build pointed in the right direction.

What actually goes in it

I keep the structure boring on purpose. Fancy wikis rot. Flat, named markdown files get used.

Project brief. What we are building, who it is for, what it is not. One page. If you cannot say it in a page, you do not have a project yet. You have a mood.

Current plan. The active plan, not a museum of every plan that ever existed. Short enough to approve in one breath. Specific enough that “yes” means something. When the plan changes, rewrite it. Do not append a novel of abandoned approaches unless those approaches teach a real constraint.

Decisions. Lightweight ADRs, or just dated notes: we chose X because Y, and Z is explicitly out of scope. Agents love to re-litigate settled choices. Write the settlement down.

Team map. Who is who. Humans, roles, ownership. Which agent lanes exist if you have specialized agents. Who reviews security. Who can approve schema changes. “The team” includes the tools now, so name them and their jobs.

Glossary. Product words, domain words, the three acronyms everybody uses differently. Cheap insurance against confident mistakes.

Working notes. Scratch space for the current thread of work: open questions, links to tickets, sketches, “we tried this and it failed because…”. This is the joint notebook Cursor, Claude, Codex, and I write into while we work.

Pointers out. Links to the product repos, design files, staging URLs, runbooks, and the environment/setup notes. The notes repo should know where the rest of the world lives without trying to duplicate it.

I do not put secrets here. I put the names of secrets and where they are supposed to live. That distinction matters.

How I use it with agents

The pattern is simple: before an agent starts implementing, it reads the brief, the current plan, and the relevant decision notes. After a meaningful chunk of work, it updates the working notes or the plan so the next session does not start from zero.

That pairs cleanly with the ask-plan-confirm habit I already use. The notes repo is where the approved plan lives between sessions. The agent does not get to invent a new goal because the chat scrolled away.

A few practical rules that hold up:

  • One current plan file. Archive old plans if you must, but do not make the agent guess which file is live.
  • Prefer short files with clear names over one giant NOTES.md.
  • Write for the next reader who was not in the room. That reader might be you in two weeks, or an agent with a fresh context window.
  • Update notes as part of the work, not as a cleanup chore after merge. If it is optional, it will not happen.

What this saves

It saves re-explaining the project every morning. It saves the “wait, who owns auth?” loop. It saves agents from optimizing the wrong goal because the real goal lived in a Slack thread from Thursday.

It also saves me from being the only continuity process on the team. Continuity is a file. Version it. Diff it. Argue with it in a pull request if the decision is big enough.

Start smaller than you think

You do not need a knowledge management platform. You need a repo, a README that says what the repo is for, and a handful of markdown files that stay honest.

Create it when the project becomes real. Keep it next to the code in your mental model. Make every agent treat it as required reading before they touch the product tree.

Code is the artifact. Notes are the shared brain that keeps the artifact from wandering off.

Agent-Ready Local Environments and MCP

Getting a coding agent productive is less about the prompt and more about whether the machine in front of it can actually build, run, and reach the systems the work depends on.

Cursor, Claude, Codex, and the rest are fast when the local world is already honest: dependencies install, the app boots, tests can run, and the external surfaces (GitHub, GitLab, Slack, Outlook, Teams, whatever is in play) are reachable through clear, permissioned paths. When those pieces are missing, the agent spends its tokens rediscovering your laptop instead of doing the job.

This post is about making that setup cheap and repeatable.

The real prerequisite is a bootable story

Every repo should answer, in files the agent can read, a small set of questions:

  1. What do I need installed on this machine?
  2. How do I get from clone to a running local system?
  3. Which services does this project talk to, and which of those am I expected to use locally?
  4. Where do secrets live, and what is not allowed?
  5. How do I know the setup worked?

If those answers only exist in your head, the agent will invent something almost right. Almost right is expensive.

I keep this in the product repo as first-class docs and scripts: README, CONTRIBUTING, docs/setup, scripts/bootstrap, .env.example. When the work spans more than one codebase, I also mirror the “where the rest of the world lives” map in the project notes repo.

Make setup mechanical

Agents are excellent at following a checklist. They are mediocre at inferring your undocumented brew taps and tribal knowledge.

What works well right now:

A single bootstrap path. One script or documented sequence: install language runtimes, install dependencies, copy env templates, start local dependencies, run a smoke check. Prefer boring and explicit over clever.

Pinned, discoverable tooling. Version files (.nvmrc, .node-version, mise.toml, asdf configs, rust-toolchain, etc.) beat README poetry. The agent should be able to detect the toolchain without asking you which Node major you meant last quarter.

.env.example that matches reality. Every required variable named. No secret values. Short comments for where to get each one. If a variable is only for production, say so.

Smoke tests for the environment. A command that proves the local stack is alive: npm run doctor, make verify, a compose healthcheck, a migration status call. Green means “safe to start feature work.” Red means “fix the machine, not the feature.”

Dev containers or explicit host setup. Pick one and document it. Either the agent works inside a known container, or it works on the host with a known bootstrap. Mixing both without saying which is canonical creates two slightly broken worlds.

This is the same instinct as my old dev-setup-osx habit, updated for a world where the “new developer” might be an agent that showed up thirty seconds ago.

Tell the agent where the working systems are

Local build is only half the map. Most real work touches other systems: source hosts, chat, mail, issue trackers, cloud consoles, internal APIs.

Write down the topology.

  • Product repos and their remotes (GitHub, GitLab, both, mirrors).
  • Which environments exist: local, staging, production, preview apps.
  • Which human collaboration surfaces matter: Slack channels, Teams teams, Outlook lists, Linear/Jira projects.
  • Which of those the agent is allowed to read or write.
  • How authentication is supposed to happen for each.

Do not make the agent guess that “the deploy” means GitHub Actions in one repo and a GitLab pipeline in another. Put the pointers in markdown next to the code or in the notes repo. Ambiguity here turns into the wrong PR opened against the wrong remote, or a “fix” that never lands where humans look.

MCP is how agents reach the rest of the desk

Model Context Protocol servers are the practical bridge between the coding agent and the tools already on your desk. Used well, they turn “go check the thread / issue / inbox / pipeline” into a first-class action instead of a copy-paste scavenger hunt.

The useful pattern is not “connect everything.” It is “connect the systems this project actually depends on, with the least privilege that still helps.”

A sane MCP layout for project work often includes:

  • Source control: GitHub and/or GitLab for issues, PRs, checks, and releases.
  • Chat: Slack or Teams for the channels where decisions and unblockers live.
  • Mail/calendar: Outlook or similar when the work is genuinely gated on threads or meetings, not because it is fun to give an agent your inbox.
  • Project tracking: whatever holds the tickets, if that is not already the git host.
  • Docs and runbooks: if they live outside the repo and agents keep asking for them.

Wire these at the user or project level in Cursor (and equivalents elsewhere), then document in the repo which MCP servers are expected for this project and what they are for. An agent that knows “Teams is available for the eng channel, GitHub is available for PRs, Outlook is not in scope for this repo” wastes less time and takes fewer weird actions.

Authentication matters. Prefer the product’s normal OAuth/device flows. Keep tokens out of the notes repo and out of prompts. If a server needs auth, say so in setup docs and stop there. Do not paste credentials into markdown “for convenience.”

Boundaries beat cleverness

An agent with broad access and no rules will eventually do something technically impressive and socially awful: comment in the wrong channel, open a PR against the wrong fork, or dig through mail that was never part of the task.

Give it clear limits:

  • Read-only by default where write access is not required.
  • Explicit allow-lists of repos, channels, and projects.
  • Repo instructions that say when to use MCP versus when to stay local.
  • The same ask-plan-confirm gate you use for code changes when the action leaves the laptop (posting, labeling, merging, emailing).

Local environment setup gets the agent building. MCP gets the agent collaborating. Boundaries keep both from becoming a mess you have to unwind.

A minimal checklist I actually use

When I stand up a project for agent-assisted work, I want at least this:

  1. Clone, bootstrap, and smoke test documented and scripted.
  2. Toolchain versions pinned in-repo.
  3. .env.example complete; real secrets elsewhere.
  4. Notes/brief that name the remotes, environments, and collaboration surfaces.
  5. MCP servers configured for the systems in play, with purpose notes in the project docs.
  6. Clear write/read expectations for each integration.
  7. A first prompt that points the agent at setup docs before feature work.

None of that is fancy. All of it is what makes Cursor, Claude, Codex, and friends look brilliant on day one instead of lost in your PATH.

The goal is simple: when an agent sits down at the project, the local world boots, the surrounding systems are findable, and the rules of engagement are already written down.

Ask, Plan, Confirm: Making Agents Stop Before They Start

The scariest thing about a genuinely capable coding agent is how quickly it commits. You type two sentences, and forty seconds later there are changes across nine files, half of which you did not want and one of which quietly changed a query you spent a week hardening. The agent was not wrong about how to implement the thing. It was wrong about what the thing was, and it never stopped to check.

I have watched this happen enough times that I stopped treating it as a prompting problem and started treating it as a workflow problem. A better one-shot prompt is not the fix. A gate is: the agent is not allowed to edit files until it has asked what it needs to ask, shown me a plan, and gotten a yes.

Why speed is the problem

Human engineers have a built-in pause. Before a senior developer touches your codebase they ask a couple of questions, sketch the approach, maybe drop a comment on the ticket. That pause is where the wrong-work gets caught, cheaply, in a sentence, instead of expensively, in a diff you have to read and reject.

Agents removed the pause. That is most of their value and most of their danger in the same motion. An agent that implements immediately optimizes for the wrong thing: it treats “produce a diff” as the goal, when the goal was “produce the diff we agreed on.” The gap between those two is where the deleted work and the silent scope creep live.

That gap is not a knowledge problem. The agent knows how to write the code. It just never checked that it was writing the right code.

Diagram explaining the importance of speed in work processes, contrasting 'no gate' and 'the gate' approaches. It highlights potential problems and costs associated with each method.

The same task, gated and ungated. Skipping the pause trades a small, predictable cost for a large, unpredictable one.

So I gave the pause back, deliberately, as a standing instruction on every agent in the InterlinedList repo.

The three beats

The workflow is written down in .claude/workflows/plan-first.md and every agent links to it. It is three beats, in order, and implementation is gated behind all three.

A flowchart titled 'The Gate' illustrating a process for implementation that requires three steps: Prompt, Ask, Plan, Confirm, and Implement. It emphasizes the sequence and conditions under which implementation occurs, highlighting the need for clarification and approval before proceeding.

The three beats, in order. Nothing is edited until all three pass, and work that grows past the approved plan loops back to re-plan rather than quietly expanding.

Ask. Surface what the prompt left open before committing to an approach. Ambiguous scope, unstated edge cases, a product decision hiding inside a technical request, whether a feature should be tier-gated. The migrations agent asks about column types and nullability and whether anything destructive is implied. The Next.js agent asks which surfaces are in and out. The rule has an escape hatch, because asking three questions about a one-line copy fix is its own kind of annoying: skip the questions only when the request is genuinely unambiguous and low-risk. When in doubt, ask. A pointed question is cheaper than a wrong build every single time.

Plan. Before touching files, lay out the shape of the change: the files and routes and components you will touch, the ones you will deliberately leave alone, the approach, any migration (additive, always), the tests the change needs, and anything risky. In this repo “risky” has a specific meaning: auth, IDOR, subscription gating, SSRF, secret handling, anything destructive or hard to reverse. The plan is a decision aid, not a document. It should be short enough to read in one breath and specific enough that approving it means something.

Confirm. Implement only after an explicit yes. If the plan changes in the back-and-forth, restate the revised version and get the yes again. And the part that actually matters over a long session: approval is scoped to the plan that was approved. If the work grows past it, the agent stops and re-plans instead of quietly expanding. That last clause is what keeps a “small fix” from turning into an afternoon of changes I never signed off on.

What it looks like per agent

I did not want one generic paragraph pasted eight times. The gate is the same, but what you ask about depends on the job, so each agent got the beats written for its lane.

A diagram featuring multiple lanes labeled with different topics: Migrations, Next.js, End-to-end, Docs, Unit testing, and Blog. Each lane has brief descriptions of its scope, accompanied by specific tags.

One gate, written for each lane. The two read-only reviewers pick the full gate back up the moment they move from finding to fixing.

The migrations agent plans the exact idempotent migration.sql and confirms it is purely additive before it applies anything. The unit-testing agent asks which behaviors to lock in and which boundaries to mock, then lists the cases each test file will assert. The e2e agent names the flows, the auth and seed prerequisites, and the breakpoints that matter. The docs agent confirms which of the three docs is in scope and whether a new page is needed. The blog agent (yes, this one) settles the angle and the section arc and which real code it will verify claims against before drafting a word.

The two read-only reviewers are the interesting edge. Security and UX do not implement, so there is no edit to gate. For them the gate degrades to its first beat: confirm the review scope if it is ambiguous (which routes, how deep, which breakpoints), then produce findings. But the moment the user says “now fix what you found,” they are implementers, and the full ask-plan-confirm gate snaps back on before they touch code. The reviewer does not get to slide from “here is a finding” into “and I fixed it” without crossing the same line everyone else crosses.

The obvious objection

This is slower. That is the point, and it is also not as true as it sounds. The plan step costs you a few seconds and one read. Rejecting a forty-second nine-file diff that went the wrong direction costs you the read plus the reject plus the re-prompt plus the nagging worry about what it touched that you did not catch. The gate front-loads a small, predictable cost to avoid a larger, unpredictable one. Over a day of handoffs it is not close.

It also composes with the other habit I built into these agents: every one of them does its work in an isolated git worktree, on its own branch, torn down when the task lands. Plan first, then do the approved work in a sandbox that cannot collide with anyone else. The worktree contains the blast radius. The plan makes sure there is not supposed to be a blast in the first place.

The pause was always the expensive part of good engineering. Worth teaching the machines to keep it.

I’m Adron, brainstorming and building InterlinedList.

Giving Every Agent Its Own Branch: Git Worktrees for Parallel AI Work

Run two coding agents against a single working tree and they will fight. One is halfway through editing app/api/messages/route.ts while the other checks out a different branch underneath it. The index lock flickers. A git stash from one session swallows the other’s uncommitted work. I have lost real edits this way, and every time the root cause was the same: one working tree, one HEAD, two writers.

The InterlinedList repo already had matching agents for the jobs I hand off most: a Next.js implementer, a migrations specialist, unit and e2e testers, a docs writer, security and UX reviewers. They are good at their lanes. What they were missing was a lane in the literal sense. They all drove on one road.

I found the pattern I wanted written up in Augment’s guide to git worktrees for parallel AI execution, and it maps almost one to one onto how I already think about agents. This is the writeup of what I built on top of it for this repo: the directory convention, the seven scripts that manage the lifecycle, and the wiring that makes every agent use them without being reminded.

The one-tree problem

A normal clone gives you a single working directory backed by one .git. That is fine for one person doing one thing. The moment you parallelize, the shared mutable state (the working files, the index, the current branch) becomes the bottleneck. You cannot have the migrations agent on agent/add-webhooks and the docs agent on agent/help-refresh at the same instant, because “the branch” is a property of the whole checkout.

Diagram illustrating 'The one-tree problem' in software development with two agents, A and B, interacting with a mutable checkout and related issues like index.lock flicker and checkout thrash.

Two writers, one mutable checkout. The collisions have nothing to do with the work: an index lock flickers, a git stash swallows the other session’s edits, HEAD thrashes between branches.

Git solved this in 2015 with git worktree. A worktree is a second (third, fourth) working directory attached to the same repository. Each one has its own files, its own index, and its own checked-out branch, while sharing one object store on disk. The object store is the expensive part, so you share it. A directory of files is cheap, so you duplicate it per task. That is exactly the tradeoff you want for parallel agents.

Diagram illustrating a Git object store with multiple worktrees, highlighting shared storage and individual configurations for each task.

Share the expensive part, duplicate the cheap part. The object store lives on disk once; the working files, the index, the checked-out branch, and even the dev-server port are private to each worktree.

The convention

Every agent works in .trees/<task-id> on a branch named agent/<task-id>, cut from origin/develop (this repo integrates on develop, not main). So a task called add-list-webhooks lives at .trees/add-list-webhooks on branch agent/add-list-webhooks. The directory name and the branch name always agree because both are derived from the same sanitized slug.

The .trees/ container is gitignored. Worktrees are workspace, not history:

# .gitignore
# Agent worktrees (see scripts/worktrees/)
.trees/

That single ignore line is the whole footprint the pattern leaves in the tracked tree. Everything else is scripts and instructions.

The scripts

I did not want agents (or me) typing raw git worktree incantations and getting the branch name wrong, or forgetting to copy .env.local, or leaving stale metadata behind. So the lifecycle lives in scripts/worktrees/ as a small set of focused shell scripts. Here is every one of them.

Diagram illustrating the worktree lifecycle in Git, detailing steps for creating, inspecting, removing, and cleaning worktrees. Sections include 'STEP 1' for branch creation, 'WHILE WORKING' for listing and locking, 'WHEN IT LANDS' for removing branches, and 'DAILY SWEEP' for cleanup processes.

The whole lifecycle before the per-script detail. Create, inspect while working, remove when it lands, sweep the merged trees on a schedule.

_lib.sh is the shared library the others source. It holds the functions that keep conventions consistent: wt_repo_root resolves the primary checkout even when you call it from inside a linked worktree (it reads git rev-parse --git-common-dir and walks up), wt_sanitize lowercases a task id and strips it to [a-z0-9._-], wt_resolve_base fetches and prefers origin/<base> over a local branch, wt_port_for hashes a branch name into a stable dev port, and wt_is_locked reads the porcelain worktree list to check lock state. Nothing in it is clever. It exists so the clever bits are written once.

wt-create.sh <task-id> [base] is the one agents call first. It creates the worktree and makes it ready to work in, in one shot:

scripts/worktrees/wt-create.sh add-list-webhooks

Under the hood it resolves the base ref (default develop), ensures .trees/ is in .gitignore, turns on git rerere so repeated conflict resolutions replay across parallel merges, runs git worktree add -b agent/<slug> .trees/<slug> origin/<base>, copies the root .env.local into the worktree, appends a deterministic DEV_PORT derived from the branch name, runs npm ci --prefer-offline, and finally locks the worktree so other sessions can see it is in use. Flags let you opt out where it makes sense: --no-install skips the dependency install for a quick plumbing check, --no-lock leaves it unlocked, and --baseline runs the test suite right after setup so a green baseline proves any later failure came from the agent’s change rather than a pre-existing break.

The port assignment is worth a sentence. Two agents both running next dev on 3000 is another collision, a quieter one. wt_port_for runs the branch name through cksum and maps it into the 3100 to 9998 range, so each worktree gets a stable, distinct port written into its own .env.local. Start the server with npm run dev -- -p "$DEV_PORT" and two dev servers coexist.

wt-list.sh answers “what is running right now.” It prunes stale metadata, then prints one row per worktree with path, branch, lock state, and short HEAD:

PATH BRANCH LOCKED HEAD
/Users/adron/Codez/interlinedlist feature/fix-blog-image - 58afa91562
/Users/adron/Codez/interlinedlist/.trees/add-list-webhooks agent/add-list-webhooks yes b78faf9e21

Before an agent touches a shared file, it can look here and see who else is holding what. Git will not warn you about two branches editing the same file, so this list plus discipline about non-overlapping file domains is the actual safety mechanism.

wt-lock.sh <task-id> [reason] and wt-unlock.sh <task-id> are thin wrappers over git worktree lock/unlock. A lock is advisory: it resists prune and move, and it is the signal in wt-list.sh that says “an agent is live in here, do not reap this.”

wt-remove.sh <task-id> is the teardown. It unlocks if needed, runs git worktree remove, and prunes. This matters more than it looks: deleting a worktree with rm -rf leaves dangling metadata in .git/worktrees/ that haunts you until the next prune. The script never does that. --force discards uncommitted changes on purpose, and --delete-branch drops agent/<task-id> in the same step when the work has landed.

wt-cleanup.sh [base] is the bulk sweep. It walks every worktree physically under .trees/, and for each one whose branch is already an ancestor of origin/develop (in other words, merged), it removes the worktree and deletes the branch. It deliberately skips anything outside .trees/, so the primary checkout and any sibling worktrees I keep elsewhere on disk are never touched. This is the script you point a daily cron or a post-merge hook at.

Each of these is also exposed as an npm script, so npm run wt:create -- add-list-webhooks, npm run wt:list, and npm run wt:remove -- add-list-webhooks all work if you prefer that entry point. There is a scripts/worktrees/README.md documenting the whole set alongside the code.

Wiring it into the agents

Scripts nobody runs are decoration. The point was to make every agent reach for a worktree by default, so I added the instruction in three places at three levels of specificity.

At the top, CLAUDE.md now states the standing rule: every agent works in an isolated worktree, torn down with the lifecycle scripts. That is the repo-wide contract.

In the middle, two shared protocol docs under .claude/workflows/ hold the full detail: worktrees.md spells out the create-work-remove lifecycle, the boundaries (shared object store, non-overlapping files, and the important caveat that the database is shared even though the files are not), and plan-first.md covers the companion habit I wrote about separately. Every agent and skill links to these rather than repeating them.

At the leaf, each of the eight agent definitions in .claude/agents/ got a “Work in an isolated git worktree (required)” section written for its job. The implementers (Next.js, migrations, tests, docs, blog) get the full create-work-remove flow. The two read-only reviewers (security, UX) get a variant that tells them to cd into the worktree under review and read its diff, and explicitly not to create, lock, or remove anything. The migrations agent gets an extra warning in bold, because the worktree isolates schema.prisma and the migration files but not the Postgres instance: db:migrate still hits localhost and db:migrate:deploy still hits production from any worktree. That is the one place the isolation is a lie, and the agent needs to know it.

The five paired skills in .claude/skills/ (the ones that back the implementer agents) got a short “Worktree-first, plan-first” block near the top pointing at the same protocol docs, so whether the work comes in through the agent or the skill, the instruction is there.

Proving it works

I ran the whole lifecycle before committing any of it. Create a worktree from develop, confirm it is locked and has its port, unlock and re-lock it, remove it with the branch, and verify the cleanup pass leaves the sibling worktrees alone:

scripts/worktrees/wt-create.sh wt-smoke-test --no-install
scripts/worktrees/wt-list.sh # shows agent/wt-smoke-test, LOCKED yes
scripts/worktrees/wt-unlock.sh wt-smoke-test
scripts/worktrees/wt-lock.sh wt-smoke-test "smoke re-lock"
scripts/worktrees/wt-remove.sh wt-smoke-test --delete-branch
scripts/worktrees/wt-cleanup.sh develop # safe no-op, siblings untouched

Every step did what it said, the .gitignore guard refused to duplicate the .trees/ line it found already present, and the sibling worktree I keep for feed-perf work was never in scope for cleanup. That last part was the thing I most wanted to confirm, because a cleanup script that reaches outside its sandbox is worse than no cleanup script.

Where agents still get confused

Isolating the filesystem fixes the filesystem. It does nothing about the fact that the work itself overlaps, and there are a handful of ways an agent still gets lost.

The database is one instance, and every worktree writes to it. I flagged this to the migrations agent in bold, but it deserves more than a warning. If the migrations agent adds a column on agent/add-webhooks, that column now exists in the same localhost Postgres every other worktree points at. The docs agent three trees over never sees the changed schema.prisma, yet its queries hit the mutated database anyway. Additive-only migrations keep this survivable most of the time, since an extra column nobody reads is harmless. But the moment two agents touch the same table, or one runs db:migrate:deploy and reaches production from what looked like a sandbox, the isolation is a fiction. The files are private. The database is not.

Green in isolation, red on merge. This is the one that bites. Agent A changes a function signature in lib/lists/queries.ts. Agent B, on its own branch, calls that function from a route it owns. Their files never overlap, so wt-list.sh shows no conflict and git stays quiet. Both test suites pass, because A’s worktree still holds B’s old caller and B’s worktree still holds A’s old signature. It’s all green right up until both branches land on develop, and then the integration is broken in a way neither agent could see from inside its own tree. Worktrees convert a loud, immediate collision into a quiet one that surfaces later. Often a fair trade, but a trade.

Stale base drift. Every worktree is cut from origin/develop the moment it’s created. Agents aren’t always short-lived. Let one run for a few hours while three others merge back, and it’s now building on a develop that no longer exists. It will reintroduce a helper that got deleted upstream, or write against an API another agent already reshaped. The worktree has no idea the ground moved under it. Nothing in the create-work-remove loop forces a re-fetch, so a long-running agent drifts out of date without noticing.

“Does this already exist?” stops having one answer. With five branches in flight, whether feature X is “already built” depends on which tree you grep. An agent that checks the primary checkout won’t see work-in-progress on another branch, and it will happily build a second copy. I’ve watched two sessions independently implement overlapping halves of the same feature, each sure it was first, because neither branch was visible to the other and neither said up front what it was about to touch. wt-list.sh tells you which branches exist. It says nothing about what each one intends to change.

The lock is a suggestion. wt-lock resists prune and move, and it flags a tree as live in the list, but it will not stop another agent from opening the same file on its own branch and editing away. The real guard against two agents clobbering one file is the up-front decomposition plus the discipline to run wt-list.sh and honor what it shows. An agent that skips the check has no seatbelt, just the shape of one.

Abandoned trees pile up. wt-cleanup.sh only sweeps branches already merged into develop. A worktree from a crashed or cancelled session is unmerged, still locked, and invisible to the sweep. It sits on disk with a full node_modules until someone removes it by hand. And an agent resuming a task can trip over a half-finished tree from an earlier run and read its stale state as current work.

Losing the current directory. The workflow says cd .trees/<task-id> and do everything there. Shell state doesn’t always survive between tool calls, and an absolute path into the primary checkout looks identical to one into a worktree. An agent that loses track of where it is can read the main checkout’s copy of a file, reason about it as though it were its branch’s version, and edit the wrong tree. The isolation holds only as long as the agent keeps its bearings.

What this buys

The honest version: worktrees do not make independent tasks independent. If two agents both need to edit the same route, isolating their filesystems just delays the merge conflict, it does not prevent it. The decomposition still has to be real. What the pattern removes is the accidental collision, the kind that has nothing to do with the work and everything to do with sharing one mutable checkout. Those were most of my pain, and now they are gone by construction.

Advantages and disadvantages

Advantages

  • Accidental collisions disappear. Each agent gets its own files, index, and HEAD, so index-lock flicker, a stray git stash eating another session’s edits, and checkout thrash stop happening.
  • Every task lands on its own agent/<task-id> branch, which keeps review and merge clean and stops one task’s half-finished mess from bleeding into another’s diff.
  • The object store is shared, so the costly part of the repo lives on disk once and spinning up another tree is cheap.
  • Each worktree gets a deterministic DEV_PORT, so several next dev servers run side by side instead of fighting over 3000.
  • The lifecycle is scripted and wired into every agent definition, so the right setup and teardown happen without anyone remembering the incantation.
  • git rerere is on by default, so a conflict you resolve once replays across the parallel merges that hit it again.
  • Cleanup is fenced to .trees/, so the bulk sweep never reaches the primary checkout or the sibling worktrees I keep elsewhere.

Disadvantages

  • The database isn’t isolated, and neither is anything else global (production through db:migrate:deploy, OAuth apps, third-party rate limits). File isolation quietly implies an isolation that isn’t there.
  • Overlapping edits on separate branches turn into silent, deferred conflicts and semantic breakage that pass every isolated test and only show up at integration.
  • A long-running worktree drifts from a moving develop, and nothing in the loop forces the re-fetch that would catch it up.
  • Locks are advisory, so the actual protection against two agents editing one file is decomposition plus discipline, not anything git enforces.
  • With several branches live, “does this already exist” has no single answer, and agents duplicate each other’s work when branches can’t see one another.
  • Every worktree carries a full node_modules, so disk use and npm ci time multiply with each active task.
  • Crashed or abandoned sessions leave locked, unmerged trees that the merged-only cleanup won’t reap, so someone clears them by hand.
  • Coordination is still manual: the tooling shows which branches exist, not which files each agent means to touch.

The next habit I want in every agent sits upstream of all of this: stop and plan before touching a single file. That one is worth its own post.

More solutions to the confusion above are coming in the next few posts. Subscribe so you don’t miss ’em: drop your email into the box just below this post, or grab the RSS feed.

I’m Adron, brainstorming and building InterlinedList.