AgentGrid
An internal developer-experience tool: a local control plane in Go that makes running several coding agents in parallel safe, by tracking what each one has claimed, detecting when a plan has gone stale, and scoring how risky a branch has become.
Context
One coding agent in one terminal is easy. Several agents across branches, worktrees and terminals is a coordination problem: two agents editing the same file without knowing it, an agent growing a 4,000-line diff nobody can review, or an agent touching `migrations/` while attention is elsewhere.
Problem
A better prompt does not stop two agents from editing the same file, and a smarter model does not know that `main` moved underneath the plan it made twenty minutes ago. What limits how many agents I can run at once is whether I can trust the combined result, which is a coordination problem.
What I did
- Built a single Go binary plus a SQLite file under
.agentgrid/, with no daemon, server, or cloud component, running alongsidegitinstead of wrapping it. - Designed a claim registry: agents record path, glob, or module claims with an intent (
read,edit,create,delete) before starting work. - Implemented deterministic overlap detection that refuses conflicting claims outright, so the conflict surfaces before the work instead of at merge.
- Implemented stale detection, comparing what an agent claimed and modified against what has landed on the base branch since it started, returning a structured recommendation instead of a bare warning.
- Implemented diff-risk scoring over configurable thresholds and forbidden paths, returning a level plus structured reason codes.
- Kept the tool a boundary instead of a framework: it embeds no prompts, no planner, and no model logic, and it never merges, force-pushes, or rewrites history.
Scope and shape
An orchestrator that launches agents, hands them tasks, and supervises results was the other option. I built a layer that sits beside git and answers questions about what is happening, and left process management to tmux and the agents themselves.
It shells out to git, tmux and gh instead of reimplementing them. Every meaningful action appends to an events table, so current state is a projection over a fact log, and the whole system can be debugged with sqlite3 alone.
Claims
An agent registers what it intends to touch before it starts. Claims come in three kinds, a literal path, a glob, or a named module, each carrying an intent. Overlap is then a deterministic computation.
The rule is asymmetric: two agents reading the same files only warns, while any pairing involving edit, create or delete is a hard conflict and is refused. Blocking at claim time is what keeps both agents from doing work that has to be thrown away.
$ agentgrid claim add --agent b --glob 'src/auth/**' --intent edit
error: hard conflict with agent 'a'
agent a claim src/auth/session.ts intent edit
overlap src/auth/** ∩ src/auth/session.ts
exit status 3Stale detection
An agent reads a module, forms a plan, and starts working. Something else lands on the base branch touching those same files. The agent is now working from a picture of the repository that no longer exists, and nothing about its own branch looks wrong.
So staleness is computed against both what the agent claimed and what it has actually modified, because either is enough to invalidate a plan:
// Files that landed on the base branch since this agent branched off.
baseChanged := git.DiffNames(agent.BaseCommit, baseBranchHead)
// Everything this agent is reasoning about: what it reserved,
// plus what it has already changed.
watched := union(matching(agent.Claims), agent.Modified)
if hit := intersect(baseChanged, watched); len(hit) > 0 {
return Stale{Files: hit, Recommendation: recommend(hit, agent)}
}A bare "you are stale" warning would be close to useless, so the verdict carries a recommendation derived from the shape of the overlap: a few files the agent already modified suggests rebase; a few files it claimed but has not touched is cheap to review; a large overlap against real work means re-plan; and anything hitting a forbidden path says narrow.
Diff-risk, and what the tool will not do
The third mechanism scores how risky a branch has become: files and lines changed against configured thresholds, how many modules it spans, whether it touched a forbidden path, whether it modified files it never claimed, and whether code changed without any test file changing with it.
Severity is the maximum across firing reasons, never a sum. Summing small signals produces a score with no single explanation behind it, while the maximum always points at one specific reason a branch is rated high. Every reason is emitted as a structured code, so the output is scriptable.
It never merges, force-pushes, or rewrites history, and it does not auto-resolve anything. It informs, warns, and blocks a claim, and the merge stays with the person running it, because a tool that takes irreversible git actions on its own could destroy the work it exists to protect.
Result
- Coordination failures are made visible: overlapping edits refused at claim time, expired plans surfaced with a recommended action, and oversized or off-limits diffs flagged before review.
- Every read command emits stable
--json, so the checks compose into scripts and hooks instead of requiring a person to watch a dashboard. - State is a fact log in SQLite: the current view is a projection over appended events, and the whole system can be debugged with
sqlite3alone. - A deliberately small surface: a dozen commands, each answering a question an engineer would otherwise answer by hand.
- This is the v0.1 MVP, used by one person on my own projects. Everything described here is behavior that ships today; there is no throughput benchmark behind it.
Tech
- Go
- SQLite
- git
- tmux
- gh
- CLI design
- policy engine