The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the Hubd listing page.
The project tracker for teams of humans and AI agents — in plain files.
A tool for agents rarely fails by crashing. It fails by answering — confidently, and wrong. A list that ended early without saying so. A count that turns out to be mostly duplicates. A task close that lands on somebody else's id. A person would stop at "wait, fifteen hundred tasks? I didn't create fifteen hundred tasks." An agent has no such prior: it takes the number and builds on it, and every view downstream inherits the mistake, still sounding sure.
hubd is built against that failure mode, and it shows in the boring parts. The logs are append-only and attributed, so a wrong view stays recoverable from data that was always right. Every truncation announces itself. Anything the hub cannot observe is reported as unobserved rather than estimated. Much of this codebase is not features — it is refusals to sound certain.
You run two, three, five agent sessions — different tools, different vendors — across your projects. Each one is brilliant, and each one has no idea the others exist. You are the coordination layer: copy-pasting context, re-explaining state, discovering on Monday what an agent did on Friday.
hubd replaces you in that job with the most boring technology available: plain files. A shared headquarters for your whole team — agents and humans: a journal of what everyone did, task queues every agent can wait on, cross-project tasks, and a read-only kanban to watch it all. All markdown and JSONL, in a folder you own.

hub serve — the board is read-only and has exactly one button (⚙ Rules, it opens
AGENTS.md). Cards move because agents move them; the page just re-reads the files.
Not a runner. Orchestrators launch your coding agents and stream their output — that's making coding faster. hubd manages the work: which projects, what's next, who does it and when, what already happened. An orchestrator can run your agents; hubd runs your projects. They compose.
hubd — the daemon: an MCP server (stdio, JSON-RPC 2.0) that agents talk to.hub — the CLI: the same data for humans, no LLM required.Like sshd and ssh. The daemon serves agents; the CLI serves you.
Option A — start a company (copy the folder). One command drops
hubd-company/ into a folder of your own:
Or clone this repo and copy the folder — it doesn't have to be your repo root.
You get a ready org structure:
constitution (AGENTS.md), role onboardings, project cards, an operator card,
queues, recipes, and a weekly agent-written chronicle/
(the narrative layer).
Hiring an agent = a fresh session reads a role file. This template is NOT
included in the npm package; it comes from the repo.
Option B — add the binaries to what you have:
The npm package ships: hub/ (binaries + lib), prompts/, docs/, README.md,
LICENSE, and HARVEST.md. It does NOT include hubd-company/.
New here? Two guides: the quick start walks the whole path — install → team folder → first agent → queues — and recipes gives complete scenarios (a standing worker, an orchestrator fleet, owner buttons, harvesting a chat, infra topology).
Connect your agent (any MCP client):
HUBD_AGENT is worth setting on day one. Every write names its author —
journal entries, tasks, queue messages — and the field is required: an
append-only log with an unattributed write in it stays unattributable forever. HUBD_AGENT is the floor: when a caller does
not say who it is, the write is attributed to that name plus a short per-session
suffix, instead of failing. Name the function, not the model — dev-hubd,
reviewer-bsdos — because which model you are is already in your client's own
transcript, while many sessions share it. Model and client names (claude,
gpt, cursor) and placeholders (unknown, cli, root) are refused for
that reason. A caller that knows its own function can always be more specific
than the floor.
No MCP? No problem — every model that can read and write files can join:
paste the matching block from prompts/ (Claude Code, Cursor,
Codex/AGENTS.md, or an MCP chat) — it wires hubd in and points at HUBD.md,
the always-current protocol.
Running it for a team? hubd also speaks MCP over HTTP — one shared hub all your agents point at, token-gated and multi-tenant. See self-hosting.
hubd is a tool, like git or node: you install the code, and your data
is a folder you own. They are two separate things — and that is the whole point.
npm i -g @bzdos/hubd@latest (or run one-off with npx -y @bzdos/hubd). A new
version ships the engine (changelog); it never touches your data.HUBD_DIR (default ~/.hubd): plain markdown + JSONL, yours to keep.
HUBD_TEAM_DIR set on its own means the same one directory for everything; set
both only when the queues really live somewhere else. hub doctor says which won.HUBD_AGENT: the default author for calls that omit one, per
server config. Set it in every client and on every host; a required field with no
floor turns a forgotten argument into a failed call.hub doctor counts how many commits this hub
is behind origin, because a sync loop that keeps retrying looks exactly like one
that works: one node here went 228 commits without receiving anyone else's work
while every report called the hub healthy. It also names tracked paths that differ
only by case — on macOS or Windows those are one file for two index entries, which
no commit can ever clean, and they stop a merge permanently. Since 0.9.6 hubd will
not create such a pair in the first place, and doctor flags any card still holding
conflict markers, since a reader serves those as content rather than as an error.hub queue resolve keeps ours in place and
appends theirs at the end, which leaves every byte cursor in the hub valid.hub card resolve unions the bullet-list hunks (two nodes appending
facts have not disagreed) and leaves prose hunks for you, named by section. It
exits non-zero while anything is left.HUBD_DIR a git repo and sync it however you like —
a private remote over SSH works, no GitHub needed. Each machine installs the
code from npm; your data travels in your own git. Two separate tracks: code from
the package, data in your folder. Upgrading the code never migrates or deletes
your data — the event logs are append-only and richer than any one version's schema.~/.hubd, a
laptop that never joined — is folded in with hub absorb <dir> --as <label>: its logs
become that label's per-node files here, its task ids are renamed <label>-<n> in every
field and every text so they stop colliding with yours, its queue history is kept aside
and never re-delivered, and the plan (id map, unread blocks, cards kept for a human)
prints before anything is written. Nothing already in your hub is rewritten.hub version prints the number and the path
of the copy that printed it, because on a real machine those are one question: a stale
global install and a live source checkout are both called hub. From 0.9.4 each journal
line also carries the version that appended it, so hub doctor reports the whole mesh —
which node is behind, whether this copy is the stale one, and whether two hubds are
writing into one node at the same time, naming the agents on each version. That last
detail is 0.9.12 paying for a wrong guess of its own: the warning used to say "two installs
on one node", and on this hub there was one install — a resident MCP server kept writing the
version it had imported while a fresh CLI wrote the current one out of the same file.
Upgrading a package on disk does not reach a process that already imported it. This whole
block exists because the machine that develops hubd ran a CLI nine releases old for weeks
and nothing anywhere could have said so.hub whereami (shell) and hub_context (MCP) answer
from state: the project, its digest with age and a digestStale verdict, open tasks, who else
is heartbeating in this checkout, the journal tail — plus, in the shell, the git inventory
(commit subjects, diff stat, untracked files with their first line, files changed in the last
half hour). hub_whatsnew({since:"session"}) returns what the session itself wrote, which the
default "since my last call" checkpoint cannot. Editor hooks that run hub whereami at session
start and after a compaction: prompts/client-hooks.md.area is a path glob relative to the project
root (src/**/*.ts, docs/{a,b}.md, a directory). hub claim check <path> /
hub_claim_check says whose zone a file is in before you write it, and hub_context reports
claimsTouched when a freshly changed file sits in somebody's. The lock stays soft: it
informs, it never forbids. Prose areas are still accepted, flagged matchable:false.hub card <slug> --replace "<old>" --with "<new>" (or
hub_card_set({replace:[{from,to}], appendLine})) fixes one stale line without rewriting the
owner's framing; a from that is not there is an error, never a silent no-op. hub_report
tells you the digest's age in every reply and nudges once it trails the journal you just moved.hub_whatsnew returns an environment list, every item saying what is wrong, what
fixes it, and who can — the agent, the agent plus a client restart, or you. A
protocol change names the sections that actually moved, so nobody re-reads the whole
manual. hub doctor shows the same list to a human. Nothing blocks a call, nothing
needs acknowledging: an item disappears when the condition does. Per-node state in
.env-state.json, never mesh-synced — three machines have three environments.hub report of prefix-tagged
lines (DECIDE: … | why, FACT:, COMM:, NEXT:, DONE: ids) that fan into
the project card's sections — structure in fields, not one prose blob. "What
changed" is read from git, not retyped. The card's section headings (in any
language) come from one file, HUB/sections.json, which drives both the card
scaffold and the report router — so they never drift.wait
until something arrives, then goes back to waiting. No polling you, no
prodding them. A queue has one live consumer by default — run a single waiting
session per role. Roles listed in <team>/subscriber-roles.json fan out instead:
every waiting session gets its own cursor and sees every message. Crossing
machines is a separate, replaceable concern: scripts/mesh-sync.sh moves the
folder over git+ssh, and mrgd can carry the
same queues as Matrix room traffic — concurrently, on the same directory. See
docs/interop.md → Transport,
including how to check which of the two is actually enabled on a given node.resources/, with structured frontmatter
(type, address, os, provider, status) and typed [[wikilink]] edges
(runs_on, depends_on, deploys_to, exposes, part_of, ...). The same
edge mechanism reads project cards, so hub graph renders one topology
across projects ↔ resources; a task links to what it touches with
--resource. Facts go in fields, not prose.harvest) and hub harvest, so
you invoke it straight from your client — no fetching the file. See
HARVEST.md.AGENTS.md (yours
to write); hubd's own mechanics live in HUBD.md, regenerated per node from the
installed version (gitignored, never synced). Update the code → the next hub
run (or hub upgrade) refreshes HUBD.md, so even agents that only read the
files never follow stale instructions.Files first. Dumb server, smart agents — no AI inside: hubd stores and
serves, intelligence comes from your agents. Never sound more certain than the
data: a tool that misleads its reader is broken even when nothing errored, so
a truncated answer says it was truncated and a number the hub cannot observe is
never estimated. Human-readable everything. Zero dependencies. Read-only for
the human; write access flows through rules.
Graceful degradation: no MCP → files; no hubd → files still readable as-is — in
any editor, grep, or a Markdown app like Obsidian. See
Reading your hub with any tool.
The board at the top is the real thing on invented data:
node scripts/capture-kanban.mjs --gif stands up a throwaway hub in a temp
directory, serves it, then edits it mid-capture — assigns a card, closes one,
files a task, records a decision — and lets the page notice by itself. Nothing is
staged and nobody's actual hub is ever filmed. Six board updates, and only one of
them is a card sliding right: agents also add work, and most of what lands in a
coordination log moves no card at all.
Not an orchestrator (doesn't launch agents or stream output). Not vector memory (the journal stores facts you can read, not embeddings). Not a Jira for humans (the human here is a spectator and a legislator, not an assignee). Not another chat (talk to hubd through your agent; hands — CLI; eyes — kanban).
hubd's own development runs through hubd: one human and a few agents on models from different vendors, coordinating through nothing but the files above. It's our daily dogfood — and the most honest illustration we can offer of the protocol under real use, including the evening a tooling failure forced everything back to plain files and the work simply kept moving. One team's story, lightly anonymized and self-reported, not a benchmark: twelve weeks of it in field notes — every mechanism that broke, and the bug that had every dashboard confidently agreeing on a number that was 72% invented — and one evening hour by hour in the case study.
The human's main job was editing the rules.
The core is MIT, forever. Personal use is free, forever. If a hosted team plan ever exists, the line is simple: agents are free, humans are billed.
Shipped: multi-machine sync (per-host append-only logs, conflict-free); remote
access over HTTP (token-gated, multi-tenant, see self-hosting);
a typed relationship graph ([[wikilink]] edges across projects and resources,
hub graph); resources as first-class cards (hosts / services / endpoints);
structured reports that fan into card sections; one-file section i18n
(sections.json); a per-node HUBD.md protocol that regenerates to match the
installed version; harvest as an MCP prompt; cwd → project auto-bootstrap
(hub_context: marker file / recorded sync path / folder-name guess, no manual
hub_get needed); a presence registry (hub_heartbeat/hub_presence,
TTL freshness like claims) so MCP/headless agents show up next to screen-scraped
ones, with queue depth surfaced in hub_brief — and, from 0.9.13, a fleet view that
admits its blind spots: presence/ is node-local, so each node publishes one small
presence.<node>.json and hub_presence reports which node observed each row plus a
coverage list naming any member that is reporting nothing. A role nobody reports is
invisible, not dead — telling those two apart is worth 92 hours, which is what confusing
them cost once; and buttons — owner-decision
queue items rolled up in hub_brief as "N buttons waiting (oldest X days)"
(HUB/owner-roles.json names the human roles).
Next: task kinds with their own lifecycles (a communicative task knows it's
waiting on a reply); an end-to-end remote mode (the server never reads your
work); a gateway that proxies your personal MCP servers; and the
narrative layer promoted into the server — hub_chronicle / hub_probe
plus mood/check-in journal kinds, once the file-first version proves itself
(design, templates in hubd-company/). The file
format is the stable contract; everything else is negotiable.
The case hubd was built against, and the one worth describing because it is the awkward shape real work has:
Bringing up a from-scratch EL2 hypervisor on a Banana Pi M64 — a bare-metal type-1 hypervisor running FreeBSD 15.1 arm64 as its guest, plus a Mali-400 GPU driver ported to FreeBSD along the way. Three separate repositories came out of it: bzdk (the hypervisor), lima-freebsd (the GPU driver, extracted so it is useful without the rest), and bsdos (the operating system this is all for).
The build machine and the board were never the same machine. The cross-compiler, the FreeBSD and drm-kmod source trees and the Mesa build lived on one host. The board arrived at another, on a different network, with the serial console and the debug Ethernet physically attached there. So the work was split: compile in one place, flash and observe in another. Several agents worked it in parallel — one on clocks, one chasing DMA coherency, one writing tests.
What that costs without a shared journal is specific, not abstract:
None of that needs a server, and none of it left the machines involved: the data is markdown and JSONL in a folder, synced through a private git remote over SSH. That is the whole reason it was built this way.
MIT.