Summary
A single hive serving a diverse org makes its agents compete over unrelated
work. Splitting into several subject-scoped hives fixes that, but the moment
you do, you discover the hub is a directory, not a coordinator — and
several things that ought to be fleet-wide are per-hive by accident rather than
by design.
This RFC reports what actually broke splitting one 43-repo hive into two, and
proposes what a constellation — several spokes under one hub, acting as one
system — would need.
We are running this now: a self-hosted hub, two spokes, shared credentials.
Everything below is measured, not speculative.
What we built
hub.tunaos.org (HIVE_MODE=hub, registry)
|
+-----------------+-----------------+
| |
hive.tunaos.org reef.tunaos.org
25 repos · ACMM L6 18 repos · ACMM L5
OS, images, installers, desktop apps, app
packaging distribution, docs
| |
+--------- shared -------------------+
GitHub App installation
agent OAuth credentials
Spokes register themselves by heartbeat once they have HIVE_HUB_URL and the
hub's HIVE_HUB_SECRET. That part works exactly as documented.
What the hub does NOT coordinate
Each of these is fleet-wide in reality but per-hive in the implementation.
1. Provider quota
Every spoke probes and consumes provider quota independently, with no shared
view. Two spokes on one Anthropic subscription each believe they have the whole
weekly cap; the first to notice exhaustion is the one that hits it.
This is the single biggest constraint on splitting. A split creates zero
additional quota — it divides the same pool among more agents. Ours:
DeepSeek dry, OpenAI capped for four days, Anthropic near its weekly cap,
Google carrying the fleet and hitting its own 5-hour window.
Constellation need: headroom as hub state, so a spoke can ask "what is left
for the fleet" rather than "what is left as far as I can see".
2. GitHub App rate limit
Both spokes use one App installation and therefore share its 7100 req/hr.
Bringing up the second spoke exhausted it during initial enumeration:
403 API rate limit exceeded for installation ID 137498420 [rate reset in 19m22s]
... all 18 repos failed to enumerate
Nothing coordinates this. Each spoke polls its own repos on its own schedule
and discovers the shared ceiling by hitting it.
Constellation need: either a documented per-spoke budget, or hub-level
awareness so spokes back off cooperatively instead of racing.
3. Agent credentials
Each hive pod has its own /data/home, so by default every spoke needs its
own interactive claude /login — and those sessions rotate refresh tokens
against each other. We saw exactly that failure earlier: a copied credential
was zeroed when the other session refreshed, and Claude Code silently fell back
to metered API billing.
We solved it by mounting one credential directory into both spokes, which works
because a single hive already shares ~/.claude across its 11 agents by
symlink. Extending that across hives is the same model.
But it needs both spokes on one node (RWO binds to a node), and PVCs are
namespaced — we needed a second PV over the same hostPath to cross the
namespace boundary. That is a workaround, not a design.
Constellation need: a first-class notion of fleet-shared credentials.
4. Repo ownership
Nothing prevents two spokes managing the same repo, which would put two fleets
on the same backlog opening competing PRs — the exact thing a split exists to
prevent.
Our org watcher nearly caused this: it read one spoke's repo list, could not
read the other's (no owner session), concluded 18 repos were unmanaged, and was
one step from adding them to the first hive.
Constellation need: the hub should know which spoke owns a repo, and refuse
or warn on overlap. The registry already receives each spoke's repo list on
every heartbeat — the data is there, unused.
5. Routing new work
When a repo appears in the org, something must decide which spoke gets it. We
do this by asking an AI with each hive's charter in the prompt, falling back
across backends when one is rate-limited, and leaving a repo unassigned
rather than guessing — a repo in the wrong hive is worse than one nobody has
picked up.
That is spoke-side today and duplicated per operator. It is a natural hub
responsibility: the hub already knows every spoke and its charter.
Proposal
Sequenced smallest-first; each step is useful alone.
1. Overlap detection (cheap, high value). The hub already receives repo
lists via heartbeat. Flag a repo claimed by more than one spoke in
/api/registry and the hub UI. Pure read-side; no new protocol.
2. Spoke charters in the registry. Let a spoke advertise a one-line scope
alongside its repo list. Makes the registry self-describing and gives any
router — AI or human — the data it needs.
3. Fleet headroom. Let spokes report provider headroom on the heartbeat
they already send, and expose the aggregate. Read-only first; a spoke can
decide what to do with the number.
4. Shared-credential guidance. Document the one-directory-many-readers
model, with the node/namespace constraints spelled out. Ideally a supported
config field rather than a hostPath trick.
5. Hub-side routing (optional). Only once 1–3 exist. POST /api/route
taking a repo and returning a spoke, so every operator does not reimplement it.
Deliberately not proposed: the hub scheduling work onto spokes, or holding
credentials. It is a directory; making it a control plane changes its failure
model from "registry is stale" to "fleet is down".
Open questions
- Is per-spoke GitHub App installation the intended answer to the rate-limit
ceiling? It solves it cleanly but multiplies App management.
- Should overlap be an error or a warning? Legitimate cases may exist (a docs
repo two subject areas both touch).
- Does hub-side routing belong here at all, or in a separate tool that reads
the registry?
- Is there appetite for spokes reporting quota to the hub, given it is
operator-sensitive data?
Environment
ghcr.io/kubestellar/hive-hub:stable + two hive:stable spokes on a 2-node
Talos cluster; ACMM L6 and L5; one GitHub App installation; shared Claude Max
and Antigravity credentials. Related: #5632, #5633, #5661, #5690.
Summary
A single hive serving a diverse org makes its agents compete over unrelated
work. Splitting into several subject-scoped hives fixes that, but the moment
you do, you discover the hub is a directory, not a coordinator — and
several things that ought to be fleet-wide are per-hive by accident rather than
by design.
This RFC reports what actually broke splitting one 43-repo hive into two, and
proposes what a constellation — several spokes under one hub, acting as one
system — would need.
We are running this now: a self-hosted hub, two spokes, shared credentials.
Everything below is measured, not speculative.
What we built
Spokes register themselves by heartbeat once they have
HIVE_HUB_URLand thehub's
HIVE_HUB_SECRET. That part works exactly as documented.What the hub does NOT coordinate
Each of these is fleet-wide in reality but per-hive in the implementation.
1. Provider quota
Every spoke probes and consumes provider quota independently, with no shared
view. Two spokes on one Anthropic subscription each believe they have the whole
weekly cap; the first to notice exhaustion is the one that hits it.
This is the single biggest constraint on splitting. A split creates zero
additional quota — it divides the same pool among more agents. Ours:
DeepSeek dry, OpenAI capped for four days, Anthropic near its weekly cap,
Google carrying the fleet and hitting its own 5-hour window.
Constellation need: headroom as hub state, so a spoke can ask "what is left
for the fleet" rather than "what is left as far as I can see".
2. GitHub App rate limit
Both spokes use one App installation and therefore share its 7100 req/hr.
Bringing up the second spoke exhausted it during initial enumeration:
Nothing coordinates this. Each spoke polls its own repos on its own schedule
and discovers the shared ceiling by hitting it.
Constellation need: either a documented per-spoke budget, or hub-level
awareness so spokes back off cooperatively instead of racing.
3. Agent credentials
Each hive pod has its own
/data/home, so by default every spoke needs itsown interactive
claude /login— and those sessions rotate refresh tokensagainst each other. We saw exactly that failure earlier: a copied credential
was zeroed when the other session refreshed, and Claude Code silently fell back
to metered API billing.
We solved it by mounting one credential directory into both spokes, which works
because a single hive already shares
~/.claudeacross its 11 agents bysymlink. Extending that across hives is the same model.
But it needs both spokes on one node (RWO binds to a node), and PVCs are
namespaced — we needed a second PV over the same hostPath to cross the
namespace boundary. That is a workaround, not a design.
Constellation need: a first-class notion of fleet-shared credentials.
4. Repo ownership
Nothing prevents two spokes managing the same repo, which would put two fleets
on the same backlog opening competing PRs — the exact thing a split exists to
prevent.
Our org watcher nearly caused this: it read one spoke's repo list, could not
read the other's (no owner session), concluded 18 repos were unmanaged, and was
one step from adding them to the first hive.
Constellation need: the hub should know which spoke owns a repo, and refuse
or warn on overlap. The registry already receives each spoke's repo list on
every heartbeat — the data is there, unused.
5. Routing new work
When a repo appears in the org, something must decide which spoke gets it. We
do this by asking an AI with each hive's charter in the prompt, falling back
across backends when one is rate-limited, and leaving a repo unassigned
rather than guessing — a repo in the wrong hive is worse than one nobody has
picked up.
That is spoke-side today and duplicated per operator. It is a natural hub
responsibility: the hub already knows every spoke and its charter.
Proposal
Sequenced smallest-first; each step is useful alone.
1. Overlap detection (cheap, high value). The hub already receives repo
lists via heartbeat. Flag a repo claimed by more than one spoke in
/api/registryand the hub UI. Pure read-side; no new protocol.2. Spoke charters in the registry. Let a spoke advertise a one-line scope
alongside its repo list. Makes the registry self-describing and gives any
router — AI or human — the data it needs.
3. Fleet headroom. Let spokes report provider headroom on the heartbeat
they already send, and expose the aggregate. Read-only first; a spoke can
decide what to do with the number.
4. Shared-credential guidance. Document the one-directory-many-readers
model, with the node/namespace constraints spelled out. Ideally a supported
config field rather than a hostPath trick.
5. Hub-side routing (optional). Only once 1–3 exist.
POST /api/routetaking a repo and returning a spoke, so every operator does not reimplement it.
Deliberately not proposed: the hub scheduling work onto spokes, or holding
credentials. It is a directory; making it a control plane changes its failure
model from "registry is stale" to "fleet is down".
Open questions
ceiling? It solves it cleanly but multiplies App management.
repo two subject areas both touch).
the registry?
operator-sensitive data?
Environment
ghcr.io/kubestellar/hive-hub:stable+ twohive:stablespokes on a 2-nodeTalos cluster; ACMM L6 and L5; one GitHub App installation; shared Claude Max
and Antigravity credentials. Related: #5632, #5633, #5661, #5690.