fix(missions): grant coding tools + pin claw workspace to /mission/repo
Mission agents were burning ~275K tokens producing nothing: the coder had only file_read and its workspace was the empty ephemeral sandbox, so it dumped a full spec inline instead of writing files. Two root causes: 1. Risk-profile allowlists used pre-0.8 tool names. `coding_readwrite` allow-listed `file_write` (renamed to `file_edit` in ZeroClaw 0.8, and `file_write` now refuses on ephemeral workspaces) and omitted file_edit / content_search / glob_search / git_operations — the exact tools the phase prompt tells agents to use. Since allowed_tools is a strict allowlist, agents were effectively read-only. Documents the correct profiles in agent.config.example.toml (they only lived in host config; the live runtime profiles were corrected via its config API). 2. workspace.path never got set. `agents.<alias>.workspace.path` is an Option<PathBuf> the ZeroClaw Configurable macro skips from prop enumeration, so provision_claw's set_prop always 404'd and the whole call errored into a swallowed eprintln. Removes the dead set_prop and pins the workspace out-of-band: MissionRuntimeProvisioner:: pin_agent_workspaces patches the shared config file on the per-mission container (format-preserving via toml_edit, atomic temp+mv); the daemon applies it on the same reload that surfaces the freshly-provisioned claws. Covered by unit tests for the TOML stamp. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
6d5e7c87d7
commit
34409bca0c
@@ -159,6 +159,7 @@ pub async fn on_launch(
|
||||
|
||||
let provisioner = RuntimeProvisioner::from_env();
|
||||
let mut first_team_id: Option<Uuid> = None;
|
||||
let mut provisioned_claws: Vec<cm_domain::AgentId> = Vec::new();
|
||||
for (purpose, template_id) in &picks {
|
||||
let template = cm_db::repo::team_templates::get(pool, *template_id)
|
||||
.await
|
||||
@@ -176,6 +177,7 @@ pub async fn on_launch(
|
||||
&template,
|
||||
&team_name,
|
||||
"claude-sonnet-5",
|
||||
&mut provisioned_claws,
|
||||
)
|
||||
.await?;
|
||||
// Record (mission, team, purpose) in mission_teams so the Team
|
||||
@@ -201,6 +203,29 @@ pub async fn on_launch(
|
||||
.await
|
||||
.map_err(|e| format!("bind team on mission: {e}"))?;
|
||||
|
||||
// Pin every provisioned claw's workspace to /mission/repo so
|
||||
// file_edit / content_search / glob_search / git_operations operate
|
||||
// on the mission's checked-out repo instead of the empty per-agent
|
||||
// sandbox. This CANNOT go through the config prop API (workspace.path
|
||||
// is a PathBuf the prop-schema won't expose — see provision_claw), so
|
||||
// we patch the shared config file directly on the per-mission runtime
|
||||
// container. The daemon picks it up on the same reload that surfaces
|
||||
// the freshly-provisioned claws for the run. Non-fatal: without the
|
||||
// pin, agents still write (to the sandbox) but the committer can't
|
||||
// find the changes in /mission/repo.
|
||||
if !provisioned_claws.is_empty() {
|
||||
if let Some(mp) = crate::mission_runtime::MissionRuntimeProvisioner::from_env() {
|
||||
if let Err(e) = mp
|
||||
.pin_agent_workspaces(mission_id, &provisioned_claws, "/mission/repo")
|
||||
.await
|
||||
{
|
||||
eprintln!(
|
||||
"mission_orchestrator: pin workspaces for mission {mission_id} failed (continuing): {e}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Herdr second-runtime: if runtime_kind='local_herdr', spawn a
|
||||
// pane on target_node running the first available local CLI.
|
||||
// Non-fatal on failure — the operator sees the error in server
|
||||
@@ -253,6 +278,7 @@ async fn mint_team_from_template(
|
||||
template: &TeamTemplateDetail,
|
||||
team_name: &str,
|
||||
default_model: &str,
|
||||
provisioned_claws: &mut Vec<cm_domain::AgentId>,
|
||||
) -> Result<Uuid, String> {
|
||||
// Build the topology graph from role slots so the team's `graph`
|
||||
// NOT NULL column is satisfied + downstream topology executors
|
||||
@@ -332,24 +358,20 @@ async fn mint_team_from_template(
|
||||
// scout/researcher, coding_readwrite for coder/tester/committer,
|
||||
// etc.). Passing "toolfree" — the old default — left every
|
||||
// agent with zero tools regardless of what its prompt asked for.
|
||||
//
|
||||
// Workspace pinning to /mission/repo is NOT done here (the
|
||||
// config prop-schema can't set workspace.path — see
|
||||
// provision_claw's doc); the caller pins the collected claws
|
||||
// out-of-band via MissionRuntimeProvisioner::pin_agent_workspaces.
|
||||
if let Some(p) = provisioner {
|
||||
// /mission/repo is the bind-mount path inside the per-mission
|
||||
// runtime container (see mission_runtime::ensure_container).
|
||||
// Pinning workspace.path there lets file_edit / glob_search /
|
||||
// content_search actually operate on the mission's checked-out
|
||||
// repo instead of the empty per-agent sandbox.
|
||||
if let Err(e) = p
|
||||
.provision_claw(
|
||||
claw_id,
|
||||
default_model,
|
||||
&template.template.risk_profile,
|
||||
Some("/mission/repo"),
|
||||
)
|
||||
match p
|
||||
.provision_claw(claw_id, default_model, &template.template.risk_profile)
|
||||
.await
|
||||
{
|
||||
eprintln!(
|
||||
Ok(_) => provisioned_claws.push(agent.id),
|
||||
Err(e) => eprintln!(
|
||||
"mission_orchestrator: provision claw {claw_id} failed (continuing): {e}"
|
||||
);
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user