Watch
0
0
Fork
You've already forked hyperhive
0

Compare commits

..
32 changed files with 398 additions and 108 deletions

View file

@ -80,14 +80,19 @@ module docs for the exact call chain.
The **manager (`ruth`)** differs from other agents in naming/bootstrap, The **manager (`ruth`)** differs from other agents in naming/bootstrap,
its socket flavour and its default permissions; its prompt, tool its socket flavour and its default permissions; its prompt, tool
allow-list and state dirs work as for every other agent. allow-list and state dirs work as for every other agent. A hive with
`services.hyperhive.ruthless = true` runs no manager: `hive-c0re` skips
its root-agent create/start sweep
(`hive-c0re/src/workers/auto_update.rs`), and this section doesn't
apply.
<!-- vale write-good.Passive = NO --> <!-- vale write-good.Passive = NO -->
- **Naming/bootstrap** — the manager's broker recipient name, state-dir - **Naming/bootstrap** — the manager's broker recipient name, state-dir
key, and nixos-container name are all `ruth` (container `h-ruth`). key, and nixos-container name are all `ruth` (container `h-ruth`).
Like every agent it comes from the swarm dashboard or `hive-c0re` spawns it directly at boot if missing, with no operator
`swarmctl agent create`; `hive-c0re` creates nothing at boot. approval step — every other agent is created at swarm level
(`swarmctl agent create`).
Roster-wise, `ruth` is just another entry. Roster-wise, `ruth` is just another entry.
- **Wire-protocol** — `hive-core-agent-sock`'s unified `Request` enum - **Wire-protocol** — `hive-core-agent-sock`'s unified `Request` enum
is the same on both socket flavours. Container lifecycle is the same on both socket flavours. Container lifecycle

View file

