From a2d205cd9b8cee932870120a063b8690ce6a0eeb Mon Sep 17 00:00:00 2001 From: Friel Date: Tue, 31 Mar 2026 22:02:30 +0000 Subject: [PATCH] feat(core): remove spawn_mode from agent roles --- .../src/protocol/thread_history.rs | 1 - codex-rs/core/config.schema.json | 17 +---------------- codex-rs/core/root_agent_prompt.md | 18 +++++++++--------- .../src/tools/handlers/multi_agents/spawn.rs | 8 -------- .../src/tools/handlers/multi_agents_tests.rs | 2 +- .../tools/handlers/multi_agents_v2/spawn.rs | 6 ------ codex-rs/core/watchdog_agent_prompt.md | 2 +- codex-rs/protocol/src/protocol.rs | 13 ------------- codex-rs/tui/src/chatwidget.rs | 5 ----- codex-rs/tui/src/chatwidget/tests.rs | 1 - codex-rs/tui/src/multi_agents.rs | 3 --- 11 files changed, 12 insertions(+), 64 deletions(-) diff --git a/codex-rs/app-server-protocol/src/protocol/thread_history.rs b/codex-rs/app-server-protocol/src/protocol/thread_history.rs index a26e9ec88b..48fa56d687 100644 --- a/codex-rs/app-server-protocol/src/protocol/thread_history.rs +++ b/codex-rs/app-server-protocol/src/protocol/thread_history.rs @@ -2527,7 +2527,6 @@ mod tests { prompt: "inspect the repo".into(), model: "gpt-5.4-mini".into(), reasoning_effort: codex_protocol::openai_models::ReasoningEffort::Medium, - spawn_mode: codex_protocol::protocol::AgentSpawnMode::Spawn, status: AgentStatus::Running, }), ]; diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 4b4c60ff5b..3622d4a1ce 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -6,13 +6,6 @@ "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", "type": "string" }, - "AgentRoleSpawnMode": { - "enum": [ - "spawn", - "fork" - ], - "type": "string" - }, "AgentRoleToml": { "additionalProperties": false, "properties": { @@ -43,14 +36,6 @@ }, "type": "array" }, - "spawn_mode": { - "allOf": [ - { - "$ref": "#/definitions/AgentRoleSpawnMode" - } - ], - "description": "Optional default spawn mode when `spawn_agent` omits `spawn_mode`." - }, "watchdog_interval_s": { "description": "Optional watchdog interval in seconds for roles that should behave as watchdogs.", "format": "int64", @@ -2625,4 +2610,4 @@ }, "title": "ConfigToml", "type": "object" -} \ No newline at end of file +} diff --git a/codex-rs/core/root_agent_prompt.md b/codex-rs/core/root_agent_prompt.md index aa5afdacb8..ed73b8bc29 100644 --- a/codex-rs/core/root_agent_prompt.md +++ b/codex-rs/core/root_agent_prompt.md @@ -1,6 +1,6 @@ # You are the Root Agent -You are the **root agent** in a multi-agent Codex session. Until you see `# You are a Subagent`, these instructions define your role. If this thread was created from the root thread with `spawn_mode = "fork"` (a forked child), you may see both sets of instructions; apply subagent instructions as local role guidance while root instructions remain governing system-level rules. +You are the **root agent** in a multi-agent Codex session. Until you see `# You are a Subagent`, these instructions define your role. If this thread was created from the root thread with `fork_context = true` (a forked child), you may see both sets of instructions; apply subagent instructions as local role guidance while root instructions remain governing system-level rules. ## Root Agent Responsibilities @@ -36,23 +36,23 @@ Create a subagent and give it an initial task. Parameters: - `message` (required): the task description. - `agent_type` (optional): the role to assign (`default`, `explorer`, `fast-worker`, or `worker`). -- `spawn_mode` (optional): one of `spawn` or `fork`. +- `fork_context` (optional): when `true`, the child receives the current thread history. Guidance: -- When `spawn_mode` is omitted, the default is `fork` unless the selected role overrides it. +- When `fork_context` is omitted, the default comes from the selected role and otherwise falls back to `true`. - Use `agent_type = "explorer"` for specific codebase questions; it defaults to context-free `spawn`. - Use `agent_type = "fast-worker"` for tightly constrained execution work that can run from a self-contained prompt; it also defaults to context-free `spawn`. - Use `agent_type = "worker"` for broader implementation work that should inherit current-thread context; it defaults to `fork`. -- Choose `fork` vs `spawn` by context requirements first (not by task shape). -- Use `spawn_mode = "fork"` when the child should preserve your current conversation history and rely on current-thread context, including: +- Choose `fork_context = true` vs `false` by context requirements first (not by task shape). +- Use `fork_context = true` when the child should preserve your current conversation history and rely on current-thread context, including: - current debugging-thread relevance (for example, "summarize only failures relevant to this investigation") - active plan / ExecPlan branch continuation - recent user decisions, tradeoffs, or rejected approaches - parallel review work that should inherit the same context automatically -- Use `spawn_mode = "spawn"` only when the child can do the task correctly from a fresh prompt you provide now, without needing current-thread context. -- For `spawn`, make the task, inputs, and expected output explicit (especially for independent, output-heavy work where you want the child to distill results and keep the root thread context clean). -- Needle-in-a-haystack searches are strong `spawn` candidates when the child can search from a precise prompt without current-thread context. -- Do not choose `spawn` solely because work is output-heavy or command-heavy if it still depends on current-thread context. +- Use `fork_context = false` only when the child can do the task correctly from a fresh prompt you provide now, without needing current-thread context. +- For `fork_context = false`, make the task, inputs, and expected output explicit (especially for independent, output-heavy work where you want the child to distill results and keep the root thread context clean). +- Needle-in-a-haystack searches are strong `fork_context = false` candidates when the child can search from a precise prompt without current-thread context. +- Do not choose `fork_context = false` solely because work is output-heavy or command-heavy if it still depends on current-thread context. ### 2) `send_input` diff --git a/codex-rs/core/src/tools/handlers/multi_agents/spawn.rs b/codex-rs/core/src/tools/handlers/multi_agents/spawn.rs index a3b04f2f12..766625366a 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents/spawn.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents/spawn.rs @@ -11,7 +11,6 @@ use crate::agent::role::default_fork_context_for_role; use crate::agent::role::watchdog_interval_for_role; use crate::config::Config; use codex_features::Feature; -use codex_protocol::protocol::AgentSpawnMode; use codex_protocol::protocol::SessionSource; use std::collections::HashSet; @@ -198,13 +197,6 @@ impl ToolHandler for Handler { prompt, model: effective_model, reasoning_effort: effective_reasoning_effort, - spawn_mode: if is_watchdog { - AgentSpawnMode::Watchdog - } else if fork_context { - AgentSpawnMode::Fork - } else { - AgentSpawnMode::Spawn - }, status, } .into(), diff --git a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs index 603484e5f0..653d0dd520 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs @@ -401,7 +401,7 @@ async fn multi_agent_v2_spawn_fork_context_ignores_child_model_overrides() { } #[tokio::test] -async fn spawn_agent_watchdog_role_returns_handle_without_spawn_mode() { +async fn spawn_agent_watchdog_role_returns_handle_with_role_defaults() { let (mut session, mut turn) = make_session_and_context().await; let manager = thread_manager(); session.services.agent_control = manager.agent_control(); diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs index fc446f928f..43ca2166ea 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs @@ -6,7 +6,6 @@ use crate::agent::role::DEFAULT_ROLE_NAME; use crate::agent::role::apply_role_to_config; use crate::agent::role::default_fork_context_for_role; use codex_protocol::AgentPath; -use codex_protocol::protocol::AgentSpawnMode; use codex_protocol::protocol::InterAgentCommunication; use codex_protocol::protocol::Op; @@ -178,11 +177,6 @@ impl ToolHandler for Handler { prompt, model: effective_model, reasoning_effort: effective_reasoning_effort, - spawn_mode: if fork_context { - AgentSpawnMode::Fork - } else { - AgentSpawnMode::Spawn - }, status, } .into(), diff --git a/codex-rs/core/watchdog_agent_prompt.md b/codex-rs/core/watchdog_agent_prompt.md index 6ff46d661d..8347de0f0d 100644 --- a/codex-rs/core/watchdog_agent_prompt.md +++ b/codex-rs/core/watchdog_agent_prompt.md @@ -52,7 +52,7 @@ When you detect these, prescribe the corrective action explicitly. Use only the multi-agent tools that exist here: -- `spawn_agent` (prefer `spawn_mode = "fork"` when shared context matters). +- `spawn_agent` (prefer `fork_context = true` when shared context matters). - `send_input`. - `compact_parent_context` (watchdog-only recovery tool; see below). - `wait`. diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 1cdd2d5acf..ee22bbc631 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -3420,16 +3420,6 @@ pub struct CollabAgentSpawnBeginEvent { pub reasoning_effort: ReasoningEffortConfig, } -#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS, Default)] -#[serde(rename_all = "snake_case")] -#[ts(rename_all = "snake_case")] -pub enum AgentSpawnMode { - #[default] - Spawn, - Fork, - Watchdog, -} - #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)] pub struct CollabAgentRef { /// Thread ID of the receiver/new agent. @@ -3477,9 +3467,6 @@ pub struct CollabAgentSpawnEndEvent { pub model: String, /// Effective reasoning effort used by the spawned agent after inheritance and role overrides. pub reasoning_effort: ReasoningEffortConfig, - /// Spawn mode used for this agent. - #[serde(default)] - pub spawn_mode: AgentSpawnMode, /// Last known status of the new agent reported to the sender agent. pub status: AgentStatus, } diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 36beb28126..ba05bdbb74 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -139,7 +139,6 @@ use codex_protocol::protocol::AgentReasoningEvent; use codex_protocol::protocol::AgentReasoningRawContentDeltaEvent; #[cfg(test)] use codex_protocol::protocol::AgentReasoningRawContentEvent; -use codex_protocol::protocol::AgentSpawnMode; use codex_protocol::protocol::AgentStatus; use codex_protocol::protocol::ApplyPatchApprovalRequestEvent; #[cfg(test)] @@ -3666,10 +3665,6 @@ impl ChatWidget { prompt: prompt.unwrap_or_default(), model: String::new(), reasoning_effort: ReasoningEffortConfig::Medium, - // Thread history items do not carry spawn_mode yet, so the - // replay path must choose an explicit fallback for reconstructed - // spawn rows. Plain spawn is the least surprising default. - spawn_mode: AgentSpawnMode::Spawn, status: first_receiver .as_ref() .and_then(|thread_id| agents_states.get(&thread_id.to_string())) diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 4a4982b0f0..a7119d962c 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -2341,7 +2341,6 @@ async fn collab_spawn_end_shows_requested_model_and_effort() { new_agent_nickname: Some("Robie".to_string()), new_agent_role: Some("explorer".to_string()), prompt: "Explore the repo".to_string(), - spawn_mode: codex_protocol::protocol::AgentSpawnMode::Spawn, model: "gpt-5".to_string(), reasoning_effort: ReasoningEffortConfig::High, status: AgentStatus::PendingInit, diff --git a/codex-rs/tui/src/multi_agents.rs b/codex-rs/tui/src/multi_agents.rs index cb889b7243..293c80fcf0 100644 --- a/codex-rs/tui/src/multi_agents.rs +++ b/codex-rs/tui/src/multi_agents.rs @@ -583,7 +583,6 @@ fn status_summary_spans(status: &AgentStatus) -> Vec> { mod tests { use super::*; use crate::history_cell::HistoryCell; - use codex_protocol::protocol::AgentSpawnMode; #[cfg(target_os = "macos")] use crossterm::event::KeyEvent; #[cfg(target_os = "macos")] @@ -610,7 +609,6 @@ mod tests { new_agent_nickname: Some("Robie".to_string()), new_agent_role: Some("explorer".to_string()), prompt: "Compute 11! and reply with just the integer result.".to_string(), - spawn_mode: AgentSpawnMode::Spawn, model: "gpt-5".to_string(), reasoning_effort: ReasoningEffortConfig::High, status: AgentStatus::PendingInit, @@ -749,7 +747,6 @@ mod tests { new_agent_nickname: Some("Robie".to_string()), new_agent_role: Some("explorer".to_string()), prompt: String::new(), - spawn_mode: AgentSpawnMode::Spawn, model: "gpt-5".to_string(), reasoning_effort: ReasoningEffortConfig::High, status: AgentStatus::PendingInit,