fix(missions): bind graph nodes to claws via attrs, not a dropped top-level key
`inject_node_agents` wrote the claw alias as a top-level `"agent"` key on
each graph node, but `cm_topology::Node` only deserializes `{id, role,
level, attrs}` — serde silently dropped it. `TurnRequest::agent` came back
`None` and every mission turn fell back to `ZEROCLAW_DEFAULT_AGENT`
(`scout`), running with scout's workspace and tools instead of the
mission's claws. The runtime trace confirms it: every turn logged
`"agent_alias":"scout"`.
That is why mission agents reported an "empty greenfield" workspace and
emitted artifacts inline instead of writing them: scout is jailed to
`/zeroclaw-data/.zeroclaw/agents/scout/workspace` and cannot see
`/mission/repo`. The per-mission provisioning and `workspace.path` pinning
shipped earlier were correct — they were just applied to agents that
nothing ever drove.
- bind into `node.attrs["agent"]` (top-level key kept for display/debug)
- extract the DB-free `apply_node_agents` and add a regression test that
round-trips through the real `TopologyGraph` deserializer, which is the
guard that was missing
- log loudly in `topology_exec::run_turn` when a node falls back to the
default agent, instead of silently swapping in a different agent
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0a647c0bfa
commit
9bc5f6a142
@@ -354,7 +354,19 @@ impl TurnExecutor for ZeroClawDriveExecutor {
|
||||
.map(str::trim)
|
||||
.filter(|a| !a.is_empty())
|
||||
.map(str::to_string)
|
||||
.unwrap_or_else(|| self.alias_for(&req.role));
|
||||
.unwrap_or_else(|| {
|
||||
// Falling back here means the graph node was never bound to a
|
||||
// claw, so the turn runs as the default agent with the DEFAULT
|
||||
// agent's workspace and tools — not the mission's. That silently
|
||||
// produced whole missions of unusable output, so say so loudly.
|
||||
let fallback = self.alias_for(&req.role);
|
||||
eprintln!(
|
||||
"topology_exec: node={} role={} has no bound agent — falling back to `{fallback}` \
|
||||
(its workspace/tools, NOT the mission's)",
|
||||
req.node_id, req.role,
|
||||
);
|
||||
fallback
|
||||
});
|
||||
let prompt = Self::build_prompt(&req);
|
||||
self.drive(&alias, &prompt).await
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user