@ -106,8 +106,9 @@ notes) is
reuses prior config + login. With `purge = true` the agent's reuses prior config + login. With `purge = true` the agent's
`/var/lib/hyperhive/{agents,applied}/<name>/` trees are also `/var/lib/hyperhive/{agents,applied}/<name>/` trees are also
wiped (config history + creds + notes gone forever). The wiped (config history + creds + notes gone forever). The
root/bootstrap container is destroyable like any other, and stays gone root/bootstrap container is destroyable like any other — hive-c0re
until you create ruth again from the swarm dashboard. recreates it on the next startup if it's absent, so destroying it's
transient.
## Meta flake ## Meta flake
@ -142,7 +143,7 @@ autoupdate flake-rev bump (one shot before per-agent rebuilds).
`meta::sync_agents(hive: &HiveEnv, agents: &[AgentSpec])` — `hive` `meta::sync_agents(hive: &HiveEnv, agents: &[AgentSpec])` — `hive`
carries `hyperhive_flake`, `dashboard_port`, and the rest of the carries `hyperhive_flake`, `dashboard_port`, and the rest of the
per-hive config — is the idempotent reconciler called by `provision_container`, per-hive config — is the idempotent reconciler called by `spawn`,
`destroy`, `rebuild`, and the startup migration. Renders `flake.nix` `destroy`, `rebuild`, and the startup migration. Renders `flake.nix`
from the agent list; if it differs from disk, runs from the agent list; if it differs from disk, runs
`nix flake lock` + commits as `regenerate meta flake` (or `nix flake lock` + commits as `regenerate meta flake` (or
@ -313,12 +314,11 @@ No state loss in either migration: claude creds, `/state/` notes, the
events DB, and both proposed + applied history all survive. The root events DB, and both proposed + applied history all survive. The root
agent keeps its session; sub-agents stay logged in. agent keeps its session; sub-agents stay logged in.
## The root/bootstrap container ## The root/bootstrap container is hive-c0re-managed
The root agent container runs through the **same lifecycle as The root agent container runs through the **same lifecycle as
sub-agents**. Nothing creates it at hive boot: you create ruth from the sub-agents**. On `hive-c0re serve` startup, if `ruth` is missing,
swarm dashboard like any agent, and an existing ruth container starts with hive-c0re creates it. The root agent's flake lives at
the hive like every other. The root agent's flake lives at
`/var/lib/hyperhive/applied/ruth/`; its proposed config at `/var/lib/hyperhive/applied/ruth/`; its proposed config at
`/var/lib/hyperhive/agents/ruth/config/`. The root agent can edit its own `/var/lib/hyperhive/agents/ruth/config/`. The root agent can edit its own
`agent.nix` (visible inside the container at `/agents/ruth/config/`) `agent.nix` (visible inside the container at `/agents/ruth/config/`)
@ -336,13 +336,14 @@ Differences from sub-agents:
and `/var/lib/hyperhive/applied` → `/applied` (RO) so the root agent and `/var/lib/hyperhive/applied` → `/applied` (RO) so the root agent
can `git fetch` deployed/failed/denied tags from any agent's can `git fetch` deployed/failed/denied tags from any agent's
authoritative applied repo (see "Root-agent view of applied" below). authoritative applied repo (see "Root-agent view of applied" below).
- Its socket comes from the generic per-agent registration, in the same - `socket_server::start_manager` binds the root agent's socket,
pure transport with no dedicated helpers — it uses the same
per-agent runtime dir as any other agent (`/run/hyperhive/agents/ruth/`), per-agent runtime dir as any other agent (`/run/hyperhive/agents/ruth/`),
not a special manager-only path. not a special manager-only path.
**Migration note** (for older hosts): drop any `containers.root = **Migration note** (for older hosts): drop any `containers.root =
{ ... }` block from your host NixOS config, and any `services.hyperhive.ruthless` { ... }` block from your host NixOS config. hyperhive creates and
definition, then create ruth from the swarm dashboard. updates the root agent itself.
## Root-agent policy ## Root-agent policy

View file

@ -31,9 +31,9 @@ Beyond that:
restart and rebuild** — only an explicit purge (or a hive restart and rebuild** — only an explicit purge (or a hive
`--purge`-style host operation, or the agent's own choices) touches `--purge`-style host operation, or the agent's own choices) touches
them. them.
- The **root/bootstrap agent has no guard**: destroying ruth is as final as - The **root/bootstrap agent is special**: it isn't really destroyable
for any agent, and she stays gone until you create her again from the in practice — hive-c0re recreates it automatically on its next
swarm dashboard. startup if it's ever gone.
Everything below this point is implementation detail: exact table Everything below this point is implementation detail: exact table
schemas, file layouts, and internal migration mechanics. schemas, file layouts, and internal migration mechanics.
@ -432,9 +432,11 @@ an agent's state. The mechanics, for completeness:
- `PURG3` wipes `/var/lib/hyperhive/{agents,applied}/<name>/` — the - `PURG3` wipes `/var/lib/hyperhive/{agents,applied}/<name>/` — the
union of everything `DESTR0Y` left behind. union of everything `DESTR0Y` left behind.
`actions::destroy` has no guard for the root/bootstrap agent: it destroys ruth `actions::destroy` implements the root/bootstrap agent's specialness as a soft policy
like any other agent, and she stays gone until you create her again from the guard that refuses to destroy it, backstopped by
swarm dashboard. `auto_update::ensure_root_agent`, which recreates it on the next
hive-c0re startup if it's ever absent — so even without the guard,
destroying it would only be transient.
### btrfs subvolumes for `/var/lib/hyperhive/agents/<name>` ### btrfs subvolumes for `/var/lib/hyperhive/agents/<name>`

View file

@ -89,20 +89,24 @@ swarmctl forge make-admin mara
If the forge already has a local account with your username, the first If the forge already has a local account with your username, the first
SSO login asks for its forge password once, to link the two. SSO login asks for its forge password once, to link the two.
## 4 · Ruth ## 4 · Ruth's store identity
hive-c0re creates no agent at startup. You create ruth, the manager agent, hive-c0re creates the manager agent, ruth, on its own at startup — so
like any other: open the swarm UI start page and click the paperclip, which unlike agents made through the swarm, she has no store identity yet:
opens the create form with `ruth` filled in. The form declares her, creates
her config repo and deploys her to the hive you pick.
The swarm declares a new agent `paused`, so without a further step ruth runs ```bash
but drives no turns. Open `/agents` in the swarm UI and set her `up` from her swarmctl agent create ruth --hive <hive> # on the swarm-controller's host
card. hivectl agent ruth rebuild # on ruth's hive: hands her the identity
```
On a hive that already has a ruth container from an earlier release, create `agent create` declares an agent the swarm has no declaration for as
her once the same way, so that her config repo and store identity exist. The `paused`, so without a further step ruth keeps running but drives no turns.
container keeps starting with the hive meanwhile. `swarmctl` has no verb for wanted state: open `/agents` in the swarm UI and set
her `up` from her card.
Within about ten minutes the controller creates her forge user and matrix
account and mints their tokens into the store, where she fetches them.
`swarmctl agent mint-forge-token ruth` skips the wait for the forge token.
## 5 · Matrix ## 5 · Matrix

View file

@ -174,7 +174,9 @@ Each row carries enough context that the caller renders
it directly as a bulleted list, no follow-up fetch needed. it directly as a bulleted list, no follow-up fetch needed.
`GetLooseEnds` takes no target — it always returns the caller's own `GetLooseEnds` takes no target — it always returns the caller's own
rows. `Reminder` rows always carry `owner == self`. rows, on both the agent and manager sockets alike (the manager, `ruth`,
is a normal agent here, just deployed automatically with different
default permissions). `Reminder` rows always carry `owner == self`.
Per-variant fields: Per-variant fields:

View file

@ -178,7 +178,7 @@ Power toggles never commit to the meta repo. Every operator power surface —
dashboard buttons, the MCP tools, and `hivectl stop/start/restart/kill` — dashboard buttons, the MCP tools, and `hivectl stop/start/restart/kill` —
rides the queue through that power layer, so intent, lease serialization, rides the queue through that power layer, so intent, lease serialization,
and crash-watch suppression can't drift per surface; the only direct starts and crash-watch suppression can't drift per surface; the only direct starts
left are infra containers (no lease, no left are the root-agent bootstrap and infra containers (no lease, no
harness). Cancelling a still-queued power DAG reverts `wanted` to the harness). Cancelling a still-queued power DAG reverts `wanted` to the
observed state — a cancel means "don't do it," not "do it later." hive-c0re observed state — a cancel means "don't do it," not "do it later." hive-c0re
seeds agents without a row from observed state on first touch (running ⇒ seeds agents without a row from observed state on first touch (running ⇒

View file

@ -98,7 +98,7 @@ variants regardless of who sends them):
A small `Surface` trait, with one zero-sized impl, factors A small `Surface` trait, with one zero-sized impl, factors
`hive-agent`'s wire types (`hive_core_agent_sock::{Request, Response}` — `hive-agent`'s wire types (`hive_core_agent_sock::{Request, Response}` —
one unified enum shared by every agent socket) and its turn one unified enum shared by the agent and manager sockets) and its turn
loop, so the loop itself has no per-role branches. See loop, so the loop itself has no per-role branches. See
`hive-agent/src/main.rs`'s module `hive-agent/src/main.rs`'s module
doc for the trait shape. doc for the trait shape.

View file

@ -6,7 +6,7 @@ use rmcp::schemars;
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)] #[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct SendArgs { pub struct SendArgs {
/// Logical agent name to deliver the message to (e.g. `"bob"`, /// Logical agent name to deliver the message to (e.g. `"manager"`,
/// `"alice"`). `"operator"` is refused: reach the operator over matrix /// `"alice"`). `"operator"` is refused: reach the operator over matrix
/// if you have it. /// if you have it.
pub to: String, pub to: String,
@ -106,7 +106,7 @@ pub struct SetStatusArgs {
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)] #[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct GetAgentMetaArgs { pub struct GetAgentMetaArgs {
/// Logical name of the agent to query (e.g. `"iris"`, `"bob"`). /// Logical name of the agent to query (e.g. `"iris"`, `"manager"`).
/// Omit to query your own identity + status — replaces the /// Omit to query your own identity + status — replaces the
/// previous `whoami` self-introspection tool. /// previous `whoami` self-introspection tool.
#[serde(default)] #[serde(default)]

