fix(runtime_provision): use team-template risk_profile, not hardcoded toolfree
The provisioner was hardcoding risk_profile=toolfree for every claw, which the ZeroClaw config explicitly configures to EXCLUDE every usable tool (shell, file_read, file_write, http_request, browser). Result: coder/tester/committer claws had zero tools and produced text in the context window with no ability to actually write files or run tests — exactly what the last mission summary showed. Fixes: - provision_claw now takes risk_profile: &str, passed through from the team template (development teams already had coding_readwrite, which now actually gets applied). - Research team templates updated from toolfree → research_readonly (file_read) and papers_research → research_web_readonly (file_read + web_search + web_fetch). Applied to both the on-disk TOML files and the live DB rows. - Added RuntimeProvisioner::default_risk_profile_for_role for auto-provision code paths that lack a template context — picks coding_readwrite for coder-like roles, research_readonly otherwise. - Split rebind_model out of provision_claw so the model-change UI path doesnt inadvertently clobber the existing risk_profile. Templates DB fixup for missions launched pre-deploy is already applied via manual UPDATE.
This commit is contained in:
@@ -327,8 +327,16 @@ async fn mint_team_from_template(
|
|||||||
.map_err(|e| format!("set_model_binding {claw_id}: {e}"))?;
|
.map_err(|e| format!("set_model_binding {claw_id}: {e}"))?;
|
||||||
|
|
||||||
// Runtime provisioning is opt-in — no-op if unconfigured.
|
// Runtime provisioning is opt-in — no-op if unconfigured.
|
||||||
|
// Pass the team template's risk_profile so the claw actually
|
||||||
|
// gets the tools its role expects (research_readonly for
|
||||||
|
// 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.
|
||||||
if let Some(p) = provisioner {
|
if let Some(p) = provisioner {
|
||||||
if let Err(e) = p.provision_claw(claw_id, default_model).await {
|
if let Err(e) = p
|
||||||
|
.provision_claw(claw_id, default_model, &template.template.risk_profile)
|
||||||
|
.await
|
||||||
|
{
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"mission_orchestrator: provision claw {claw_id} failed (continuing): {e}"
|
"mission_orchestrator: provision claw {claw_id} failed (continuing): {e}"
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -994,7 +994,7 @@ pub async fn set_model(
|
|||||||
// model on their next turn. provision_claw overwrites
|
// model on their next turn. provision_claw overwrites
|
||||||
// agents.<alias>.model_provider on the shared ZeroClaw config.
|
// agents.<alias>.model_provider on the shared ZeroClaw config.
|
||||||
if let Some(provisioner) = crate::runtime_provision::RuntimeProvisioner::from_env() {
|
if let Some(provisioner) = crate::runtime_provision::RuntimeProvisioner::from_env() {
|
||||||
if let Err(e) = provisioner.provision_claw(id.as_uuid(), model).await {
|
if let Err(e) = provisioner.rebind_model(id.as_uuid(), model).await {
|
||||||
eprintln!("set_model({id}): runtime rebind failed: {e}");
|
eprintln!("set_model({id}): runtime rebind failed: {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,8 +129,9 @@ 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);
|
||||||
provisioner
|
provisioner
|
||||||
.provision_claw(claw_id, &m.model)
|
.provision_claw(claw_id, &m.model, risk)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
eprintln!("teams: provision claw {claw_id} failed: {e}");
|
eprintln!("teams: provision claw {claw_id} failed: {e}");
|
||||||
|
|||||||
@@ -93,10 +93,56 @@ impl RuntimeProvisioner {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create `claw_<id>` as a live runtime agent bound to `model_alias`, the
|
/// Rebind an existing claw's model without touching its risk_profile
|
||||||
/// `toolfree` risk profile, and the `clawmates_door` MCP bundle. Idempotent
|
/// or mcp_bundles. Used by the "change model" UI on the Agents page
|
||||||
/// on the create step.
|
/// so we don't accidentally demote a coding_readwrite claw back to
|
||||||
pub async fn provision_claw(&self, claw_id: Uuid, model: &str) -> Result<String, String> {
|
/// the default when the user just wanted a different model.
|
||||||
|
pub async fn rebind_model(&self, claw_id: Uuid, model: &str) -> Result<(), String> {
|
||||||
|
let alias = claw_alias(claw_id);
|
||||||
|
let model_alias = provider_alias_for(model);
|
||||||
|
self.set_prop(
|
||||||
|
&format!("agents.{alias}.model_provider"),
|
||||||
|
serde_json::json!(model_alias),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sensible fallback risk_profile for a given role slot when no
|
||||||
|
/// template-level risk_profile is available. Coder/tester/committer/
|
||||||
|
/// engineer roles need write access; everything else defaults to
|
||||||
|
/// read-only so we never accidentally over-grant tools.
|
||||||
|
pub fn default_risk_profile_for_role(role: &str) -> &'static str {
|
||||||
|
let r = role.to_ascii_lowercase();
|
||||||
|
let write_roles = [
|
||||||
|
"coder",
|
||||||
|
"tester",
|
||||||
|
"committer",
|
||||||
|
"db_engineer",
|
||||||
|
"api_designer",
|
||||||
|
"backend",
|
||||||
|
"frontend",
|
||||||
|
"engineer",
|
||||||
|
"implementer",
|
||||||
|
"patcher",
|
||||||
|
];
|
||||||
|
if write_roles.iter().any(|w| r.contains(w)) {
|
||||||
|
"coding_readwrite"
|
||||||
|
} else {
|
||||||
|
"research_readonly"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create `claw_<id>` as a live runtime agent bound to `model_alias`,
|
||||||
|
/// `risk_profile` (from the team template — controls which tools this
|
||||||
|
/// agent gets: `toolfree` = nothing, `research_readonly` = file_read
|
||||||
|
/// only, `coding_readwrite` = file_read + file_write + shell, etc.),
|
||||||
|
/// and the `clawmates_door` MCP bundle. Idempotent on the create step.
|
||||||
|
pub async fn provision_claw(
|
||||||
|
&self,
|
||||||
|
claw_id: Uuid,
|
||||||
|
model: &str,
|
||||||
|
risk_profile: &str,
|
||||||
|
) -> 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);
|
||||||
|
|
||||||
@@ -125,7 +171,7 @@ impl RuntimeProvisioner {
|
|||||||
.await?;
|
.await?;
|
||||||
self.set_prop(
|
self.set_prop(
|
||||||
&format!("agents.{alias}.risk_profile"),
|
&format!("agents.{alias}.risk_profile"),
|
||||||
serde_json::json!("toolfree"),
|
serde_json::json!(risk_profile),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
self.set_prop(
|
self.set_prop(
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ description = "Comprehensive deep-dive into a codebase — forensics, archi
|
|||||||
stack = ["research", "code-forensics", "obsidian", "documentation"]
|
stack = ["research", "code-forensics", "obsidian", "documentation"]
|
||||||
category = "research"
|
category = "research"
|
||||||
default_topology = "pipeline"
|
default_topology = "pipeline"
|
||||||
risk_profile = "toolfree"
|
risk_profile = "research_readonly"
|
||||||
mcp_bundles = ["clawmates_door", "clawmates_skills", "gitea_forge"]
|
mcp_bundles = ["clawmates_door", "clawmates_skills", "gitea_forge"]
|
||||||
version = 1
|
version = 1
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ description = "Standing self-audit: read every project agent's .brain and s
|
|||||||
stack = ["research", "self-improvement", "brain-inspection", "level-up"]
|
stack = ["research", "self-improvement", "brain-inspection", "level-up"]
|
||||||
category = "research"
|
category = "research"
|
||||||
default_topology = "pipeline"
|
default_topology = "pipeline"
|
||||||
risk_profile = "toolfree"
|
risk_profile = "research_readonly"
|
||||||
mcp_bundles = ["clawmates_door", "clawmates_skills"]
|
mcp_bundles = ["clawmates_door", "clawmates_skills"]
|
||||||
version = 1
|
version = 1
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ description = "Standing scan across all monitored data sources + media type
|
|||||||
stack = ["research", "monitoring", "digest", "obsidian", "rss", "podcasts"]
|
stack = ["research", "monitoring", "digest", "obsidian", "rss", "podcasts"]
|
||||||
category = "research"
|
category = "research"
|
||||||
default_topology = "pipeline"
|
default_topology = "pipeline"
|
||||||
risk_profile = "toolfree"
|
risk_profile = "research_readonly"
|
||||||
mcp_bundles = ["clawmates_door", "clawmates_skills", "web_fetch"]
|
mcp_bundles = ["clawmates_door", "clawmates_skills", "web_fetch"]
|
||||||
version = 1
|
version = 1
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ description = "Bidirectional research↔project loop: do any of the papers
|
|||||||
stack = ["research", "novelty", "publication", "obsidian", "citation-analysis"]
|
stack = ["research", "novelty", "publication", "obsidian", "citation-analysis"]
|
||||||
category = "research"
|
category = "research"
|
||||||
default_topology = "pipeline"
|
default_topology = "pipeline"
|
||||||
risk_profile = "toolfree"
|
risk_profile = "research_readonly"
|
||||||
mcp_bundles = ["clawmates_door", "clawmates_skills", "web_fetch"]
|
mcp_bundles = ["clawmates_door", "clawmates_skills", "web_fetch"]
|
||||||
version = 1
|
version = 1
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ description = "Pull, catalog, and summarize every paper we can find on a do
|
|||||||
stack = ["research", "papers", "arxiv", "obsidian", "library"]
|
stack = ["research", "papers", "arxiv", "obsidian", "library"]
|
||||||
category = "research"
|
category = "research"
|
||||||
default_topology = "pipeline"
|
default_topology = "pipeline"
|
||||||
risk_profile = "toolfree"
|
risk_profile = "research_web_readonly"
|
||||||
mcp_bundles = ["clawmates_door", "clawmates_skills", "web_fetch"]
|
mcp_bundles = ["clawmates_door", "clawmates_skills", "web_fetch"]
|
||||||
version = 1
|
version = 1
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user