drop legacy /state mount for manager (#604)
This commit is contained in:
parent
7aadf38483
commit
dc99e64b2d
4 changed files with 60 additions and 62 deletions
|
|
@ -26,11 +26,6 @@ pub const CONTAINER_RUNTIME_MOUNT: &str = "/run/hive";
|
|||
/// Persistent across destroy/recreate so OAuth login survives.
|
||||
pub const CONTAINER_CLAUDE_MOUNT: &str = "/root/.claude";
|
||||
|
||||
/// Mount point of the per-agent durable knowledge dir inside the container.
|
||||
/// Agents are told (system prompt) to keep `notes.md` and any other scratch
|
||||
/// state here; persists across destroy/recreate.
|
||||
pub const CONTAINER_NOTES_MOUNT: &str = "/state";
|
||||
|
||||
/// Mount point of the shared directory accessible to all agents.
|
||||
/// All agents can read/write here; agents should only put things they're
|
||||
/// willing to lose (other agents may delete them).
|
||||
|
|
@ -865,25 +860,29 @@ fn set_nspawn_flags(
|
|||
// below are gated on `container == MANAGER_NAME` anyway.
|
||||
let agent_name = container.strip_prefix(AGENT_PREFIX).unwrap_or(container);
|
||||
|
||||
// Compute the in-container state mount point. Sub-agents get
|
||||
// /agents/<name>/state; the manager keeps the legacy /state path.
|
||||
// Claude credentials always land at /root/.claude for all agents so
|
||||
// the `claude` CLI (which reads $HOME/.claude) finds them without any
|
||||
// HOME override.
|
||||
let notes_mount = if container == MANAGER_NAME {
|
||||
CONTAINER_NOTES_MOUNT.to_owned()
|
||||
} else {
|
||||
format!("/agents/{agent_name}/state")
|
||||
};
|
||||
// Claude credentials always land at /root/.claude so the
|
||||
// `claude` CLI (which reads $HOME/.claude) finds them without
|
||||
// any HOME override.
|
||||
let claude_mount = CONTAINER_CLAUDE_MOUNT;
|
||||
|
||||
let mut binds = format!(
|
||||
"--bind={runtime}:{CONTAINER_RUNTIME_MOUNT} --bind={claude}:{claude_mount} --bind={notes}:{notes_mount} --bind={shared}:{CONTAINER_SHARED_MOUNT}",
|
||||
"--bind={runtime}:{CONTAINER_RUNTIME_MOUNT} --bind={claude}:{claude_mount} --bind={shared}:{CONTAINER_SHARED_MOUNT}",
|
||||
runtime = runtime_dir.display(),
|
||||
claude = claude_dir.display(),
|
||||
notes = notes_dir.display(),
|
||||
shared = HOST_SHARED_ROOT,
|
||||
);
|
||||
|
||||
// Per-agent state at `/agents/<container>/state`. Skipped for
|
||||
// the manager — the `/agents` bind below already exposes its
|
||||
// own state (along with every sub-agent's). Pre-#604 the manager
|
||||
// had a bespoke `/state` legacy alias bind; that's gone.
|
||||
if container != MANAGER_NAME {
|
||||
let _ = write!(
|
||||
binds,
|
||||
" --bind={notes}:/agents/{agent_name}/state",
|
||||
notes = notes_dir.display(),
|
||||
);
|
||||
}
|
||||
if container == MANAGER_NAME {
|
||||
// systemd-nspawn refuses to start a container whose bind
|
||||
// source doesn't exist. The meta repo is created by the
|
||||
|
|
|
|||
|
|
@ -398,10 +398,19 @@ where
|
|||
};
|
||||
# Container-wide env: every service + co-process daemon can
|
||||
# resolve the agent's durable state dir without hard-coding it.
|
||||
# `environment.variables` only writes /etc/environment (login
|
||||
# shells); `systemd.globalEnvironment` is the analogue for
|
||||
# systemd units so tea-login / forge-avatar-sync /
|
||||
# matrix-avatar-sync etc. can read `$HYPERHIVE_STATE_DIR`
|
||||
# without each service having to redeclare it (#604).
|
||||
environment.variables = {
|
||||
HIVE_LABEL = name;
|
||||
HYPERHIVE_STATE_DIR = "/agents/${name}/state";
|
||||
};
|
||||
systemd.globalEnvironment = {
|
||||
HIVE_LABEL = name;
|
||||
HYPERHIVE_STATE_DIR = "/agents/${name}/state";
|
||||
};
|
||||
systemd.services.${service}.environment = parentEnv // {
|
||||
HIVE_PORT = toString port;
|
||||
HIVE_LABEL = name;
|
||||
|
|
|
|||
|
|
@ -196,14 +196,15 @@ pub fn write_payload(agent: &str, host_path: &Path, message: &str) -> Result<(),
|
|||
}
|
||||
|
||||
/// Container-visible state prefix the caller's `file_path` must live
|
||||
/// under. Sub-agents see their state at `/agents/<name>/state/`;
|
||||
/// the manager keeps the legacy `/state/` mount (see
|
||||
/// `lifecycle::set_nspawn_flags`). Auto-file paths use the same
|
||||
/// prefix so the round-trip is symmetric.
|
||||
/// under. Every agent sees its state at `/agents/<container>/state/`
|
||||
/// (see `lifecycle::set_nspawn_flags`). Auto-file paths use the same
|
||||
/// prefix so the round-trip is symmetric. The manager logical name
|
||||
/// maps to its container name (`hm1nd`) per `lifecycle::MANAGER_NAME` —
|
||||
/// the pre-#604 legacy `/state/` alias is gone.
|
||||
#[must_use]
|
||||
pub fn container_state_prefix(agent: &str) -> String {
|
||||
if agent == hive_sh4re::MANAGER_AGENT {
|
||||
"/state/".to_owned()
|
||||
format!("/agents/{}/state/", crate::lifecycle::MANAGER_NAME)
|
||||
} else {
|
||||
format!("/agents/{agent}/state/")
|
||||
}
|
||||
|
|
@ -275,18 +276,23 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn manager_uses_legacy_state_prefix() {
|
||||
// The manager container mounts its state at `/state/` (legacy),
|
||||
// not `/agents/manager/state/`. Same host path; different
|
||||
// container-visible path. resolve_host_path needs to know.
|
||||
assert_eq!(container_state_prefix("manager"), "/state/");
|
||||
let p = resolve_host_path("manager", "/state/reminders/x.md").unwrap();
|
||||
fn manager_uses_container_name_prefix() {
|
||||
// Post-#604: manager's container view of its state is at
|
||||
// `/agents/<MANAGER_NAME>/state/` (= `/agents/hm1nd/state/`),
|
||||
// same as every other agent — the legacy bare `/state/` mount
|
||||
// was dropped from lifecycle::set_nspawn_flags.
|
||||
assert_eq!(container_state_prefix("manager"), "/agents/hm1nd/state/");
|
||||
let p = resolve_host_path("manager", "/agents/hm1nd/state/reminders/x.md").unwrap();
|
||||
// NB: the host path still resolves under `agents/manager/`
|
||||
// (Coordinator::agent_notes_dir takes the broker LOGICAL name).
|
||||
// That's a pre-existing manager-logical-vs-container-name
|
||||
// discrepancy tracked separately in #162; out of scope here.
|
||||
assert_eq!(
|
||||
p,
|
||||
PathBuf::from("/var/lib/hyperhive/agents/manager/state/reminders/x.md")
|
||||
);
|
||||
// And the sub-agent prefix must NOT be accepted for the manager.
|
||||
assert!(resolve_host_path("manager", "/agents/manager/state/x.md").is_err());
|
||||
// And the legacy `/state/` prefix must NOT be accepted anymore.
|
||||
assert!(resolve_host_path("manager", "/state/x.md").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -684,19 +684,12 @@
|
|||
# No `set -e`: any subshell failure must not propagate.
|
||||
# A failed unit aborts `nixos-container update` which blocks rebuilds.
|
||||
FORGE_URL=${lib.escapeShellArg config.hyperhive.forge.url}
|
||||
# Manager bind-mounts state at /state; sub-agents at
|
||||
# /agents/<name>/state. Glob both — each container only sees
|
||||
# its own mount, so there is exactly one hit (or zero when
|
||||
# the forge hasn't been seeded yet).
|
||||
TOKEN_FILE=""
|
||||
for f in /state/forge-token /agents/*/state/forge-token; do
|
||||
if [ -f "$f" ]; then
|
||||
TOKEN_FILE="$f"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -z "$TOKEN_FILE" ]; then
|
||||
echo "tea-login: no forge-token found; skipping"
|
||||
# $HYPERHIVE_STATE_DIR is set system-wide by the meta flake
|
||||
# (systemd.globalEnvironment, /agents/<name>/state per agent
|
||||
# including manager post-#604).
|
||||
TOKEN_FILE="$HYPERHIVE_STATE_DIR/forge-token"
|
||||
if [ ! -f "$TOKEN_FILE" ]; then
|
||||
echo "tea-login: no forge-token at $TOKEN_FILE; skipping"
|
||||
exit 0
|
||||
fi
|
||||
TOKEN=$(cat "$TOKEN_FILE")
|
||||
|
|
@ -764,14 +757,10 @@
|
|||
exit 0
|
||||
fi
|
||||
FORGE_URL=${lib.escapeShellArg config.hyperhive.forge.url}
|
||||
TOKEN_FILE=""
|
||||
for f in /state/forge-token /agents/*/state/forge-token; do
|
||||
if [ -f "$f" ]; then
|
||||
TOKEN_FILE="$f"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -z "$TOKEN_FILE" ]; then
|
||||
# $HYPERHIVE_STATE_DIR is set system-wide by the meta flake
|
||||
# (systemd.globalEnvironment) to `/agents/<name>/state`.
|
||||
TOKEN_FILE="$HYPERHIVE_STATE_DIR/forge-token"
|
||||
if [ ! -f "$TOKEN_FILE" ]; then
|
||||
echo "forge-avatar-sync: no forge-token found; skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
|
@ -904,17 +893,12 @@
|
|||
exit 0
|
||||
fi
|
||||
# Token written by `hive-c0re::matrix::ensure_user_for` to the
|
||||
# agent's bind-mounted state dir. Either appears at the legacy
|
||||
# `/state/` (manager) or per-agent `/agents/<name>/state/` path.
|
||||
TOKEN_FILE=""
|
||||
for f in /state/matrix-token /agents/*/state/matrix-token; do
|
||||
if [ -f "$f" ]; then
|
||||
TOKEN_FILE="$f"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -z "$TOKEN_FILE" ]; then
|
||||
echo "matrix-avatar-sync: no matrix-token found; skipping"
|
||||
# agent's bind-mounted state dir. $HYPERHIVE_STATE_DIR is set
|
||||
# system-wide by the meta flake (systemd.globalEnvironment) to
|
||||
# `/agents/<name>/state`.
|
||||
TOKEN_FILE="$HYPERHIVE_STATE_DIR/matrix-token"
|
||||
if [ ! -f "$TOKEN_FILE" ]; then
|
||||
echo "matrix-avatar-sync: no matrix-token at $TOKEN_FILE; skipping"
|
||||
exit 0
|
||||
fi
|
||||
TOKEN=$(cat "$TOKEN_FILE")
|
||||
|
|
|
|||
Loading…
Reference in a new issue