feat: add agent roles to collab tools (#9275)

Add `agent_type` parameter to the collab tool `spawn_agent` that
contains a preset to apply on the config when spawning this agent
This commit is contained in:
jif-oai
2026-01-15 13:33:52 +00:00
committed by GitHub
parent bad4c12b9d
commit 05b960671d
6 changed files with 142 additions and 14 deletions

View File

@@ -1,6 +1,8 @@
pub(crate) mod control;
pub(crate) mod role;
pub(crate) mod status;
pub(crate) use codex_protocol::protocol::AgentStatus;
pub(crate) use control::AgentControl;
pub(crate) use role::AgentRole;
pub(crate) use status::agent_status_from_event;

View File

@@ -0,0 +1,85 @@
use crate::config::Config;
use crate::protocol::SandboxPolicy;
use serde::Deserialize;
use serde::Serialize;
/// Base instructions for the orchestrator role.
const ORCHESTRATOR_PROMPT: &str = include_str!("../../templates/agents/orchestrator.md");
/// Base instructions for the worker role.
const WORKER_PROMPT: &str = include_str!("../../gpt-5.2-codex_prompt.md");
/// Default worker model override used by the worker role.
const WORKER_MODEL: &str = "gpt-5.2-codex";
/// Enumerated list of all supported agent roles.
const ALL_ROLES: [AgentRole; 3] = [
AgentRole::Default,
AgentRole::Orchestrator,
AgentRole::Worker,
];
/// Hard-coded agent role selection used when spawning sub-agents.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AgentRole {
/// Inherit the parent agent's configuration unchanged.
Default,
/// Coordination-only agent that delegates to workers.
Orchestrator,
/// Task-executing agent with a fixed model override.
Worker,
}
/// Immutable profile data that drives per-agent configuration overrides.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct AgentProfile {
/// Optional base instructions override.
pub base_instructions: Option<&'static str>,
/// Optional model override.
pub model: Option<&'static str>,
/// Whether to force a read-only sandbox policy.
pub read_only: bool,
}
impl AgentRole {
/// Returns the string values used by JSON schema enums.
pub fn enum_values() -> Vec<String> {
ALL_ROLES
.iter()
.filter_map(|role| serde_json::to_string(role).ok())
.collect()
}
/// Returns the hard-coded profile for this role.
pub fn profile(self) -> AgentProfile {
match self {
AgentRole::Default => AgentProfile::default(),
AgentRole::Orchestrator => AgentProfile {
base_instructions: Some(ORCHESTRATOR_PROMPT),
..Default::default()
},
AgentRole::Worker => AgentProfile {
base_instructions: Some(WORKER_PROMPT),
model: Some(WORKER_MODEL),
..Default::default()
},
}
}
/// Applies this role's profile onto the provided config.
pub fn apply_to_config(self, config: &mut Config) -> Result<(), String> {
let profile = self.profile();
if let Some(base_instructions) = profile.base_instructions {
config.base_instructions = Some(base_instructions.to_string());
}
if let Some(model) = profile.model {
config.model = Some(model.to_string());
}
if profile.read_only {
config
.sandbox_policy
.set(SandboxPolicy::new_read_only_policy())
.map_err(|err| format!("sandbox_policy is invalid: {err}"))?;
}
Ok(())
}
}

View File

