Compare commits

..

1 Commits

Author SHA1 Message Date
Ahmed Ibrahim
367a8a2210 Clarify spawn agent authorization (#14432)
- Clarify that spawn_agent requires explicit user permission for
delegation or parallel agent work.
- Add a regression test covering the new description text.
2026-03-11 23:03:07 -07:00
5 changed files with 23 additions and 121 deletions

View File

@@ -853,7 +853,10 @@ fn create_spawn_agent_tool(config: &ToolsConfig) -> ToolSpec {
name: "spawn_agent".to_string(),
description: format!(
r#"
Only use `spawn_agent` if and only if the user explicitly asked for sub-agents or parallel agent work. Spawn a sub-agent for a well-scoped task. Returns the agent id (and user-facing nickname when available) to use to communicate with this agent. This spawn_agent tool provides you access to smaller but more efficient sub-agents. A mini model can solve many tasks faster than the main model. You should follow the rules and guidelines below to use this tool.
Only use `spawn_agent` if and only if the user explicitly asks for sub-agents, delegation, or parallel agent work.
Requests for depth, thoroughness, research, investigation, or detailed codebase analysis do not count as permission to spawn.
Agent-role guidance below only helps choose which agent to use after spawning is already authorized; it never authorizes spawning by itself.
Spawn a sub-agent for a well-scoped task. Returns the agent id (and user-facing nickname when available) to use to communicate with this agent. This spawn_agent tool provides you access to smaller but more efficient sub-agents. A mini model can solve many tasks faster than the main model. You should follow the rules and guidelines below to use this tool.
{available_models_description}
### When to delegate vs. do the subtask yourself

View File

@@ -180,6 +180,24 @@ async fn spawn_agent_description_lists_visible_models_and_reasoning_efforts() ->
!description.contains("Hidden Model"),
"hidden picker model should be omitted from spawn_agent description: {description:?}"
);
assert!(
description.contains(
"Only use `spawn_agent` if and only if the user explicitly asks for sub-agents, delegation, or parallel agent work."
),
"expected explicit authorization rule in spawn_agent description: {description:?}"
);
assert!(
description.contains(
"Requests for depth, thoroughness, research, investigation, or detailed codebase analysis do not count as permission to spawn."
),
"expected non-authorization clarification in spawn_agent description: {description:?}"
);
assert!(
description.contains(
"Agent-role guidance below only helps choose which agent to use after spawning is already authorized; it never authorizes spawning by itself."
),
"expected agent-role clarification in spawn_agent description: {description:?}"
);
Ok(())
}

View File

@@ -1,115 +0,0 @@
use codex_protocol::protocol::RolloutLine;
use schemars::r#gen::SchemaSettings;
use serde_json::Map;
use serde_json::Value;
use std::any::TypeId;
use std::collections::BTreeMap;
use std::collections::HashSet;
use std::io;
use std::path::PathBuf;
use ts_rs::TS;
use ts_rs::TypeVisitor;
const GENERATED_TS_HEADER: &str = "// GENERATED CODE! DO NOT MODIFY BY HAND!\n\n";
const TS_RS_NOTE: &str = "// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n";
const JSON_SCHEMA_FILENAME: &str = "rollout-line.schema.json";
const TYPESCRIPT_FILENAME: &str = "rollout-line.schema.ts";
fn main() -> io::Result<()> {
let out_dir = std::env::args_os()
.nth(1)
.map(PathBuf::from)
.unwrap_or_else(|| {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let codex_rs_dir = manifest_dir.parent().unwrap_or(&manifest_dir);
codex_rs_dir.join("out/rollout-line-schema")
});
std::fs::create_dir_all(&out_dir)?;
std::fs::write(
out_dir.join(JSON_SCHEMA_FILENAME),
rollout_line_schema_json()?,
)?;
std::fs::write(
out_dir.join(TYPESCRIPT_FILENAME),
generate_typescript_bundle::<RolloutLine>(),
)?;
for filename in [JSON_SCHEMA_FILENAME, TYPESCRIPT_FILENAME] {
println!("Wrote {}", out_dir.join(filename).display());
}
Ok(())
}
fn rollout_line_schema_json() -> io::Result<Vec<u8>> {
let schema = SchemaSettings::draft07()
.into_generator()
.into_root_schema_for::<RolloutLine>();
let value = serde_json::to_value(schema).map_err(io::Error::other)?;
let value = canonicalize_json(&value);
serde_json::to_vec_pretty(&value).map_err(io::Error::other)
}
fn canonicalize_json(value: &Value) -> Value {
match value {
Value::Array(items) => Value::Array(items.iter().map(canonicalize_json).collect()),
Value::Object(map) => {
let mut entries: Vec<_> = map.iter().collect();
entries.sort_by(|(left, _), (right, _)| left.cmp(right));
let mut sorted = Map::with_capacity(map.len());
for (key, child) in entries {
sorted.insert(key.clone(), canonicalize_json(child));
}
Value::Object(sorted)
}
_ => value.clone(),
}
}
fn generate_typescript_bundle<T: TS + 'static + ?Sized>() -> String {
let mut declarations = BTreeMap::new();
let mut seen = HashSet::new();
collect_typescript_declarations::<T>(&mut declarations, &mut seen);
let body = declarations
.into_values()
.collect::<Vec<_>>()
.join("\n\n")
.replace("\r\n", "\n")
.replace('\r', "\n");
format!("{GENERATED_TS_HEADER}{TS_RS_NOTE}\n{body}\n")
}
fn collect_typescript_declarations<T: TS + 'static + ?Sized>(
declarations: &mut BTreeMap<PathBuf, String>,
seen: &mut HashSet<TypeId>,
) {
let Some(output_path) = T::output_path() else {
return;
};
if !seen.insert(TypeId::of::<T>()) {
return;
}
let mut declaration = String::new();
if let Some(docs) = T::docs() {
declaration.push_str(&docs.replace("\r\n", "\n").replace('\r', "\n"));
}
declaration.push_str("export ");
declaration.push_str(&T::decl().replace("\r\n", "\n").replace('\r', "\n"));
declarations.insert(output_path.components().collect(), declaration);
let mut visitor = TypeScriptDeclarationCollector { declarations, seen };
T::visit_dependencies(&mut visitor);
}
struct TypeScriptDeclarationCollector<'a> {
declarations: &'a mut BTreeMap<PathBuf, String>,
seen: &'a mut HashSet<TypeId>,
}
impl TypeVisitor for TypeScriptDeclarationCollector<'_> {
fn visit<T: TS + 'static + ?Sized>(&mut self) {
collect_typescript_declarations::<T>(self.declarations, self.seen);
}
}

View File

@@ -2423,7 +2423,7 @@ pub enum TruncationPolicy {
Tokens(usize),
}
#[derive(Serialize, Deserialize, Clone, JsonSchema, TS)]
#[derive(Serialize, Deserialize, Clone, JsonSchema)]
pub struct RolloutLine {
pub timestamp: String,
#[serde(flatten)]

View File

@@ -78,10 +78,6 @@ mcp-server-run *args:
write-config-schema:
cargo run -p codex-core --bin codex-write-config-schema
# Generate RolloutLine schema artifacts.
write-rollout-line-schema *args:
cargo run -p codex-protocol --bin codex-write-rollout-line-schema -- "$@"
# Regenerate vendored app-server protocol schema artifacts.
write-app-server-schema *args:
cargo run -p codex-app-server-protocol --bin write_schema_fixtures -- "$@"