Topology executor: accept a durable ZEROCLAW_TOKEN (skip one-time pairing)
ci / gates (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / sandbox-k8s (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / e2e (push) Has been cancelled

ZeroClaw pairing codes are single-use, so a static ZEROCLAW_PAIRING_CODE only
works for the first run. Add ZEROCLAW_TOKEN: pair once out-of-band, set the
durable bearer, and runs are repeatable. PAIRING_CODE stays as a fallback; one
of the two is required.

Validated live on gw-04: a single-node pipeline driven through the deployed
authed POST /api/topologies/run drove a real ZeroClaw role-agent over /ws/chat
and returned a RunRecord with real output + token metering, persisted to
topology_runs.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-16 13:19:43 -07:00
co-authored by Claude Opus 4.8
parent 6d77a0acc1
commit d1409a0776
+12 -9
View File
@@ -62,26 +62,29 @@ impl ZeroClawDriveExecutor {
/// Build from the environment: /// Build from the environment:
/// - `ZEROCLAW_GATEWAY_URL` (required) e.g. `http://127.0.0.1:42617` /// - `ZEROCLAW_GATEWAY_URL` (required) e.g. `http://127.0.0.1:42617`
/// - `ZEROCLAW_PAIRING_CODE` (required) /// - `ZEROCLAW_TOKEN` (preferred) a durable bearer token — pair once
/// out-of-band, set this, and runs are repeatable. If set, no pairing.
/// - `ZEROCLAW_PAIRING_CODE` (fallback) a one-time pairing code — consumed
/// on first use, so only good for a single run. One of TOKEN/CODE required.
/// - `ZEROCLAW_AGENT_MAP` (optional) `role=alias,role=alias` /// - `ZEROCLAW_AGENT_MAP` (optional) `role=alias,role=alias`
/// - `ZEROCLAW_DEFAULT_AGENT` (optional, default `scout`) /// - `ZEROCLAW_DEFAULT_AGENT` (optional, default `scout`)
pub fn from_env() -> Result<Self, String> { pub fn from_env() -> Result<Self, String> {
let gateway_url = let gateway_url =
std::env::var("ZEROCLAW_GATEWAY_URL").map_err(|_| "ZEROCLAW_GATEWAY_URL not set")?; std::env::var("ZEROCLAW_GATEWAY_URL").map_err(|_| "ZEROCLAW_GATEWAY_URL not set")?;
let pairing_code = let token = std::env::var("ZEROCLAW_TOKEN").ok().filter(|t| !t.is_empty());
std::env::var("ZEROCLAW_PAIRING_CODE").map_err(|_| "ZEROCLAW_PAIRING_CODE not set")?; let pairing_code = std::env::var("ZEROCLAW_PAIRING_CODE").unwrap_or_default();
if token.is_none() && pairing_code.is_empty() {
return Err("set ZEROCLAW_TOKEN or ZEROCLAW_PAIRING_CODE".to_string());
}
let default_alias = let default_alias =
std::env::var("ZEROCLAW_DEFAULT_AGENT").unwrap_or_else(|_| "scout".to_string()); std::env::var("ZEROCLAW_DEFAULT_AGENT").unwrap_or_else(|_| "scout".to_string());
let role_aliases = std::env::var("ZEROCLAW_AGENT_MAP") let role_aliases = std::env::var("ZEROCLAW_AGENT_MAP")
.ok() .ok()
.map(|s| parse_agent_map(&s)) .map(|s| parse_agent_map(&s))
.unwrap_or_default(); .unwrap_or_default();
Ok(Self::new( let mut exec = Self::new(gateway_url, pairing_code, role_aliases, default_alias);
gateway_url, exec.token = Arc::new(Mutex::new(token));
pairing_code, Ok(exec)
role_aliases,
default_alias,
))
} }
fn alias_for(&self, role: &str) -> String { fn alias_for(&self, role: &str) -> String {