@@ -541,24 +541,12 @@ impl Session {
web_search_mode: per_turn_config.web_search_mode,
});
let base_instructions = if per_turn_config.features.enabled(Feature::Collab) {
const COLLAB_INSTRUCTIONS: &str =
include_str!("../templates/collab/experimental_prompt.md");
let base = session_configuration
.base_instructions
.as_deref()
.unwrap_or(model_info.base_instructions.as_str());
Some(format!("{base}\n\n{COLLAB_INSTRUCTIONS}"))
} else {
session_configuration.base_instructions.clone()
};
TurnContext {
sub_id,
client,
cwd: session_configuration.cwd.clone(),
developer_instructions: session_configuration.developer_instructions.clone(),
base_instructions,
base_instructions: session_configuration.base_instructions.clone(),
compact_prompt: session_configuration.compact_prompt.clone(),
user_instructions: session_configuration.user_instructions.clone(),
approval_policy: session_configuration.approval_policy.value(),

View File

@@ -76,11 +76,13 @@ impl ToolHandler for CollabHandler {
mod spawn {
use super::*;
use crate::agent::AgentRole;
use std::sync::Arc;
#[derive(Debug, Deserialize)]
struct SpawnAgentArgs {
message: String,
agent_type: Option<AgentRole>,
}
#[derive(Debug, Serialize)]
@@ -95,6 +97,7 @@ mod spawn {
arguments: String,
) -> Result<ToolOutput, FunctionCallError> {
let args: SpawnAgentArgs = parse_arguments(&arguments)?;
let agent_role = args.agent_type.unwrap_or(AgentRole::Default);
let prompt = args.message;
if prompt.trim().is_empty() {
return Err(FunctionCallError::RespondToModel(
@@ -112,7 +115,10 @@ mod spawn {
.into(),
)
.await;
let config = build_agent_spawn_config(turn.as_ref())?;
let mut config = build_agent_spawn_config(turn.as_ref())?;
agent_role
.apply_to_config(&mut config)
.map_err(FunctionCallError::RespondToModel)?;
let result = session
.services
.agent_control

View File

@@ -1,3 +1,4 @@
use crate::agent::AgentRole;
use crate::client_common::tools::ResponsesApiTool;
use crate::client_common::tools::ToolSpec;
use crate::features::Feature;
@@ -441,6 +442,15 @@ fn create_spawn_agent_tool() -> ToolSpec {
description: Some("Initial message to send to the new agent.".to_string()),
},
);
properties.insert(
"agent_type".to_string(),
JsonSchema::String {
description: Some(format!(
"Optional agent type to spawn ({}).",
AgentRole::enum_values().join(", ")
)),
},
);
ToolSpec::Function(ResponsesApiTool {
name: "spawn_agent".to_string(),

View File

@@ -0,0 +1,37 @@
You are Codex Orchestrator, based on GPT-5. You are running as an orchestration agent in the Codex CLI on a user's computer.
## Role
- You do not solve the task yourself. Your job is to delegate, coordinate, and verify.
- Monitor progress, resolve conflicts, and integrate results into a single, coherent outcome.
- You should always spawn a worker to perform actual work but before this, you can discuss the problem, ask follow-up questions, discussion design etc. Workers are only here to perform the actual job.
## Multi-agent workflow
1. Understand the request and identify the minimum set of workers needed.
2. Spawn worker(s) with precise goals, constraints, and expected deliverables.
3. Monitor workers with `wait`, route questions via `send_input`, and keep scope boundaries clear.
4. When all workers report done, spawn a verifier agent to review the work.
5. If the verifier reports issues, assign fixes to the relevant worker(s) and repeat steps 35 until the verifier passes.
6. Close all agents when you don't need them anymore (i.e. when the task if fully finished).
## Collaboration rules
- Tell every worker they are not alone in the environment and must not revert or overwrite others' work.
- Default: workers must not spawn sub-agents unless you explicitly allow it.
- For large logs or long-running tasks (tests, builds), delegate to a worker and instruct them not to spawn additional agents.
- Use sensible `wait` timeouts and adjust for task size; do not exceed maximums.
## Collab tools
- `spawn_agent`: create a worker or verifier with an initial prompt (set `agent_type`).
- `send_input`: send follow-ups, clarifications, or fix requests (`interrupt` can stop the current task first).
- `wait`: poll an agent for completion or status.
- `close_agent`: close the agent when done.
## Presenting your work and final message
- Keep responses concise, factual, and in plain text.
- Summarize: what was delegated, key outcomes, tests/verification status, and any remaining risks.
- If verification failed, state the issues clearly and what you asked workers to change.
- Do not dump large files; reference paths with backticks.