Skip to content

asc-me/graphban

v1.0.0

Graphban fleet roles for Cursor. Mint a wave in the Fleet view for the MCP config; this ships the agents that use it.

Graphban for Cursor (generated)

Generated by scripts/gen_subagents.py (AL-213) — do not hand-edit. Regenerate after changing the roster or AGENTS.md so the fleet can't drift. Emitted natively per toolchain — Cursor & Claude Code get Markdown + their own frontmatter; Codex gets a TOML role file. One source, native output per tool.

Cursor stores one MCP config and reuses it across every agent. That used to make a fleet impossible here: a role lived in the credential, so every agent shared one role.

Enrolment seats split those apart. The credential says who you are; a SEAT says what role you hold for this run. So the config is written once and never again, and the fleet is provisioned by pasting codes into prompts.

Install

ln -s "$(pwd)/.cursor/plugins/graphban" ~/.cursor/plugins/local/graphban

Then restart Cursor. This ships the role agents.

Once, ever — the credential

Settings -> API Keys -> create one, and put it in ~/.cursor/mcp.json:

{
  "mcpServers": {
    "graphban": {
      "url": "http://localhost:8000/api/mcp",
      "headers": { "X-API-Key": "<your key>" }
    }
  }
}

One server, one key. You do not rewrite this per wave, and End wave never touches it — it expires the seats and leaves the credential authenticating.

There is no environment-variable form: Cursor does not interpolate ${env:VAR}, ${VAR} or $VAR here. Probed against 3.16.2 with the variables present in the process, and the entry is silently dropped rather than sent as a literal — a config that looks right and connects nothing.

Each wave — the seats

Fleet view -> Provision a whole wave. Add one seat per agent (two workers means two seats) and issue them. Each row copies a filled prompt with the code already in it; paste one per agent.

A seat is not a credential. It grants one role on one project for one session and expires in half an hour, which is what makes it safe to paste into a prompt.

Two workers need two seats. Agents that redeem the same code share a session, and the server treats a shared session as one opinion — so they could not review each other.

If an agent dies, Reissue that seat. Codes are single-use by design: reusing one would silently disable review between the agents that shared it.

Without seats

An agent that registers with no code gets all-in-one: unrestricted, no role gate, you are the reviewer. That is the correct default for one developer and one agent, and it costs nothing.

For a fleet on one credential with no seats issued, each agent must declare who it is:

register_agent(label=..., capabilities={"instance": "<unique per agent>"}, role_hint=...)

On one credential an agent that declares nothing that differs is refused review — absence is not a difference, or omitting a field would launder a self-review.

What this does and does not guarantee

Roles are enforced. The server issued the seat, so a worker reaching for sign_off is refused — it cannot promote itself, because it has nothing to promote itself with.

Independence is decided by the server, not declared by the agent: two agents that redeemed different seats are two sessions, so a reviewer signing a worker's item means something.

It is not an adversarial boundary. An agent handed two codes can use either. Enrolment makes the ACCIDENT impossible — which is the failure that actually happens — and does not defeat intent.