git-native work graph · coding-agent fleets

How staq works

The work graph that keeps a fleet of coding agents from colliding — and from silently breaking each other's decisions. The state is a git repository. There is no database.

one script, one $4/mo box · deploy-hub.sh

01The deployed system

Three surfaces, none of them a staq-built screen: your terminal & agents, a headless hub (an HTTP API over a git repo), and the git remote itself. Here's what's running on the box.

Agent fleet claude-code codex foreman Cloudflare DNS A · grey-cloud Any $4/mo VPS · one box ubuntu 24.04 · 512 MB · nyc3 Caddy :443 TLS staq-hub 127.0.0.1:4242 work.git (bare) /srv/staq/work.git ⟵ the entire state ⟶ HTTPS · Bearer proxy → loopback git over SSH (git-shell) → CAS mutex
Agents reach the coordination API over HTTPS (Caddy terminates TLS, proxies to the loopback-bound hub) and sync the work-graph by pushing/pulling work.git directly over SSH. The bare repo is the single source of truth.
no database

git is the state

Tasks, locks, decisions, contracts and the event outbox are all git refs & blobs in one bare repo. Nothing to provision, back up, or keep in sync.

stateless shim

the hub holds nothing

The HTTP hub is a ~41 MB process with zero state of its own — it just runs git plumbing against work.git. Kill it and restart anywhere; no data is lost.

02The mutex: a compare-and-swap on a git ref

Claiming a task is a single atomic git update-ref with an expected old value. Git's ref store does the compare-and-swap — so two agents racing for the same task can never both win. No lock server, no quorum.

agent A update-ref … expect=0000 agent B update-ref … expect=0000 refs/locks/T-12 atomic CAS in git A wins → claimed ref created B rejected stale expected value
Verified under load: 2000 concurrent rounds, exactly one winner each, zero double-grants. The same push to the shared remote makes the mutex distributed across machines.

03The moat: cross-issue decision coherence

The mutex stops two agents editing the same task. The harder problem — and the thing nothing else does — is catching when an agent's decision on task A silently breaks task B. staq records what each task relies on (a contract) and what each decision touches, then intersects them.

decision on Task A touches: { symbol: AuthToken } Task B · contract relies_on: { symbol: AuthToken } ∩ overlap shared surface found severity by B's status in_reviewBLOCK claimedWARN openskip* doneskip
Pure deterministic set-math — no LLM, no external calls at check time. A block on an in_review task fails the gate (staq check exits non-zero).
* The temporal hole — closed. A decision recorded while task B was still open used to be skipped and never revisited. Now, when B transitions to in_review, staq review replays the entire prior decision log against it (recheckTaskCoherence) — so a break that was invisible at decision time is surfaced at the moment it matters.

04Using it

Agents coordinate through the hub's HTTP API and sync the graph over the git remote. Only /health is open; everything else needs the bearer token.

EndpointWhat it does
GET /healthLiveness — the only unauthenticated route.200
GET /statusAll tasks with lock state projected on.200 / 401
GET /readyThe pull queue: open, unblocked, unheld tasks with cost/lane/contended annotations.200 / 401
POST /claimServer-side CAS claim of a task.200 / 409
POST /releaseRelease a held lock.200 / 403 / 404
# talk to the coordination API
curl -H "Authorization: Bearer $STAQ_HUB_TOKEN" https://your-hub.example.com/ready

# sync the work-graph (the distributed git-ref mutex lives here)
git remote add hub ssh://staq@your-hub.example.com/srv/staq/work.git
git push hub refs/work/*

Work originates in Linear; staq only coordinates. The one-way importer mirrors issues → tasks (blocked_by → deps) without ever writing back — paginated beyond 250 and reconciling closures against live state, never on a fragile time window.

05Security model

bearer auth

token-gated API

Every route but /health requires Authorization: Bearer. The token is never logged, stored in a ref, or echoed in a response.

fail-closed bind

loopback + TLS front

The hub binds 127.0.0.1 only; Caddy terminates TLS in front. It refuses to bind a public interface without a token set.

git-shell

restricted SSH

The staq user's login shell is git-shell — SSH allows git push/pull and nothing else. No interactive shell, no arbitrary commands.

disposable

nothing precious on the box

State is the git repo. A bad release is a per-machine rollback; a dead hub loses zero data. Re-deploy is idempotent.