fix(mission_runtime): re-add shared /zeroclaw-data mount for agent library
ci / gates (push) Successful in 5s
ci / frontend (push) Successful in 37s
ci / rust (push) Successful in 3m6s
ci / e2e (push) Skipped
ci / publish (push) Successful in 2m29s

Fresh runtimes had zero agents in their config so WS handshake with
?agent=scout returned 400. Bind-mount the shared runtimes data dir
so per-mission gateways inherit the seeded claw_* agents.

Per-mission pairing (minted via /admin/paircode/new) still works
against the shared devices.db — each mission gets its own accepted
token. Concurrency caveat on sqlite sessions.db documented in the
const doc comment.
This commit is contained in:
Omar Sobh
2026-07-22 18:24:11 -07:00
parent 70e7ab3ad6
commit 8ba9bf0c1c
+30 -4
View File
@@ -55,6 +55,22 @@ const EDGE_NETWORK: &str = "clawmates_edge";
/// by `clawmates-runtime.service`. /// by `clawmates-runtime.service`.
const MISSIONS_HOST_ROOT: &str = "/var/lib/clawmates-missions"; const MISSIONS_HOST_ROOT: &str = "/var/lib/clawmates-missions";
/// Host path holding the shared ZeroClaw config + seeded agent library
/// (built up over time by the shared clawmates-runtime.service). Per-
/// mission runtime containers bind-mount this so their gateway
/// inherits all the `claw_*` agents that team_template_loader has
/// provisioned. Each per-mission container then MINTS ITS OWN pairing
/// code via /admin/paircode/new so the accepted-tokens list is
/// independent per mission. Overridable for dev via
/// `CLAWMATES_RUNTIME_SEED_DIR`.
///
/// Concurrency caveat: the sqlite files under .zeroclaw/data/ are
/// currently shared across all per-mission runtimes AND the shared
/// runtime. Concurrent daemons opening the same sessions.db can
/// interleave; in practice topology_worker sequentializes runs per
/// mission so this rarely bites. Long-term: copy-on-write per mission.
const DEFAULT_SEED_DIR: &str = "/root/clawmates-runtime/data";
/// Deterministic docker container name for a mission's runtime. /// Deterministic docker container name for a mission's runtime.
/// Uses the full UUID hex — UUIDv7 encodes time in the leading bytes, /// Uses the full UUID hex — UUIDv7 encodes time in the leading bytes,
/// so a short prefix isn't guaranteed unique across missions minted /// so a short prefix isn't guaranteed unique across missions minted
@@ -150,13 +166,11 @@ impl MissionRuntimeProvisioner {
// present or docker start fails with EACCES/ENOENT. // present or docker start fails with EACCES/ENOENT.
let mission_dir = format!("{MISSIONS_HOST_ROOT}/{mission_id}"); let mission_dir = format!("{MISSIONS_HOST_ROOT}/{mission_id}");
let _ = tokio::fs::create_dir_all(&mission_dir).await; let _ = tokio::fs::create_dir_all(&mission_dir).await;
let seed_dir = std::env::var("CLAWMATES_RUNTIME_SEED_DIR")
.unwrap_or_else(|_| DEFAULT_SEED_DIR.to_string());
let mounts = vec![ let mounts = vec![
// Mount just this mission's directory. Agents can navigate // Mount just this mission's directory. Agents can navigate
// its `/repo` subdir but never see other missions'. // its `/repo` subdir but never see other missions'.
// `/zeroclaw-data` is intentionally NOT bind-mounted — each
// per-mission gateway boots with a fresh state directory
// and mints its own pairing code, scraped in
// `wait_for_pairing_code` below.
Mount { Mount {
target: Some("/mission".to_string()), target: Some("/mission".to_string()),
source: Some(mission_dir.clone()), source: Some(mission_dir.clone()),
@@ -164,6 +178,18 @@ impl MissionRuntimeProvisioner {
read_only: Some(false), read_only: Some(false),
..Default::default() ..Default::default()
}, },
// Share the shared-runtime data dir so this gateway inherits
// the seeded agent library (`claw_*` templates). We then
// mint a per-mission pairing code via /admin/paircode/new
// below — the mint writes into the shared devices.db but
// the resulting token is unique to this mission.
Mount {
target: Some("/zeroclaw-data".to_string()),
source: Some(seed_dir),
typ: Some(MountTypeEnum::BIND),
read_only: Some(false),
..Default::default()
},
]; ];
let host_config = HostConfig { let host_config = HostConfig {