AgentGrid
A local control plane in Go that makes running several coding agents in parallel safer, by tracking what each one has claimed, refusing overlapping edit claims, 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 or glob claims with an intent (
readoredit) before starting work. - Implemented deterministic overlap detection that refuses a conflicting claim outright, exiting with code 3 and writing nothing, so the conflict surfaces before the work instead of at merge.
- Implemented stale detection, comparing the files an agent claimed against what has landed on the base branch since the current merge-base, and returning a recommendation (
rebase,review, orre-plan) 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 the only
gitcommands it runs are reads, so it never merges, force-pushes, or rewrites history.
Result
- Coordination failures are made visible: overlapping edit claims refused at claim time, plans whose claimed files changed on the base branch surfaced with a recommended action, and oversized or off-limits commits 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. - 107 tests at commit e04138e, run in CI on Linux and macOS.
- A deliberately small surface: a dozen commands, each answering a question an engineer would otherwise answer by hand.
- This is the v0.1 MVP, with no tagged release yet, used by one person on my own projects. The code is in a public repository; there is no throughput benchmark behind it.
Technical detail
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 whatever runs the agents.
The design principle. AgentGrid embeds no prompts, no planner, and no model logic, and it does not launch agents or manage worktrees. Anything that reasons about code changes quickly; the rules about who may touch which files do not. Keeping them separate means the policy layer survives changes in the agent tooling underneath it.
The rules themselves live in a pure package that does no I/O, covered by table-driven tests (71 subtests).
It shells out to git instead of reimplementing it, and only to read: rev-parse, merge-base, rev-list and diff. State is a few tables in one SQLite file (agents, claims, stale marks, diff-risk snapshots), so the current state can be inspected with sqlite3 alone.
Claims
An agent registers what it intends to touch before it starts. A claim is a literal path or a glob, and each carries an intent, read or edit. Overlap is then a deterministic computation.
The rule is asymmetric: two agents reading the same files are allowed through without a warning, while any pairing involving edit is a conflict and is refused. Registration is atomic: if any of a new agent's claims conflicts, nothing is written. claim check runs the same test as a dry run. An agent's own overlapping claims never conflict with each other. Blocking at claim time is what keeps both agents from doing work that has to be thrown away.
$ agentgrid claim add b 'glob:src/auth/**:edit'
--- stdout: (empty)
--- stderr
error: claim glob:src/auth/**:edit conflicts with existing claims:
a holds: path src/auth/session.ts edit
--- exit: 3
$ agentgrid claim list --agent b
--- stdout
no claims registered.
--- stderr: (empty)
--- exit: 0Stale 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 refresh takes the files that changed on the base branch since the agent's current merge-base and checks them against the agent's claims, read and edit alike. Files the agent changed without claiming are not part of this check. Because the merge-base is recomputed each time, rebasing past the change clears the stale mark on the next refresh.
A bare "you are stale" warning would be close to useless, so the verdict carries a recommendation, decided by the intent of the claims that matched: an overlap only on edit claims says rebase, an overlap only on read claims says review, and an overlap on both says re-plan.
var rec core.Recommendation
switch {
case hasEdit && hasRead:
rec = core.RecommendReplan
case hasEdit:
rec = core.RecommendRebase
case hasRead:
rec = core.RecommendReview
default:
// Reachable only if a claim has an unknown intent; treat as re-plan
// rather than silently dropping the conflict.
rec = core.RecommendReplan
}Diff-risk, and what the tool will not do
The third mechanism scores how risky a branch's committed changes have become: files and lines changed against three configured tiers each, whether it touched a forbidden path such as migrations/, whether it touched binary files, and whether it modified files outside its claims or changed anything with no claims registered. Thresholds and forbidden paths come from a config file that is validated on every command, and each score is saved as a snapshot that diff-risk --no-refresh reads back without touching git.
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.
Every command that reads state takes --json, and the exit codes are a contract: 0 for success, 1 for an error, 3 for a policy refusal. stale and diff-risk report rather than block, so they exit 0 whatever they find, and a hook reads the JSON. status puts every agent on one table: ahead/behind, latest risk level, stale flag, and whether the branch has merged.
It never merges, force-pushes, or rewrites history, and it does not auto-resolve anything. The only thing it blocks is a conflicting claim; 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.
Limits in this version
- Claims never expire. There is no command yet to release one, so a finished agent's edit claims keep blocking until the state database is reset.
- Claims are voluntary. Nothing stops an agent from editing a file it never claimed; diff-risk reports it only after the commit.
- Uncommitted work is not scored: diff-risk looks at commits only.
- Overlap between two globs is checked with a heuristic, so some real overlaps are missed.
- Deleting a registered agent's branch makes
statusandrefreshfail for every agent. - Diff-risk counts a
readclaim as covering edits to that file.
Go, SQLite, git, CLI design, policy engine