View file

@ -148,7 +148,7 @@ impl AgentServer {
let log = format!("{args:?}"); let log = format!("{args:?}");
let to = args.to.clone(); let to = args.to.clone();
// Check per-agent allow-list (services.hyperhive.agent.allowedRecipients). When no // Check per-agent allow-list (services.hyperhive.agent.allowedRecipients). When no
// policy file is present the check is a no-op. // policy file is present (e.g. manager containers) the check is a no-op.
if let Err(refusal) = crate::send_allow::check_send_allowed(&to) { if let Err(refusal) = crate::send_allow::check_send_allowed(&to) {
return run_tool_envelope("send", log, async move { refusal }).await; return run_tool_envelope("send", log, async move { refusal }).await;
} }

View file

@ -7,7 +7,8 @@
/// Where the NixOS module writes the per-agent send allow-list (see /// Where the NixOS module writes the per-agent send allow-list (see
/// `nix/agent-modules/mcp.nix`). Empty list = unrestricted (the /// `nix/agent-modules/mcp.nix`). Empty list = unrestricted (the
/// default). Non-empty list constrains `mcp__hyperhive__send`'s `to` /// default). Non-empty list constrains `mcp__hyperhive__send`'s `to`
/// field. /// field; the manager is always implicitly permitted regardless of
/// the list contents.
const SEND_ALLOW_PATH: &str = "/etc/hyperhive/send-allow.json"; const SEND_ALLOW_PATH: &str = "/etc/hyperhive/send-allow.json";
/// Enforce the per-agent send allow-list. Returns `Ok` when the /// Enforce the per-agent send allow-list. Returns `Ok` when the

View file

@ -394,7 +394,7 @@ struct ExtraLink {
} }
/// Best-effort: pull the last 30 messages addressed to us via the /// Best-effort: pull the last 30 messages addressed to us via the
/// per-agent socket. Empty list on any transport / decode /// per-agent / manager socket. Empty list on any transport / decode
/// failure — the inbox section is decorative, not authoritative. /// failure — the inbox section is decorative, not authoritative.
async fn recent_inbox(socket: &std::path::Path) -> Vec<hive_sh4re::inbox::InboxRow> { async fn recent_inbox(socket: &std::path::Path) -> Vec<hive_sh4re::inbox::InboxRow> {
const LIMIT: u64 = 30; const LIMIT: u64 = 30;

View file

@ -37,8 +37,8 @@ to track it:
- **`agent_config/`** — per-agent registries (permissions, - **`agent_config/`** — per-agent registries (permissions,
resource limits, topology). resource limits, topology).
- **`stats/`** — dashboard metrics aggregation + OTEL export. - **`stats/`** — dashboard metrics aggregation + OTEL export.
- **`socket_server/`** — the unix-socket request server for the - **`socket_server/`** — the unix-socket request server shared by
per-agent sockets. per-agent + manager sockets.
- **`forge/`** — optional Forgejo wiring (`docs/integrations/forge.md`). - **`forge/`** — optional Forgejo wiring (`docs/integrations/forge.md`).
- **`coordinator.rs`** — top-level wiring for `serve`. - **`coordinator.rs`** — top-level wiring for `serve`.
- **`meta.rs`**, **`migrate.rs`** — the meta flake + schema/state - **`meta.rs`**, **`migrate.rs`** — the meta flake + schema/state

View file

@ -126,9 +126,10 @@ async fn ff_applied_main(
/// is cleared because its contents (the mcp socket) don't survive restarts /// is cleared because its contents (the mcp socket) don't survive restarts
/// anyway. With `purge=true` the persistent trees are also wiped — config /// anyway. With `purge=true` the persistent trees are also wiped — config
/// history, claude creds, notes — there is no undo. /// history, claude creds, notes — there is no undo.
/// The bootstrap/root container is destroyable like any other. Nothing /// The bootstrap/root container is destroyable like any other: it's
/// recreates it afterwards: ruth stays gone until she is created again from /// imperative infra that `auto_update::ensure_root_agent` recreates on the
/// the swarm dashboard. /// next hive-c0re startup if absent, so destroying it is transient rather
/// than something to refuse at the API.
/// ///
/// Submits the teardown DAG and returns — it does not wait for the container to /// Submits the teardown DAG and returns — it does not wait for the container to
/// go away. Same contract as every other lifecycle op (`rebuild`, `kill`, /// go away. Same contract as every other lifecycle op (`rebuild`, `kill`,

View file

@ -165,11 +165,14 @@ mod tests {
#[test] #[test]
fn apply_reconcile_adds_missing_live_agent() { fn apply_reconcile_adds_missing_live_agent() {
let live = vec!["alice".to_owned(), "newbie".to_owned()]; let live = vec![
crate::lifecycle::MANAGER_NAME.to_owned(),
"newbie".to_owned(),
];
let (next, changed) = apply_reconcile(&BTreeSet::new(), &live, &[]); let (next, changed) = apply_reconcile(&BTreeSet::new(), &live, &[]);
assert!(changed); assert!(changed);
assert!(next.contains("newbie")); assert!(next.contains("newbie"));
assert!(next.contains("alice")); assert!(next.contains(crate::lifecycle::MANAGER_NAME));
} }
#[test] #[test]

View file

@ -496,7 +496,7 @@ impl Coordinator {
} }
/// Snapshot the hive-wide configuration fields as a [`HiveEnv`]. /// Snapshot the hive-wide configuration fields as a [`HiveEnv`].
/// Pass the result to `lifecycle::provision_container` / `rebuild` / `meta::sync_agents` /// Pass the result to `lifecycle::spawn` / `rebuild` / `meta::sync_agents`
/// instead of threading the individual fields separately. /// instead of threading the individual fields separately.
#[must_use] #[must_use]
pub fn hive_env(&self) -> HiveEnv { pub fn hive_env(&self) -> HiveEnv {

View file

@ -296,6 +296,7 @@ async fn reload_gateway_nginx() {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::lifecycle::MANAGER_NAME;
// ── legacy mode (frontend_dir = None) ────────────────────────────── // ── legacy mode (frontend_dir = None) ──────────────────────────────
@ -308,10 +309,13 @@ mod tests {
} }
#[test] #[test]
fn render_includes_every_agent() { fn render_includes_manager() {
let names: Vec<String> = ["alice", "iris"].iter().map(|s| (*s).to_owned()).collect(); let names: Vec<String> = [MANAGER_NAME, "iris"]
.iter()
.map(|s| (*s).to_owned())
.collect();
let body = render(&names, None); let body = render(&names, None);
assert!(body.contains("/agent/alice/")); assert!(body.contains(&format!("/agent/{MANAGER_NAME}/")));
assert!(body.contains("/agent/iris/")); assert!(body.contains("/agent/iris/"));
} }
@ -457,10 +461,13 @@ mod tests {
} }
#[test] #[test]
fn split_mode_includes_every_agent() { fn split_mode_includes_manager() {
let names: Vec<String> = ["alice", "iris"].iter().map(|s| (*s).to_owned()).collect(); let names: Vec<String> = [MANAGER_NAME, "iris"]
.iter()
.map(|s| (*s).to_owned())
.collect();
let body = render(&names, Some(FAKE_FRONTEND)); let body = render(&names, Some(FAKE_FRONTEND));
assert!(body.contains("/agent/alice/")); assert!(body.contains(&format!("/agent/{MANAGER_NAME}/")));
assert!(body.contains("/agent/iris/")); assert!(body.contains("/agent/iris/"));
} }

View file

@ -34,6 +34,12 @@ pub const MAX_AGENT_NAME: usize = 9;
/// Logical name of the manager agent (broker recipient, state-dir key, /// Logical name of the manager agent (broker recipient, state-dir key,
/// meta flake attribute). All persistent state lives under `ruth/`. /// meta flake attribute). All persistent state lives under `ruth/`.
pub const MANAGER_NAME: &str = "ruth"; pub const MANAGER_NAME: &str = "ruth";
/// Container name of the manager. Uses the same `h-` prefix as sub-agents
/// so `nixos-container list` output is uniform and the list filter is
/// a single `starts_with(AGENT_PREFIX)` check. Logical name → container
/// name: `ruth` → `h-ruth`.
pub const MANAGER_CONTAINER: &str = "h-ruth";
/// Mount point of the per-agent runtime directory inside the container. /// Mount point of the per-agent runtime directory inside the container.
pub const CONTAINER_RUNTIME_MOUNT: &str = "/run/hive"; pub const CONTAINER_RUNTIME_MOUNT: &str = "/run/hive";
@ -219,6 +225,15 @@ async fn port_collision(self_name: &str) -> Result<Option<String>> {
Ok(None) Ok(None)
} }
pub async fn spawn(name: &str, hive: &HiveEnv, paths: &AgentPaths) -> Result<()> {
create_container(name, hive, paths).await?;
// Runtime dir must exist before nixos-container start (nspawn bind-mount
// source). Create it here so callers don't need a separate preamble step.
ensure_agent_runtime_dir(name)?;
write_dropins(name, hive, paths).await?;
priv_run("start", name).await
}
/// First-spawn pre-create provisioning — the job queue's `Provision` /// First-spawn pre-create provisioning — the job queue's `Provision`
/// node. Fail fast on a port collision, set up the proposed/applied /// node. Fail fast on a port collision, set up the proposed/applied
/// repos + state subvolume + claude/notes dirs, then register the new /// repos + state subvolume + claude/notes dirs, then register the new
@ -251,6 +266,16 @@ pub async fn create_only(name: &str) -> Result<()> {
priv_run("create", name).await priv_run("create", name).await
} }
/// First-spawn provisioning + `nixos-container create`, without the
/// drop-in write or the start. `spawn` composes this with
/// `write_dropins` + start for direct callers (root-agent bootstrap);
/// the job queue instead runs `provision_container` (`Provision` node)
/// and `create_only` (`Create` node) as separate DAG steps.
pub async fn create_container(name: &str, hive: &HiveEnv, paths: &AgentPaths) -> Result<()> {
provision_container(name, hive, paths).await?;
create_only(name).await
}
/// Rebuild-path preamble, run by the job queue's `Prebuild` node: fail fast on /// Rebuild-path preamble, run by the job queue's `Prebuild` node: fail fast on
/// a port collision, then make sure the applied repo + state dirs exist. /// a port collision, then make sure the applied repo + state dirs exist.
/// Container untouched. /// Container untouched.

View file

@ -1,6 +1,6 @@
//! Loose-ends aggregator. Assembles a `Vec<LooseEnd>` for a single agent //! Loose-ends aggregator. Assembles a `Vec<LooseEnd>` for a single agent
//! (`for_agent`) — always the caller's own. `Request::GetLooseEnds` from //! (`for_agent`) — always the caller's own. `Request::GetLooseEnds` from
//! an agent socket lands here. Reminders and todos are //! either the agent or manager socket lands here. Reminders and todos are
//! agent-local (in-container `hive-agent` stores) and never sourced from //! agent-local (in-container `hive-agent` stores) and never sourced from
//! here. //! here.
//! //!

View file

@ -267,17 +267,26 @@ async fn cmd_serve(
// Copy value first for the dashboard + knowledge-webhook tasks. // Copy value first for the dashboard + knowledge-webhook tasks.
let dashboard_port = env.dashboard_port; let dashboard_port = env.dashboard_port;
let coord = Arc::new(Coordinator::open(&db, env, model_prices, build_slots)?); let coord = Arc::new(Coordinator::open(&db, env, model_prices, build_slots)?);
socket_server::start_manager(coord.clone())?;
// Idempotent pre-flight: rewrite pre-meta-layout applied // Idempotent pre-flight: rewrite pre-meta-layout applied
// repos, ensure proposed repos carry the `applied` // repos, ensure proposed repos carry the `applied`
// remote, bootstrap the meta repo, repoint containers at // remote, bootstrap the meta repo, repoint containers at
// `meta#<name>` (one-shot, guarded by a marker file). // `meta#<name>` (one-shot, guarded by a marker file).
// Runs before the boot reconcile, so agents are built against meta. // Runs before manager auto-spawn so the new manager is
// built against meta from the first attempt.
if let Err(e) = migrate::run(&coord).await { if let Err(e) = migrate::run(&coord).await {
tracing::warn!(error = ?e, "startup migration failed"); tracing::warn!(error = ?e, "startup migration failed");
} }
// Before any agent starts, so the bind mounts it gets from the stored // Before any agent starts, so the bind mounts it gets from the stored
// permissions are the meta repo's grants until the swarm answers. // permissions are the meta repo's grants until the swarm answers.
crate::workers::permissions_import::seed_local(); crate::workers::permissions_import::seed_local();
// Auto-create the root agent container if it isn't there yet. Block
// on this — without root the system has no manager harness.
// Failures are logged but allowed: a broken auto-spawn shouldn't
// make the dashboard unreachable for debugging.
if let Err(e) = auto_update::ensure_root_agent(&coord).await {
tracing::warn!(error = ?e, "auto-spawn root agent failed");
}
// Auto-update in the background — don't block service start. // Auto-update in the background — don't block service start.
// Sub-agent rebuilds can take tens of seconds; we want the admin // Sub-agent rebuilds can take tens of seconds; we want the admin
// socket up immediately. // socket up immediately.

View file

@ -27,7 +27,7 @@ use anyhow::{Context, Result};
use tokio::process::Command; use tokio::process::Command;
use crate::coordinator::Coordinator; use crate::coordinator::Coordinator;
use crate::lifecycle::{self, AGENT_PREFIX}; use crate::lifecycle::{self, AGENT_PREFIX, MANAGER_CONTAINER, MANAGER_NAME};
use crate::meta; use crate::meta;
const KILL_SWITCH: &str = "HIVE_SKIP_META_MIGRATION"; const KILL_SWITCH: &str = "HIVE_SKIP_META_MIGRATION";
@ -157,7 +157,11 @@ async fn enumerate_agents() -> Result<Vec<hive_types::Ident>> {
Ok(containers Ok(containers
.into_iter() .into_iter()
.filter_map(|c| { .filter_map(|c| {
let name = c.strip_prefix(AGENT_PREFIX)?; let name = if c == MANAGER_CONTAINER {
MANAGER_NAME
} else {
c.strip_prefix(AGENT_PREFIX)?
};
hive_types::Ident::parse(name).ok() hive_types::Ident::parse(name).ok()
}) })
.collect()) .collect())

Some files were not shown because too many files have changed in this diff Show more