agent-semaphore
# agent-semaphore
**A coordination layer for parallel coding agents.** Worktrees do not remove merge
conflicts — they postpone them to integration time. agent-semaphore closes the gap:
**intent-carrying claims** on scopes, **a warning at the moment of writing**, **conflict
prediction before anything is committed**, and a **serialized landing queue with a
mandatory test gate**.
Local-first: no daemon, no cloud, no account. A single SQLite file in the git common dir
is the whole rendezvous point, so every worktree of the repo sees it by construction.
Cross-vendor by design — Claude Code hooks and MCP, Codex CLI over MCP, everyone else
over a git `pre-commit` hook.
[](https://github.com/alwh1te/agent-semaphore/actions/workflows/ci.yml)
[](LICENSE)

---
## The problem
Agent-written pull requests conflict at **27.7%** — against 10–20% for human ones
([AgenticFlict](https://arxiv.org/abs/2604.03551), 107K+ agent PRs). On co-active pairs
the split is **19.8% intra-agent vs 41.7% cross-agent**: agents have no horizontal
awareness of each other, and every product that ships coordination coordinates only its
own agents. A badly resolved conflict carries up to **~26x** the bug density of ordinary
code (EMSE 2020) — the expensive part is not the conflict, it is the quiet bad
resolution.
Isolation is solved and commoditized (a worktree or a container per agent — everyone
ships it). Prediction and integration are not: nobody runs `git merge-tree` between live
worktrees, and standalone local merge queues effectively do not exist.
## What it does
| Layer | Mechanism |
|---|---|
| **Claims** | Leases, never locks: TTL, renewal by activity, monotonic fencing epochs, a mandatory intent (`reason`). Atomic all-or-nothing acquisition of the whole scope set in canonical path order, so deadlocks are impossible by construction. `exclusive` / `shared` / `intent` modes. Stealing is legal only from a dead holder or a human, and it is audited. Releasing a scope **wakes the waiters** and tells them which branch to rebase onto. |
| **Enforcement** | A `PreToolUse` hook (read-only against the DB, p95 ≈ 16–47 ms) that sees every write: protected ("hot") classes are denied always, another agent's scope is denied once in `warn` mode and permanently in `strict`. The deny text is written **for the model** — it names the holder, its intent, its branch and the exact call to make next. A `PostToolUse` hook auto-claims whatever was written. A git `pre-commit` hook is the vendor-neutral floor for agents without hooks, and for humans. |
| **Radar** | Snapshots of *dirty* worktrees taken through a temporary index (never mutating the working tree), compared pairwise with `git merge-tree --write-tree`. The tree is built twice: if the two builds disagree the snapshot is reported `UNSTABLE`, never `CLEAN`. Statuses: `CLEAN` / `TEXTUAL` / `STRUCTURAL` / `HEAVY`, with ConE-style noise filters. |
| **Queue** | FIFO under `flock`, one entry in flight. Rebase in a scratch worktree, then a **mandatory** test gate, then fencing against the global high-water epoch, then a `git update-ref` CAS into a staging branch. A conflict bounces back to the author with instructions ("your context is freshest"), and after every landing everyone is told the target moved. |
Hard guarantees exist at exactly one place: the landing path. Hooks and `pre-commit` are
cooperative admission control and telemetry, not a security boundary — `ASEM_HOOK_OFF=1`
and `ASEM_OVERRIDE=1` are documented, audited escape hatches. This is stated up front
because a coordination layer that pretends to be a sandbox is worse than none.
## Quickstart
```bash
uv tool install git+https://github.com/alwh1te/agent-semaphore # asem on PATH
# or, from a clone: uv tool install -e .
cd <your repo>
curl -O https://raw.githubusercontent.com/alwh1te/agent-semaphore/main/.agent-semaphore.toml.example
mv .agent-semaphore.toml.example .agent-semaphore.toml # set the gate command, hot classes, target branch
asem init # state in .git/agent-semaphore/
asem install --git-hooks # Claude Code hooks + .mcp.json + git pre-commit
asem doctor # PASS checklist
```
```bash
asem claim src/api/ -i "refactor auth parsing" --ttl 30m # exit 3 = held by someone else
asem check src/api/routes.py # who holds it, and what for
asem radar # conflicts between worktrees, before any commit
asem land feature-branch # rebase -> gate -> CAS into the staging branch
asem notices # messages addressed to you
asem status | asem queue status | asem doctor
```
Exit codes are part of the contract: `0` ok/free, **`3` held/conflict/bounced**, `2`
usage, `1` internal error — a script can tell "coordination said no" from "the tool
broke".
## Measured
Nobody in this space had measured whether claims actually reduce conflicts, so the repo
ships two benchmarks of its own.
**Scripted** ([`docs/benchmark.md`](docs/benchmark.md), 60 runs, deterministic agents,
compliance = 1 by construction): integration conflicts **60% → 0%**, human interventions
**9 → 0**.
**Live agents** ([`docs/bench-llm.md`](docs/bench-llm.md), 40 runs of two concurrent
`claude -p` agents, $17.95):
| mode | ICR | WME | did_work | caught-up | $/run | COR |
|---|---|---|---|---|---|---|
| no coordination | 40% | 2 | 100% | 0% | $0.34 | 1.00x |
| advisory claims | 20% | 1 | 100% | 40% | $0.50 | 1.72x |
| claims + radar | 10% | 2 | 80% | 40% | $0.46 | 1.93x |
| strict + queue | **0%** | **0** | 100% | 40% | $0.50 | 1.98x |
Three findings the scripted harness structurally could not produce:
1. **Conflicts are removed by the catch-up, not by the claim.** 10 out of 10 runs where
an agent rebased onto its peer's branch merged clean; every conflicted coordinated run
is one where both agents claimed politely and neither rebased. A claim serializes
*writing* — it does not hand you the other agent's *result*. That finding is what
produced the "release wakes the waiters and names the branch" feature.
2. **The hook never fired once in 40 runs.** With the protocol in the prompt, agents
claim before editing and never write into a held scope, so enforcement turned out to
be insurance that was not needed — not the working layer.
3. **Coordination can convert a conflict into work that never happened.** In two runs the
blocked agent quoted the holder, its intent and its branch, and abandoned its task.
Without the `did_work` column next to ICR, those runs read as a clean success — which
is why the column is there.
Semantic drift (textually clean, semantically broken) survives every advisory layer in
both benchmarks and is caught **only** by the queue's mandatory gate.
## How it plugs in
- **Claude Code** — `asem install` writes project `.claude/settings.json` (PreToolUse +
PostToolUse), adds `Bash(asem:*)` and `mcp__semaphore__*` to the allow-list, and
registers the MCP server in `.mcp.json`. Committed wiring is host-portable (`$HOME`
and a bare `asem`), so a repo shared across machines does not carry one host's paths.
- **MCP** (`asem mcp`, server key `semaphore`) — `claim`, `release`, `check`, `status`,
`extend`, `report_intent`, `radar`, `enqueue_land`, `land_status`. Every response
drains pending notices, so agents learn about steals, bounces and moved targets without
polling.
- **Codex CLI** — same MCP server via `~/.codex/config.toml`, plus a protocol snippet for
`AGENTS.md`. Headless Codex silently cancels MCP calls unless the tools are
pre-approved; [`docs/integration.md`](docs/integration.md) has the working config.
- **Anything else** — `asem install --git-hooks` puts a `pre-commit` gate in the shared
hooks dir (it chain-loads whatever hook was there before).
## Documentation
- [`docs/00-research.md`](docs/00-research.md) — the research dossier this design is
built on: the measured problem, the tool landscape, the classic prior art worth
stealing from, the 2024–26 papers, and the three confirmed gaps in the market.
- [`docs/adr/ADR-001-architecture.md`](docs/adr/ADR-001-architecture.md) — the
architecture, plus a risk register and ten adversarial attacks from a blind review
(eight of them changed the design).
- [`docs/adr/ADR-002-stack-and-state.md`](docs/adr/ADR-002-stack-and-state.md) — stack,
naming, and where state lives.
- [`docs/benchmark.md`](docs/benchmark.md) / [`docs/bench-llm.md`](docs/bench-llm.md) —
both benchmarks: method, results, and the limitations spelled out.
- [`docs/integration.md`](docs/integration.md) — installation and what is wired where.
## Status
v1 is implemented and dogfooded: the repository coordinates its own agents through it.
150 tests, a p95 hook-latency gate in CI, both benchmarks reproducible from the repo.
Known limits, stated plainly: promotion from the staging branch to `main` is still manual
and ungated (`asem promote` is the next feature); the queue never pushes; there is no
symbol-level scoping, no semantic conflict detection beyond the test gate, no LLM
auto-resolution (the published ceiling is ~55–60% correctness, which is not good enough
to run unattended); and multi-host is a v2 design, though the schema already carries the
`host` column.
## Development
```bash
uv run pytest -q # 150 tests
uv run ruff check . && uv run ruff format --check .
uv run python bench/hook_latency.py 200 # hook latency gate (p95 < 100 ms)
uv run python bench/runner.py --seeds 3 && uv run python bench/report.py
```
The `PreToolUse` hook script is vendored outside the package and must stay stdlib-only —
it runs on every write of every agent, so it has a latency budget rather than
dependencies. See [`CONTRIBUTING.md`](CONTRIBUTING.md).
## License
MIT — see [LICENSE](LICENSE).
TDQS
Scored across 9 tools
Most tools have clearly distinct roles: claiming, releasing, extending, conflict prediction, enqueueing, and status inspection. The closest overlaps are check vs. status (scoped vs. global read-only views) and claim vs. report_intent (blocking vs. non-blocking declaration), but the descriptions make those distinctions reasonably clear.
The naming mixes bare verbs (claim, check, release, extend), verb_noun forms (report_intent, enqueue_land), and noun-style names (radar, status, land_status). It is still readable and none of the names are cryptic, but there is no predictable naming convention across the set.
Nine tools is well-scoped for an agent coordination server covering claims, intents, conflict detection, and landing. Each tool appears to earn its place without obvious redundancy in count.
The surface covers the main lifecycle: discovery, claiming, releasing, extending, intent reporting, conflict prediction, enqueueing, and status checking. Minor gaps remain, such as no explicit cancel/dequeue operation for a land-queue entry or intent revision, but core workflows are supported.