fix(claws): point per-mission workspace at /mission/repo + tool-inventory preamble
Two stacked issues after risk_profile was fixed: 1. Claws had file_edit + 46 other tools available, but the templates trained the agents to expect file_read/file_write (older ZeroClaw tool names). Result: agent output kept saying "I only have file_read" and dumped implementations into the context window as text. 2. Even with file_edit, the sandbox pointed at /zeroclaw-data/.zeroclaw/agents/<alias>/workspace/ — NOT /mission/repo where the checked-out mission repo actually lives. unrestricted_filesystem=false blocked agents from reaching it. Fixes: - provision_claw now takes workspace_path. mission_orchestrator passes /mission/repo — pins the per-claw workspace via agents.<alias>.workspace.path to the bind-mount path so file_edit / content_search / glob_search operate on the mission's git checkout. - phase_task_text prepends an explicit tool inventory (file_edit, content_search, glob_search, git_operations, git_forge, ...) plus a WORKSPACE line pinned at /mission/repo. Each phase directive is rewritten to reference file_edit / git_operations explicitly and to call out "do NOT paste code in your reply expecting the platform to save it."
This commit is contained in:
@@ -333,8 +333,18 @@ async fn mint_team_from_template(
|
|||||||
// etc.). Passing "toolfree" — the old default — left every
|
// etc.). Passing "toolfree" — the old default — left every
|
||||||
// agent with zero tools regardless of what its prompt asked for.
|
// agent with zero tools regardless of what its prompt asked for.
|
||||||
if let Some(p) = provisioner {
|
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
|
if let Err(e) = p
|
||||||
.provision_claw(claw_id, default_model, &template.template.risk_profile)
|
.provision_claw(
|
||||||
|
claw_id,
|
||||||
|
default_model,
|
||||||
|
&template.template.risk_profile,
|
||||||
|
Some("/mission/repo"),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
eprintln!(
|
eprintln!(
|
||||||
|
|||||||
@@ -265,37 +265,65 @@ async fn launch_phase(
|
|||||||
|
|
||||||
fn phase_task_text(kind: &str, title: &str, description: Option<&str>) -> String {
|
fn phase_task_text(kind: &str, title: &str, description: Option<&str>) -> String {
|
||||||
let base = description.unwrap_or("").trim();
|
let base = description.unwrap_or("").trim();
|
||||||
|
// The prior template-derived system prompts trained agents to look
|
||||||
|
// for `file_read`/`file_write` — tools that no longer exist under
|
||||||
|
// ZeroClaw v0.8+. The current toolset uses `file_edit` (create /
|
||||||
|
// overwrite / patch) plus `content_search`/`glob_search`. Injecting
|
||||||
|
// the real tool inventory + concrete workspace path stops the agent
|
||||||
|
// from hallucinating "I only have file_read" and dumping the entire
|
||||||
|
// implementation into the context window instead of onto disk.
|
||||||
|
let tool_preamble = "\
|
||||||
|
TOOLS AVAILABLE (use these exact names — do NOT assume older tool names like file_read / file_write / bash exist):\n\
|
||||||
|
- file_edit — create, overwrite, or patch files in your workspace\n\
|
||||||
|
- content_search — grep across your workspace (regex on file contents)\n\
|
||||||
|
- glob_search — find files by path glob\n\
|
||||||
|
- git_operations — git status / add / commit / diff / log\n\
|
||||||
|
- git_forge — Gitea PR / branch / issue operations\n\
|
||||||
|
- web_search_tool / web_fetch — external references (research-tier profiles only)\n\
|
||||||
|
- spawn_subagent — hand off a subtask to another claw\n\
|
||||||
|
- delegate — call a peer role by name\n\
|
||||||
|
- memory_store / memory_recall — durable per-agent notes\n\
|
||||||
|
\n\
|
||||||
|
WORKSPACE: Your working directory is /mission/repo. That path is the\n\
|
||||||
|
mission's git checkout. All file_edit / content_search / glob_search\n\
|
||||||
|
operations resolve there. To read a file: file_edit with mode='read'\n\
|
||||||
|
or content_search first, then file_edit to patch. Write your outputs\n\
|
||||||
|
as REAL files with file_edit — do NOT paste code blocks in your reply\n\
|
||||||
|
expecting the platform to save them; nothing else writes files for you.\n";
|
||||||
let directive = match kind {
|
let directive = match kind {
|
||||||
"research" => {
|
"research" => {
|
||||||
"Your team is running the RESEARCH phase of this mission. \
|
"Your team is running the RESEARCH phase of this mission. \
|
||||||
Investigate the topic, gather sources, and produce a \
|
Investigate the topic, gather sources, and produce a \
|
||||||
sectioned Markdown brief the coding phase can implement \
|
sectioned Markdown brief the coding phase can implement \
|
||||||
directly. Save findings to the Obsidian vault or the \
|
directly. Save findings under /mission/repo/research/ \
|
||||||
mission's artifact directory. Emit INT-XX task markers \
|
using file_edit — one Markdown file per topic. Emit INT-XX \
|
||||||
for concrete follow-ups."
|
task markers in the last file for concrete follow-ups."
|
||||||
}
|
}
|
||||||
"coding" => {
|
"coding" => {
|
||||||
"Your team is running the CODING phase of this mission. \
|
"Your team is running the CODING phase of this mission. \
|
||||||
Implement the mission's acceptance criteria against the \
|
Implement the mission's acceptance criteria against the \
|
||||||
checked-out repo. Follow the workspace-repo-commit-protocol: \
|
/mission/repo checkout using file_edit for every source \
|
||||||
small focused commits with test coverage. Emit COMPLETED: <INT-id> \
|
file, then git_operations to commit small focused changes \
|
||||||
markers as you close research-produced tasks."
|
with test coverage. Emit COMPLETED: <INT-id> markers as you \
|
||||||
|
close research-produced tasks. Do NOT respond with source \
|
||||||
|
code in text — write it as files."
|
||||||
}
|
}
|
||||||
"benchmark" => {
|
"benchmark" => {
|
||||||
"Your team is running the BENCHMARK phase of this mission. \
|
"Your team is running the BENCHMARK phase of this mission. \
|
||||||
Author or extend benchmarks that measure the target change. \
|
Author or extend benchmarks under /mission/repo/benches or \
|
||||||
Baseline the pre-change performance, apply the change (or \
|
the crate's bench harness using file_edit. Baseline the \
|
||||||
use the mission's committed diff), then measure after."
|
pre-change performance, apply the change (or use the \
|
||||||
|
mission's committed diff), then measure after."
|
||||||
}
|
}
|
||||||
"security_scan" => {
|
"security_scan" => {
|
||||||
"Your team is running the SECURITY SCAN phase of this mission. \
|
"Your team is running the SECURITY SCAN phase of this mission. \
|
||||||
Run cargo-audit, gitleaks, trivy, and semgrep against the \
|
Run cargo-audit, gitleaks, trivy, and semgrep against \
|
||||||
checked-out repo. Triage findings, file INT-XX task markers \
|
/mission/repo. Triage findings, file INT-XX task markers \
|
||||||
for remediation, propose patches for the coding phase."
|
for remediation, propose patches for the coding phase."
|
||||||
}
|
}
|
||||||
_ => "Execute this mission phase according to the mission brief.",
|
_ => "Execute this mission phase according to the mission brief.",
|
||||||
};
|
};
|
||||||
format!("MISSION: {title}\n\n{directive}\n\nBRIEF:\n{base}")
|
format!("MISSION: {title}\n\n{tool_preamble}\n{directive}\n\nBRIEF:\n{base}")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Close phases whose topology_runs are all terminal.
|
/// Close phases whose topology_runs are all terminal.
|
||||||
|
|||||||
@@ -130,8 +130,11 @@ pub(crate) async fn build_team_with_lifecycle(
|
|||||||
.await?;
|
.await?;
|
||||||
let claw_id = agent.id.as_uuid();
|
let claw_id = agent.id.as_uuid();
|
||||||
let risk = RuntimeProvisioner::default_risk_profile_for_role(&m.role);
|
let risk = RuntimeProvisioner::default_risk_profile_for_role(&m.role);
|
||||||
|
// No workspace override on the ad-hoc team-wizard path — those
|
||||||
|
// teams aren't mission-bound so they use the default per-agent
|
||||||
|
// workspace under <install>/agents/<alias>/workspace/.
|
||||||
provisioner
|
provisioner
|
||||||
.provision_claw(claw_id, &m.model, risk)
|
.provision_claw(claw_id, &m.model, risk, None)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
eprintln!("teams: provision claw {claw_id} failed: {e}");
|
eprintln!("teams: provision claw {claw_id} failed: {e}");
|
||||||
|
|||||||
@@ -136,12 +136,22 @@ impl RuntimeProvisioner {
|
|||||||
/// `risk_profile` (from the team template — controls which tools this
|
/// `risk_profile` (from the team template — controls which tools this
|
||||||
/// agent gets: `toolfree` = nothing, `research_readonly` = file_read
|
/// agent gets: `toolfree` = nothing, `research_readonly` = file_read
|
||||||
/// only, `coding_readwrite` = file_read + file_write + shell, etc.),
|
/// only, `coding_readwrite` = file_read + file_write + shell, etc.),
|
||||||
/// and the `clawmates_door` MCP bundle. Idempotent on the create step.
|
/// and the `clawmates_door` MCP bundle.
|
||||||
|
///
|
||||||
|
/// `workspace_path`, when Some, pins the agent's per-agent workspace
|
||||||
|
/// dir via `[agents.<alias>.workspace.path]`. In per-mission runtime
|
||||||
|
/// containers this is `/mission/repo` so `file_edit` / `content_search`
|
||||||
|
/// / `glob_search` operate on the mission's checked-out repo instead
|
||||||
|
/// of the default `<install>/agents/<alias>/workspace/` sandbox
|
||||||
|
/// (which the agent can't populate with the mission's source files).
|
||||||
|
///
|
||||||
|
/// Idempotent on the create step.
|
||||||
pub async fn provision_claw(
|
pub async fn provision_claw(
|
||||||
&self,
|
&self,
|
||||||
claw_id: Uuid,
|
claw_id: Uuid,
|
||||||
model: &str,
|
model: &str,
|
||||||
risk_profile: &str,
|
risk_profile: &str,
|
||||||
|
workspace_path: Option<&str>,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let alias = claw_alias(claw_id);
|
let alias = claw_alias(claw_id);
|
||||||
let model_alias = provider_alias_for(model);
|
let model_alias = provider_alias_for(model);
|
||||||
@@ -179,6 +189,13 @@ impl RuntimeProvisioner {
|
|||||||
serde_json::json!(["clawmates_door"]),
|
serde_json::json!(["clawmates_door"]),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
if let Some(path) = workspace_path {
|
||||||
|
self.set_prop(
|
||||||
|
&format!("agents.{alias}.workspace.path"),
|
||||||
|
serde_json::json!(path),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(alias)
|
Ok(alias)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user