@marxlo/hivemind
v0.1.0
Published
Run a team of scoped Claude Code agents over one repository
Maintainers
Readme
hivemind
Run a team of scoped Claude Code agents over one repository. One agent per lane, one git worktree per agent, a leader that owns the spec and the trunk, and a message bus between them.
hivemind init # write a starter hivemind.yaml
hivemind start # worktrees, hooks, bus, tmux — one window per agent
tmux attach -t hive-<project>Are you an agent? docs/agent-guide.md is the whole tool on one page — commands, config,
the bus HTTP API, and what every error message means — written to be read instead of searched
for. Every generated agent prompt points at it.
Working on hivemind itself? CLAUDE.md is the working model, docs/design-log.md is why
each thing is the way it is (including every bug and what it taught), docs/owed.md is what
is honestly missing, and npm test covers the parts that have tests.
Why it is shaped like this
Everything here is a post-mortem, not a hypothesis. It was written after running three agents by hand over one repository for a week. That week produced exactly two data-losing incidents and about twenty long, useful messages — so the coordination was never the problem.
The problem was shared mutable state. One working tree, one main, one index.
- One agent ran
git add -Aand swept another's half-written file into an unrelated commit. The commit message described half of what the commit contained. - One agent ran
git checkout -B main origin/mainfrom a second worktree.-Bforce-moves a shared ref, and four commits stopped being reachable frommain. Nothing was destroyed, but only because someone noticed within the hour.
Both were committed by the agent that had written the rule against them, in the project's own
AGENTS.md, earlier the same day. Advisory rules do not survive contact with a busy agent.
So hivemind enforces the ones that matter with git hooks and refuses the configurations that
make them likely.
hivemind.yaml
project: neon
trunk: main
# Run by every agent's pre-push hook.
verify: npm run ci
defaults:
enforce: hard # hard | soft | off
agents:
- name: spec
role: leader # exactly one
owns: ["**"]
model: opus
brief: |
You define and update the spec, hand out work, and merge the others' branches.
- name: android
owns: [android-shell/]
denies: ["**/generated/"]
enforce: hard
todo:
- Audit the test harness for assertions nothing checks
- name: sdk
owns: [miniapp-sdk/]
denies: ["**/generated/"]
enforce: soft| Key | |
|---|---|
| owns | Path prefixes this agent may commit. Required. |
| denies | Carve-outs from owns — generated directories usually belong to the leader. |
| enforce | hard refuses out-of-lane commits, soft warns, off does nothing. Per agent. |
| role: leader | Owns the spec, merges branches, and is the only agent allowed to push to trunk. |
| verify | One command, run by every agent before push. |
| todo | Seeded into the agent's opening prompt. |
| model, effort | Passed through to claude. |
| worktreeDir | Where worktrees go. Default .hivemind/worktrees. |
| autostart | false declares a lane without running it — it still claims its scope. |
| permissionMode | Passed to the agent's settings. Declared, never inferred. |
verify is checked before anything launches: the runner must match the repo's
packageManager, and every <runner> run <script> must exist in package.json. A verify
that cannot run is worse than none — a gate everyone believes in and nobody executes — so
start refuses rather than warning.
Two workers owning overlapping paths is a config error, rejected at load. It is decidable from the file alone, so it should never be discovered by two agents editing one file.
What start actually does
For each agent: creates .hivemind/worktrees/<name> as its own worktree on hive/<name>
(reusing the branch if it exists — that is where unmerged work lives), installs its hooks,
renders its system prompt, and opens a tmux window running claude there.
It also starts the message bus, and fails the whole run if the bus does not come up.
Agents with no bus cannot coordinate, and a send that goes nowhere is worse than a refusal.
Worktrees live under .hivemind/ rather than in a sibling directory, so a project stays one
directory rather than two. start makes sure .hivemind/ is gitignored before creating
anything — unignored, every agent's full checkout shows up as untracked in the main tree and
one git add -A there stages the entire hive. Override the location with worktreeDir:.
Changing worktreeDir on an existing hive moves the worktrees rather than failing —
git worktree move, so uncommitted work comes with them. Hook paths are absolute and live
outside the worktree, so enforcement survives the move.
Nesting has one consequence worth knowing: hivemind.yaml is normally tracked, so each
worktree carries its own copy. hivemind asks git for the main worktree
(--git-common-dir) before walking up, so running a command from inside an agent's tree
resolves the real config rather than the copy beside it. Without that it reported the bus
down and no agent having a tree.
Advancing the trunk
hivemind integrate <agent> — catch the leader up to trunk, merge the agent's branch, run
verify, publish. hivemind integrate does every worker in turn.
start defines a remote named trunk (the real origin URL, or . when there is none) and
sets receive.denyCurrentBranch=updateInstead, so integration is one command in both
topologies and the primary worktree updates in place. Two consequences worth knowing:
- The gate actually fires.
verifyruns from apre-pushhook, so on a repo with no remote nobody ever pushed and the mechanism every brief calls "the only thing that catches two agents drifting apart" was inert. Pushing totrunkis a real push. - A dirty primary worktree refuses the publish rather than overwriting it. That is the
point of
updateInstead; the merge is kept, so committing there and re-running finishes it.
Never advance the trunk by hand. git update-ref moves the ref without touching the primary
worktree's index, after which git status there reports the whole repo as deleted.
Auto-polling
Agents do not poll. start writes a Stop hook into each worktree's
.claude/settings.local.json that checks the bus whenever the agent would otherwise go idle;
unread messages come back as decision: "block" with the text, so work wakes the agent
instead of waiting to be noticed. Nothing is on a timer and no session sits blocked. Any
failure in the hook exits 0, so a down bus can never wedge an agent.
hivemind send --to <agent> addresses a message; inbox and the hook filter on it. Without
--to it is a broadcast, and a broadcast is nobody's job.
Sessions
Each launcher resumes that worktree's own conversation with claude --continue when one
exists, so an agent does not re-derive what it already worked out. Claude Code compacts its
own context, so a long-lived session is the cheaper of the two.
Enforcement
Scope — a pre-commit hook checks staged paths against owns/denies and names the
agent that owns anything foreign. Override deliberately with HIVE_CROSS_LANE=1, which asks
you to say why in the commit message.
Trunk — a pre-push hook stops non-leader agents pushing to trunk. Push your branch and
ask the leader to merge.
Verify — the same pre-push hook runs verify. The same command for every agent,
deliberately: it is the one mechanism that catches two agents drifting apart, and it only
works if all of them run it. HIVE_SKIP_VERIFY=1 if you must.
Per-agent hooks require per-worktree
core.hooksPath, which hivemind sets, because linked worktrees otherwise share$GIT_COMMON_DIR/hooks. Writing each agent's hook togit rev-parse --git-path hooksputs all of them at one path and the last writer wins — every agent then silently runs whichever policy was installed last. This was found during testing, when a hard-enforced agent was let through by a soft-enforced agent's hook.
Alive is not working
status used to print "running" for anything with a live tmux window. An agent parked on an
interactive prompt satisfies that and does nothing, indefinitely, while the table says it is
fine — a process that is alive but not working, which is worse than a crash because a crash
is visible.
It now reads the pane and reports BLOCKED with the reason. The signatures are deliberately
narrow: a false "blocked" is noise, but a missed one is the exact lie the check exists to
catch, so anything ambiguous reports as running.
doctor checks the other half — whether each worktree is a trusted folder. Claude Code
asks "Is this a project you created or one you trust?" for an untrusted directory and waits,
so an agent launched into one blocks before reading a single instruction. Trust is recorded
per absolute path in ~/.claude.json and inherited from a trusted ancestor, which is the only
reason the default .hivemind/worktrees layout launches cleanly: it sits under the repo root
you already trusted. Point worktreeDir somewhere else and every agent hangs at once.
Unblocking, and the line this does not cross
Exactly one of those prompts is removed, and it is removed by preventing it rather than by answering it.
start records the worktree as a trusted folder — but only ever extending trust the repo
root already has. hivemind created that directory, from a repository you already trusted,
inside that repository. If the root is not trusted, neither is the worktree, and the agent
gets the prompt it should get; the refusal says so by name. It keeps one backup of
~/.claude.json and writes through a temp file.
Permission prompts are not auto-answered and must not be. "Do you want to proceed?" is
your decision about what an agent may do to your machine. A tool that clicks yes on your
behalf has not unblocked the agent — it has removed the control and kept the appearance of
it. An agent that should not need to ask gets an explicit permissionMode: in
hivemind.yaml, declared by a human, once, in a file you can read. That is a different act
from a tool deciding case by case in the dark.
- name: web
permissionMode: acceptEdits # default | acceptEdits | plan | dontAsk | bypassPermissionshivemind doctor
Every check is something that actually happened:
- the same branch checked out in two worktrees — git normally refuses,
checkout -Bdoes not - an index holding blobs from an older commit — the signature of a stale index, where committing would silently revert whatever landed in between
- staged paths belonging to another agent
- a detached HEAD, or a worktree on the wrong branch
- unpushed commits, and branches with no remote
- a local
trunkahead of its origin — one push away from racing someone else's
Messaging
hivemind send --from android "the CSP permits wss: — three specs say otherwise"
hivemind inbox --agent spec
hivemind inbox --agent spec --wait 25 # long-pollRooms, append-only history in .hivemind/bus/<room>.jsonl, token auth, long-polling. The bus
binds the configured port, or an ephemeral one if it is taken — it will never fall back to
talking to whatever else holds that port, which is a mistake this made once during testing and
now cannot make.
It binds loopback by default. The token is plaintext on disk and the port is the whole
credential, so reaching it from another machine is opt-in — bus.host: 0.0.0.0 — and start
says so on the line where that takes effect.
A chat log is not documentation. The agent prompts say so: if a message describes a shape, a schema, or a rule, it belongs in the repo, and the leader should put it there before anyone builds against it. A contract that lives only in a message diverges the moment the second reader sees a different message.
What this does not do
No scheduler, no task queue, no automatic work assignment. The leader is a Claude session you talk to, and it hands out work in messages. Agents are interactive sessions you can type into — which mattered repeatedly during the week this came from.
It also does not make agents correct. The thing that kept two independent implementations
honest in the project this came from was a shared conformance suite both of them ran, not the
chat. verify is where that goes. Without it you get well-coordinated divergence.
