swarm-logs covers every host-tier unit this tool could reach, so the
second, capability-gated path into host journald earns nothing and is
removed outright rather than disabled behind a flag.
Removed end to end: the MCP tool definition + handler, the
GetHostJournal/HostJournal wire variants, hive-c0re's
dispatch_host_journal handler, the ReadHostJournal capability, and the
harness-side capability->--allowedTools gate. get_host_journal was the
only capability that mapped to an MCP tool, so allowed_capability_tools
could only ever return an empty vec; it goes too rather than linger as a
function that provably does nothing.
capabilities::has_cap/caps_for stay: #4624 gave ManageRootAgent's
bind-mount enforcement (hive-c0re/src/lifecycle/host_config.rs) a
second caller of has_cap, so they're no longer callerless once this
lands on top of it.
hive-sh4re's journal module (JournalPriority) had no consumer outside
this tool and is deleted.
An existing capabilities.json still naming read_host_journal does not
error: capabilities::prune_unknown drops unrecognised names with a
warn!, and an agent left with no capabilities has its entry removed. No
migration step is needed.
Untouched: hive-c0re/src/dashboard/journal.rs's
read_host_journal_response, which matches the name but is the private
helper behind the operator-only GET /api/journal-host dashboard route
and carries no capability check.
The hive had two spellings of "this agent may act on agents that aren't
its children": the `ManageRootAgent` capability, which nothing checked,
and a `can_manage_top_level_agents` role in a third meta store,
`roles.json`, which owned the real grant — the bind mounts that put
another agent's state (rw) and config (ro) inside the holder's
container. The two drifted independently, and with the parent/child
hierarchy removed the role's set (`parent.is_none()`) silently became
every agent while nothing said so.
Collapse them. The mount grant now hangs off
`Capability::ManageRootAgent`, looked up through the one capability
path that already exists (`capabilities::has_cap` over
`capabilities.json`) rather than a second mechanism. `roles.json` and
everything that read, wrote or reconciled it is gone, along with its
`meta.rs` staging and commit-label wiring; nothing in the tree reads
that file any more.
The enum variant keeps its name deliberately. Renaming it would turn
every `manage_root_agent` already stored in `capabilities.json` into an
unrecognised name that `prune_unknown` drops without asking. Its
meaning, not its spelling, is what changed: "may manage any agent". The
doc comment and the description string now say that.
`top_level_agents()`/`top_level_agents_in()` are replaced by
`all_agents()`/`all_agents_in()`. Under "manage any agent" the mounted
set is every agent by definition, so the code states it instead of
deriving it from a predicate that no longer discriminates — and the
call-site comment explains that, because it otherwise reads as a
widening. The holder is no longer bound as its own virtual child: that
reproduced the own-state and own-config mounts exactly, so dropping it
loses nothing.
get_loose_ends now always returns the caller's own loose ends, for every
caller including the manager (ruth) — there is no separate manager
surface, ruth is a normal agent with different default capabilities.
- AgentGetLooseEndsArgs removed; get_loose_ends takes no args.
- Wire Request::GetLooseEnds collapses from an Option<String> target to
a unit variant.
- hive-c0re's handle_get_loose_ends drops the "*" hive-wide branch and
the subtree/capability resolver (resolve_agent_state_target); both
are gone since there is no longer a target to resolve.
- loose_ends::hive_wide and Capability::QueryAgentState removed as
dead code — their only callers were the two functions above.
- is_descendant_of is untouched (still used by lifecycle_handlers.rs
and schedules.rs independently of this change).
- Docs updated: docs/turn-loop/mcp.md, docs/web-ui/dashboard.md,
docs/process/conventions.md (Loose-ends wire shape + capabilities
table), plus the doc comments in hive-core-agent-sock, mcp_config.rs
and capabilities.rs that described the old shape.
Refs #4480
meta.rs writes each agent's flake, and it still named the pre-move
`hyperhive.*` paths — so every agent rebuild would print a rename
deprecation warning about a line no human wrote and no operator could fix.
A warning nobody can act on trains everyone to ignore the ones that matter,
which is the whole value of the alias shims.
Repoints the FORWARDED_VAR_OPTIONS table and every other emitted option
assignment (otel.*, docs.source, claudeCodePath, github.enable, user.name,
claudeMemoryMaxBytes) to `services.hyperhive.agent.*`, with the test
expectations that pin the rendered text. The flake input named `hyperhive`
(`hyperhive.url`, `hyperhive.inputs.nixpkgs.follows`,
`hyperhive.nixosConfigurations.*`), hive-tier `services.hyperhive.*` paths,
and the `@hyperhive.local` git identity share the word and are untouched.
Also repoints the same option paths where they appear in comments, rustdoc
and runtime message strings across the other crates — a refusal message
naming `hyperhive.allowedRecipients` sends an operator to a path that will
stop existing. Prose under docs/ is deliberately not in this commit.
Refs #4473
A subagent inherited the parent's built-in tool list, which correctly has
no `Bash` -- the agent reaches a shell through the `bash` MCP server, not
the built-in. Subagents get no such server, so the intersection was empty
and they could not run a command at all: no commits, no pushes, no gates.
Add `subagent_builtin_tools_for`/`_arg`, which reuse the shared resolver
and append `Bash` only when `Execution` -- the group that gates the `bash`
MCP server -- is present. Only the subagent spawn path calls them, so the
harness's own `--tools`/`--allowedTools` are unchanged.
The capability transfers; the mechanism does not.
Refs #4422
The `--tools` list a harness session gets is not a constant: the base set
plus whatever the agent's `HIVE_TOOL_GROUPS` add (today, `web_tools` →
`WebFetch`/`WebSearch`). That resolution lived in `hive-agent`'s
`mcp_config`, which is fine while the harness is the only thing that
spawns a `claude` — and it is not: `hive-subagent-mcp` spawns one too.
`hive-agent` is binary-only (no `src/lib.rs`, no lib target), so nothing
can depend on it to reach `builtin_tools_arg`. The alternative to a
shared home is a second list in the subagent daemon, which diverges on
the first tool anyone adds to either — and diverging upward is a
subagent holding a built-in its parent does not have.
So move the base list, the `HIVE_TOOL_GROUPS` parse and the resolution
into `hive_sh4re::permissions`, beside `ToolGroup` — whose
`builtin_tools()` was already half of the answer. `hive-agent`
re-exports them, so `mcp_config::builtin_tools_arg()` still reads the
same at the call site, and `allowed_tools_arg` now derives its built-in
half from the same function rather than repeating the merge loop.
Behaviour is unchanged. The parse is `strum::EnumString` rather than a
`serde_json::from_value` round-trip through a `Value::String`: same
`snake_case` names (a test pins the two derives against each other),
without `hive-sh4re` needing `serde_json` outside its dev-dependencies.
It is now a pure function of its input, so the fallbacks are testable
without mutating the environment — which under edition 2024 is `unsafe`
and racy across a test binary's threads.
Refs #4416
`nix/templates/harness/` is cited from `send_allow.rs` and twice from
`mcp_config.rs`, in the present tense, as where the NixOS module writes
the send allow-list, writes the extra-MCP spec, and defines the MCP http
port default. There is no such directory — `nix/templates/` holds only
`agent.nix` and `ruth.nix`.
All three describe things that live in `nix/agent-modules/mcp.nix`:
send-allow.json is written at :230, extra-mcp.json at :228, and the
`hyperhive.mcp.httpPort` option is declared at :143.
These are the mechanical half of #3923, which explicitly carved the
repointing out from the question of whether the reference gate should
widen. Nothing here changes that question.
Per mara's go-ahead on hyperhive#3902 ("getting started is good, but
terminal rendering does not go in there i think"):
Moved 21 top-level docs/*.md files into 7 new topic subdirectories
(existing web-ui/, turn-loop/, swarm/, tools/, crates/ untouched):
getting-started/ setup.md
agent-lifecycle/ agent-hierarchy.md, approvals.md, persistence.md
trust-boundary/ boundary.md, security.md
integrations/ forge.md, matrix.md, github.md, knowledge.md
networking/ gateway.md, network.md, snapshot-store.md
scheduler/ jobq.md, coordinator.md, ci.md, observability.md
process/ conventions.md, gotchas.md, pr-review-gate.md
web-ui/ terminal-rendering.md (moved into the EXISTING dir,
per mara's correction to the original getting-started
guess -- it's UI implementation detail, not onboarding)
The physical layout now matches docs/README.md's own topical headers,
which already amounted to this taxonomy -- see the scoping comment on
the issue for the two findings that motivated this (a genuine
duplication between CLAUDE.md's old "Reading paths" list and
docs/README.md's grouped one, since drifted out of sync with each
other; and the flat layout not matching the grouping we already had).
Fixed every cross-reference this moved across the whole repo (~120
files: docs/ internal links at every depth, Rust doc comments, nix
module option docs, crate READMEs) -- verified two ways: a grep sweep
confirming zero remaining references to any old path, and a script
that resolves every markdown link in docs/**/*.md + CLAUDE.md +
README.md against the filesystem and reports anything that doesn't
exist (zero broken links).
Collapsed CLAUDE.md's "Reading paths" section (the duplicate) down to
a pointer at docs/README.md, now the single index. Rewrote
docs/README.md itself to use the new subdirectory paths and added the
one doc it was missing that CLAUDE.md's old copy had (pr-review-gate.md).
Classified all 22 docs/*.md files first via a haiku subagent (mara's
suggestion) on two axes -- proposed grouping and operator-vs-
implementation focus -- before finalizing the taxonomy; spot-checked
the report and found internal inconsistencies (its classification
table disagreed with its own summary section for a few files), so this
taxonomy is my original proposal + the one correction mara gave
directly, not a blind application of the subagent's table. The
operator-focus data it gathered is still useful for a follow-up
content pass (docs skewing 'mixed' rather than pure operator-facing),
not addressed in this PR -- structure only.
nix fmt clean, both pre-push lints clean.
The gateway's nginx + dnsmasq no longer run in their own nspawn container.
`nix/host-modules/hive-gateway/default.nix` loses the
`containers.hive-gateway` wrapper and everything that existed only to punch
holes in it: `privateNetwork = false`, `CAP_NET_ADMIN`, five bind mounts,
its own `stateVersion`, `networking.firewall.enable = false`,
`networking.resolvconf.enable = false`, and the `hive-gateway-resolv`
path+service pair. 465 -> 303 lines.
The container never bought isolation here. It shared the host netns by
necessity — nginx binds the host's :80/:443, dnsmasq answers on the bridge —
so each of those settings was undoing a boundary the gateway could not
afford in the first place.
Four things made it more than a deletion, none of them visible in the nix
diff:
- The self-signed cert service also imports the hive CA leaf, so removing it
with the container would have left nginx naming a missing cert file, which
it refuses to load at all.
- The nginx reload is a hive-priv verb. It still needs root, but no longer
for the reason its doc gave, and `--machine=` was both transport and
scope — so the unit name is now hard-coded in the helper as the
containment.
- The lifecycle verb named a container that stops existing.
- `journalctl -M hive-gateway` had no machine to enter.
Per the operator's ruling, the operator verb keeps working and agents lose
it. `InfraContainer` answered three questions that used to share an answer;
it now splits into `name()` (identity), `target()` (Container vs HostUnit),
`service_unit()` (the systemd unit), and `agent_restartable()`, which the
MCP restart path checks before the capability so the refusal cannot read as
"ask for infra_admin". `SIBLING_CONTAINERS` drops the gateway — it gates the
requests that name a container as a string — while `FromStr` still accepts
it, because that answers what a name is, not who may act on it. The
dashboard's gateway journal reads host journald filtered to `nginx.service`.
Prose was corrected where it only named a location, and re-argued where the
container was doing security work: a `0666` per-agent socket was safe
because only the gateway container had the directory bind-mounted. There is
no mount now, so the directory permissions are the whole of the access
control — the constraint holds, its mechanism doesn't.
Gate: nix fmt / clippy --all-targets -D warnings / cargo test all clean (710
tests); hivectl-cli.md regenerated from the clap tree. The nix eval was run
in both TLS shapes at this commit: every delta in the rendered
virtualHosts is one of the three intended path moves, dnsmasq settings are
byte-identical, and the absence probe flips true -> false with bindMounts
emptied.