Remove partial-history subagent forks (#51329)

## What changed

- Limit multi-agent V2 `fork_turns` guidance to `all` and `none`. Accept legacy positive integer strings as full-history forks instead of truncating parent history.
- Remove partial-history fork handling and telemetry, and direct model override usage to `fork_turns: "none"`.
- Strip inherited `multi_agent.role_instructions` and `multi_agent.usage_hint` content by kind, including persisted hints whose wording predates the current bundled instructions.

## Testing

Update regression coverage for legacy `"1"` and `"3"` values inheriting full history, invalid and zero values being rejected, and stale parent hints being removed from compacted history with both marked and unmarked instructions.

GitOrigin-RevId: 8f6017dc66ad8deefd7745399ef76e1bb1435bb2
This commit is contained in:
jif
2026-10-06 10:39:16 +00:00
committed by copyberry
parent 822e58cc3d
commit 6221a217e2
37 changed files with 125 additions and 1065 deletions
@@ -53,7 +53,6 @@ const ROLE_INSTRUCTIONS: &str = "configured role developer instructions";
/// V2 fork modes, roles, and unset/blank overrides expose their agreed instruction precedence.
#[test_case("no history"; "no history")]
#[test_case("full history"; "full history")]
#[test_case("bounded history"; "bounded history")]
#[test_case("configured role without instructions"; "configured role without instructions")]
#[test_case("unset override"; "unset override")]
#[test_case("blank override"; "blank override")]
@@ -61,14 +60,12 @@ const ROLE_INSTRUCTIONS: &str = "configured role developer instructions";
#[test_case("explicit configured role"; "explicit configured role")]
#[test_case("full history configured role"; "full history configured role")]
#[test_case("implicit configured default"; "implicit configured default")]
#[test_case("bounded implicit configured default"; "bounded implicit configured default")]
#[test_case("full fork skips default role"; "full fork skips default role")]
#[tokio::test]
async fn spawned_subagents_apply_configured_developer_instruction_precedence(
case: &str,
) -> Result<()> {
let fork_turns = match case {
"bounded history" | "bounded implicit configured default" => Some("1"),
"no history" | "explicit configured role" | "implicit configured default" => Some("none"),
_ => None,
};
@@ -81,8 +78,7 @@ async fn spawned_subagents_apply_configured_developer_instruction_precedence(
let configured_override = match case {
"unset override"
| "full history configured role"
| "configured role without instructions"
| "bounded implicit configured default" => None,
| "configured role without instructions" => None,
"blank override" => Some(" "),
"full history" => Some(" child-only developer instructions "),
_ => Some(CHILD_INSTRUCTIONS),
@@ -98,7 +94,6 @@ async fn spawned_subagents_apply_configured_developer_instruction_precedence(
| "explicit configured role"
| "full history configured role"
| "implicit configured default"
| "bounded implicit configured default"
| "full fork skips default role"
);
let role_has_instructions = matches!(
@@ -106,7 +101,6 @@ async fn spawned_subagents_apply_configured_developer_instruction_precedence(
"explicit configured role"
| "full history configured role"
| "implicit configured default"
| "bounded implicit configured default"
| "full fork skips default role"
);
let expected = match case {
@@ -114,8 +108,7 @@ async fn spawned_subagents_apply_configured_developer_instruction_precedence(
"blank override" => None,
"explicit configured role"
| "full history configured role"
| "implicit configured default"
| "bounded implicit configured default" => Some(ROLE_INSTRUCTIONS),
| "implicit configured default" => Some(ROLE_INSTRUCTIONS),
_ => Some(CHILD_INSTRUCTIONS),
};
const PARENT_PROMPT: &str = "spawn the instruction override worker";
-1
View File
@@ -19,7 +19,6 @@ use crate::thread_manager::ResumeThreadWithHistoryOptions;
use crate::thread_manager::ThreadIdGenerator;
use crate::thread_manager::ThreadManagerState;
use crate::thread_manager::default_thread_id_generator;
use crate::thread_rollout_truncation::truncate_rollout_to_last_n_fork_turns;
use crate::turn_timing::now_unix_timestamp_ms;
use codex_history::InitialHistory;
use codex_history::ResumedHistory;
+18 -22
View File
@@ -7,7 +7,6 @@ use crate::agent::child_config::build_agent_resume_config;
use crate::agent::role::apply_role_to_config;
use crate::agent::types::AgentMetadata;
use crate::agent::types::LiveAgent;
use crate::agent::types::SpawnAgentForkMode;
use crate::agent::types::SpawnAgentOptions;
use crate::agents_md_manager::SessionInstructions;
use crate::codex_thread::CodexThread;
@@ -122,8 +121,7 @@ fn keep_forked_rollout_item(item: &RolloutItem, preserve_context_baselines: bool
| RolloutItem::RetainedContext(_)
| RolloutItem::SecurityRiskScore(_) => false,
// Full-history forks preserve the cached prompt prefix and can keep diffing
// from the parent's durable baseline. Truncated forks drop part of that prompt,
// so they must rebuild context on their first child turn.
// from the parent's durable baseline unless legacy compaction requires rebuilding it.
RolloutItem::TurnContext(_) | RolloutItem::WorldState(_) => preserve_context_baselines,
// Child threads inherit model context, not the parent's cumulative usage state.
RolloutItem::TokenUsageRecord(_) => false,
@@ -140,7 +138,11 @@ fn retain_forked_developer_message(item: &mut ResponseItem, usage_hint_texts: &[
return false;
};
content.retain(|content_item| {
if content_item.kind().0 == "guardian.approved_action" {
// Persisted role hints can predate the current bundled wording and lack markers.
if matches!(
content_item.kind().0.as_str(),
"guardian.approved_action" | "multi_agent.role_instructions" | "multi_agent.usage_hint"
) {
return false;
}
let ContentItem::InputText { text } = content_item.content() else {
@@ -990,11 +992,11 @@ impl LocalAgentControl {
"spawn_agent fork requires a parent spawn call id".to_string(),
));
}
let Some(fork_mode) = options.fork_mode.as_ref() else {
if options.fork_mode.is_none() {
return Err(CodexErr::Fatal(
"spawn_agent fork requires a fork mode".to_string(),
));
};
}
let SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
parent_thread_id, ..
}) = &session_source
@@ -1062,10 +1064,6 @@ impl LocalAgentControl {
Some(meta_line.meta.selected_capability_roots.clone())
})
.unwrap_or_default();
if let SpawnAgentForkMode::LastNTurns(last_n_turns) = fork_mode {
forked_rollout_items =
truncate_rollout_to_last_n_fork_turns(forked_rollout_items, *last_n_turns);
}
let multi_agent_v2_usage_hint_texts_to_filter: Vec<String> =
if multi_agent_version == MultiAgentVersion::V2 {
let parent_config = parent_thread.session.get_config().await;
@@ -1082,19 +1080,17 @@ impl LocalAgentControl {
} else {
Vec::new()
};
let mut preserve_context_baselines = matches!(fork_mode, SpawnAgentForkMode::FullHistory);
if preserve_context_baselines {
for item in forked_rollout_items.iter().rev() {
let RolloutItem::Compacted(compacted) = item else {
continue;
};
// Legacy checkpoints force the child to rebuild context regardless of the
// live parent's reference baseline; an older superseded checkpoint does not.
if compacted.replacement_history.is_none() {
preserve_context_baselines = false;
}
break;
let mut preserve_context_baselines = true;
for item in forked_rollout_items.iter().rev() {
let RolloutItem::Compacted(compacted) = item else {
continue;
};
// Legacy checkpoints force the child to rebuild context regardless of the
// live parent's reference baseline; an older superseded checkpoint does not.
if compacted.replacement_history.is_none() {
preserve_context_baselines = false;
}
break;
}
let mut replaced_parent_developer_instructions = false;
// Scrub inherited hints and replace only the parent's developer-instruction fragment.
@@ -28,7 +28,6 @@ pub(super) fn record_spawn_success(
let fork_mode = match fork_mode {
None => "none",
Some(SpawnAgentForkMode::FullHistory) => "all",
Some(SpawnAgentForkMode::LastNTurns(_)) => "last_n",
};
let history_mode = match measurements.history_mode {
ThreadHistoryMode::Legacy => "legacy",
@@ -39,7 +39,7 @@ fn records_bounded_spawn_phase_metrics() {
record_spawn_success(
&telemetry,
Some(&SpawnAgentForkMode::LastNTurns(3)),
Some(&SpawnAgentForkMode::FullHistory),
MultiAgentVersion::V2,
SpawnMeasurements {
history_mode: ThreadHistoryMode::Paginated,
@@ -74,7 +74,7 @@ fn records_bounded_spawn_phase_metrics() {
})
.collect::<BTreeMap<_, _>>();
assert_eq!(attributes.len(), 5);
assert_eq!(attributes["fork_mode"], "last_n");
assert_eq!(attributes["fork_mode"], "all");
assert_eq!(attributes["history_mode"], "paginated");
assert_eq!(attributes["multi_agent_version"], "v2");
assert_eq!(attributes["product_sku"], "codex");
+15 -530
View File
@@ -31,7 +31,6 @@ use crate::thread_manager::ForkSnapshot;
use crate::thread_manager::StartThreadOptions;
use crate::tools::handlers::multi_agents_common::thread_spawn_source;
use assert_matches::assert_matches;
use codex_extension_api::ExtensionDataInit;
use codex_extension_api::Instructions;
use codex_extension_api::LoadInstructionsFuture;
use codex_extension_api::LoadedUserInstructions;
@@ -46,8 +45,6 @@ use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_protocol::AgentPath;
use codex_protocol::ResponseItemId;
use codex_protocol::capabilities::CapabilityRootLocation;
use codex_protocol::capabilities::SelectedCapabilityRoot;
use codex_protocol::config_types::ApprovalsReviewer;
use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::ModeKind;
@@ -2243,88 +2240,6 @@ async fn spawn_agent_fork_sanitizes_inherited_compaction_metadata() {
);
}
#[tokio::test]
async fn spawn_agent_numeric_fork_from_compacted_paginated_parent_clamps_to_provable_turns() {
let harness = AgentControlHarness::new().await;
let (parent_thread_id, parent_thread) = harness.start_paginated_thread().await;
let parent_spawn_call_id = "spawn-call-paginated-numeric".to_string();
parent_thread
.session
.persist_rollout_items(&[
RolloutItem::Compacted(CompactedItem {
message: String::new(),
replacement_history: Some(vec![
ResponseItem::Message {
id: None,
role: "user".to_string(),
content: vec![ContentItem::InputText {
text: "compacted summary".to_string(),
}],
phase: None,
internal_chat_message_metadata_passthrough: None,
}
.into(),
]),
retained_context: None,
guardian_history: None,
mcp_resource_origins: None,
window_number: None,
first_window_id: None,
previous_window_id: None,
window_id: None,
compaction_response_id: None,
latest_token_usage_record: None,
resume_metadata: None,
}),
rollout_response_item(ResponseItem::Message {
id: None,
role: "user".to_string(),
content: vec![ContentItem::InputText {
text: "recent parent turn".to_string(),
}],
phase: None,
internal_chat_message_metadata_passthrough: None,
}),
rollout_response_item(spawn_agent_call(&parent_spawn_call_id)),
])
.await;
let clamped_child_thread_id = harness
.spawn_anonymous_child(
parent_thread_id,
SpawnAgentOptions {
fork_parent_spawn_call_id: Some(parent_spawn_call_id),
fork_mode: Some(SpawnAgentForkMode::LastNTurns(2)),
..Default::default()
},
)
.await;
let clamped_child_thread = harness
.manager
.get_thread(clamped_child_thread_id)
.await
.expect("clamped child thread should be registered");
let clamped_history = clamped_child_thread.session.clone_history().await;
assert!(
history_contains_text(clamped_history.raw_items(), "recent parent turn"),
"clamped numeric fork should keep the provable recent turn"
);
assert!(
!history_contains_text(clamped_history.raw_items(), "compacted summary"),
"clamped numeric fork should not expand into compacted parent context"
);
let _ = harness
.control
.shutdown_live_agent(clamped_child_thread_id)
.await
.expect("clamped child shutdown should submit");
let _ = parent_thread
.submit(Op::Shutdown {})
.await
.expect("parent shutdown should submit");
}
#[tokio::test]
async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
let managed_fragment = "<managed_developer_instructions>\nParent developer instructions.\n</managed_developer_instructions>";
@@ -2708,8 +2623,10 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
.expect("parent shutdown should submit");
}
#[test_case::test_case(true; "marked catalog hints")]
#[test_case::test_case(false; "unmarked bundled hints")]
#[tokio::test]
async fn spawn_agent_fork_strips_parent_usage_hints_from_compacted_history() {
async fn spawn_agent_fork_strips_parent_usage_hints_from_compacted_history(marked: bool) {
let harness = AgentControlHarness::new().await;
let mut parent_config = harness.config.clone();
let _ = parent_config.features.enable(Feature::MultiAgentV2);
@@ -2738,7 +2655,7 @@ async fn spawn_agent_fork_strips_parent_usage_hints_from_compacted_history() {
let parent_spawn_call_id = "spawn-call-compacted-usage-hints".to_string();
let catalog_role = |base: &str| MultiAgentRoleInstructions::Composed {
base: base.to_string(),
marked: true,
marked,
omit_update_plan_instructions: false,
max_concurrency: 2,
wait_agent_enabled: false,
@@ -2768,10 +2685,15 @@ async fn spawn_agent_fork_strips_parent_usage_hints_from_compacted_history() {
id: None,
role: "developer".to_string(),
content: vec![ContentItem::InputText {
text: "Parent root guidance.".to_string(),
text: "Previous parent root guidance.".to_string(),
}],
phase: None,
internal_chat_message_metadata_passthrough: None,
internal_chat_message_metadata_passthrough: Some(
InternalChatMessageMetadataPassthrough {
content_item_kinds: Some(vec![ContentItemKind("multi_agent.usage_hint".to_string())]),
..Default::default()
},
),
},
ResponseItem::Message {
id: None,
@@ -2922,7 +2844,7 @@ async fn spawn_agent_fork_strips_parent_usage_hints_from_compacted_history() {
"forked child history should not inherit compacted parent agent messages"
);
assert!(
!history_contains_text(history.raw_items(), "Parent root guidance."),
!history_contains_text(history.raw_items(), "Previous parent root guidance."),
"forked child history should strip stale parent hints from compacted replacement history"
);
assert!(
@@ -3529,443 +3451,6 @@ async fn spawn_agent_fork_flushes_parent_rollout_before_loading_history() {
.expect("parent shutdown should submit");
}
#[tokio::test]
async fn spawn_agent_fork_last_n_turns_keeps_only_recent_turns() {
let harness = AgentControlHarness::new().await;
let (parent_thread_id, parent_thread) = harness.start_thread().await;
let old_turn_context = parent_thread.session.new_default_turn().await;
parent_thread
.session
.record_conversation_items(
old_turn_context.as_ref(),
old_turn_context.model_info(),
&[user_message("old parent context")],
)
.await;
let source = McpAttributionSource {
connector_id: None,
plugin_id: None,
server_name: "example".to_string(),
tool_name: "search".to_string(),
first_turn_id: old_turn_context.sub_id.clone(),
};
parent_thread
.session
.services
.executed_tool_calls
.record_mcp_source(source.clone());
parent_thread
.session
.record_conversation_items(
old_turn_context.as_ref(),
old_turn_context.model_info(),
&[assistant_message("old MCP result", /*phase*/ None)],
)
.await;
let queued_communication = InterAgentCommunication::new(
AgentPath::root(),
AgentPath::try_from("/root/worker").expect("agent path"),
Vec::new(),
"queued message".to_string(),
/*trigger_turn*/ false,
);
let queued_turn_context = parent_thread.session.new_default_turn().await;
parent_thread
.session
.record_conversation_items(
queued_turn_context.as_ref(),
queued_turn_context.model_info(),
&[queued_communication.to_response_input_item().into()],
)
.await;
let triggered_communication = InterAgentCommunication::new(
AgentPath::root(),
AgentPath::try_from("/root/worker").expect("agent path"),
Vec::new(),
"triggered context".to_string(),
/*trigger_turn*/ true,
);
let triggered_turn_context = parent_thread.session.new_default_turn().await;
parent_thread
.session
.record_conversation_items(
triggered_turn_context.as_ref(),
triggered_turn_context.model_info(),
&[triggered_communication.to_response_input_item().into()],
)
.await;
parent_thread
.inject_response_items(vec![user_message("current parent task")])
.await
.expect("inject current parent task");
let spawn_turn_context = parent_thread.session.new_default_turn().await;
let parent_spawn_call_id = "spawn-call-last-n".to_string();
parent_thread
.session
.record_conversation_items(
spawn_turn_context.as_ref(),
spawn_turn_context.model_info(),
&[spawn_agent_call(&parent_spawn_call_id)],
)
.await;
parent_thread
.session
.persist_rollout_items(&[RolloutItem::TurnContext(
spawn_turn_context.to_turn_context_item(),
)])
.await;
parent_thread
.session
.ensure_rollout_materialized(PersistContext::Standard)
.await;
parent_thread
.session
.flush_rollout()
.await
.expect("parent rollout should flush");
let child_thread_id = harness
.control
.spawn_agent_with_metadata(
harness.config.clone(),
text_input("child task"),
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
parent_thread_id,
depth: 1,
agent_path: None,
agent_nickname: None,
agent_role: None,
}),
SpawnAgentOptions {
fork_parent_spawn_call_id: Some(parent_spawn_call_id.clone()),
fork_mode: Some(SpawnAgentForkMode::LastNTurns(2)),
..Default::default()
},
)
.await
.expect("forked spawn should keep only the last two turns")
.thread_id;
let child_thread = harness
.manager
.get_thread(child_thread_id)
.await
.expect("child thread should be registered");
let history = child_thread.session.clone_history().await;
assert!(
!history_contains_text(history.raw_items(), "old parent context"),
"forked child history should drop parent context outside the requested last-N turn window"
);
assert!(
!history_contains_text(history.raw_items(), "old MCP result"),
"forked child history should drop the old result while retaining its attribution"
);
assert!(
!history_contains_text(history.raw_items(), "queued message"),
"forked child history should drop queued inter-agent messages outside the requested last-N turn window"
);
assert!(
!history_contains_text(history.raw_items(), "triggered context"),
"forked child history should filter assistant inter-agent messages even when they fall inside the requested last-N turn window"
);
assert!(
history_contains_text(history.raw_items(), "current parent task"),
"forked child history should keep the parent user message from the requested last-N turn window"
);
assert!(
child_thread
.session
.reference_context_item()
.await
.is_none(),
"last-N forked child should rebuild context after truncating the cached prefix"
);
assert_eq!(
mcp_attribution_in_constructed_request(&child_thread).await,
McpAttribution {
status: McpAttributionStatus::Complete,
error_reason: None,
sources: vec![source],
}
);
let _ = harness
.control
.shutdown_live_agent(child_thread_id)
.await
.expect("child shutdown should submit");
let _ = parent_thread
.submit(Op::Shutdown {})
.await
.expect("parent shutdown should submit");
}
#[tokio::test]
async fn spawn_agent_fork_last_n_turns_drops_parent_startup_prefix_when_under_limit() {
let harness = AgentControlHarness::new().await;
let selected_capability_roots = vec![SelectedCapabilityRoot {
id: "demo@1".to_string(),
location: CapabilityRootLocation::Environment {
environment_id: "build".to_string(),
path: PathUri::parse("file:///plugins/demo").expect("plugin root URI"),
},
}];
let mut thread_extension_init = ExtensionDataInit::new();
thread_extension_init.insert(selected_capability_roots.clone());
let parent = harness
.manager
.start_thread(StartThreadOptions {
environments: Some(Vec::new()),
thread_extension_init,
..StartThreadOptions::new(harness.config.clone())
})
.await
.expect("start parent thread");
let parent_thread_id = parent.thread_id;
let parent_thread = parent.thread;
let startup_turn_context = parent_thread.session.new_default_turn().await;
parent_thread
.session
.record_conversation_items(
startup_turn_context.as_ref(),
startup_turn_context.model_info(),
&[ResponseItem::Message {
id: None,
role: "developer".to_string(),
content: vec![ContentItem::InputText {
text: "parent startup developer context".to_string(),
}],
phase: None,
internal_chat_message_metadata_passthrough: None,
}],
)
.await;
parent_thread
.inject_response_items(vec![user_message("current parent task")])
.await
.expect("inject current parent task");
let spawn_turn_context = parent_thread.session.new_default_turn().await;
let parent_spawn_call_id = "spawn-call-last-n-under-limit".to_string();
parent_thread
.session
.record_conversation_items(
spawn_turn_context.as_ref(),
spawn_turn_context.model_info(),
&[spawn_agent_call(&parent_spawn_call_id)],
)
.await;
parent_thread
.session
.ensure_rollout_materialized(PersistContext::Standard)
.await;
parent_thread
.session
.flush_rollout()
.await
.expect("parent rollout should flush");
let child_thread_id = harness
.control
.spawn_agent_with_metadata(
harness.config.clone(),
text_input("child task"),
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
parent_thread_id,
depth: 1,
agent_path: None,
agent_nickname: None,
agent_role: None,
}),
SpawnAgentOptions {
fork_parent_spawn_call_id: Some(parent_spawn_call_id),
fork_mode: Some(SpawnAgentForkMode::LastNTurns(2)),
..Default::default()
},
)
.await
.expect("bounded forked spawn should drop startup prefix")
.thread_id;
let child_thread = harness
.manager
.get_thread(child_thread_id)
.await
.expect("child thread should be registered");
let history = child_thread.session.clone_history().await;
assert!(
history_contains_text(history.raw_items(), "current parent task"),
"bounded fork should retain the requested recent parent turn"
);
assert!(
!history_contains_text(history.raw_items(), "parent startup developer context"),
"bounded fork should drop parent startup context even when fewer turns exist than requested"
);
assert_eq!(
&child_thread.session.services.selected_capability_roots,
&selected_capability_roots
);
assert!(
child_thread
.session
.reference_context_item()
.await
.is_none(),
"bounded forked child should still rebuild context after truncating the cached prefix"
);
let _ = harness
.control
.shutdown_live_agent(child_thread_id)
.await
.expect("child shutdown should submit");
let _ = parent_thread
.submit(Op::Shutdown {})
.await
.expect("parent shutdown should submit");
}
#[tokio::test]
async fn spawn_agent_fork_last_n_turns_strips_parent_usage_hints() {
let persistent_fragment =
"<persistent_mode>\nParent persistent instructions.\n</persistent_mode>";
let harness = AgentControlHarness::new().await;
let mut parent_config = harness.config.clone();
let _ = parent_config.features.enable(Feature::MultiAgentV2);
parent_config.developer_instructions = Some("Parent developer instructions.".to_string());
parent_config.multi_agent_v2.root_agent_usage_hint_text =
Some("Parent root guidance.".to_string());
let mut child_config = harness.config.clone();
let _ = child_config.features.enable(Feature::MultiAgentV2);
child_config.developer_instructions = Some("Child developer instructions.".to_string());
child_config.multi_agent_v2.subagent_developer_instructions =
Some("Child developer instructions.".to_string());
child_config.multi_agent_v2.subagent_usage_hint_text =
Some("Child subagent guidance.".to_string());
let new_thread = harness
.manager
.start_thread(StartThreadOptions::new(parent_config))
.await
.expect("start parent thread");
let parent_thread_id = new_thread.thread_id;
let parent_thread = new_thread.thread;
parent_thread
.inject_response_items(vec![user_message("parent task")])
.await
.expect("inject parent task");
let turn_context = parent_thread.session.new_default_turn().await;
let parent_spawn_call_id = "spawn-call-last-n-usage-hints".to_string();
parent_thread
.session
.record_conversation_items(
turn_context.as_ref(),
turn_context.model_info(),
&[
ResponseItem::Message {
id: None,
role: "developer".to_string(),
content: vec![ContentItem::InputText {
text: "Parent root guidance.".to_string(),
}],
phase: None,
internal_chat_message_metadata_passthrough: None,
},
ResponseItem::Message {
id: None,
role: "developer".to_string(),
content: vec![
ContentItem::InputText {
text: "Parent developer instructions.".to_string(),
},
ContentItem::InputText {
text: "Preserved bounded developer context.".to_string(),
},
ContentItem::InputText {
text: persistent_fragment.to_string(),
},
],
phase: None,
internal_chat_message_metadata_passthrough: None,
},
spawn_agent_call(&parent_spawn_call_id),
],
)
.await;
parent_thread
.session
.ensure_rollout_materialized(PersistContext::Standard)
.await;
parent_thread
.session
.flush_rollout()
.await
.expect("parent rollout should flush");
let child_thread_id = harness
.control
.spawn_agent_with_metadata(
child_config,
text_input("child task"),
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
parent_thread_id,
depth: 1,
agent_path: None,
agent_nickname: None,
agent_role: None,
}),
SpawnAgentOptions {
fork_parent_spawn_call_id: Some(parent_spawn_call_id),
fork_mode: Some(SpawnAgentForkMode::LastNTurns(2)),
..Default::default()
},
)
.await
.expect("bounded forked spawn should sanitize parent usage hints")
.thread_id;
let child_thread = harness
.manager
.get_thread(child_thread_id)
.await
.expect("child thread should be registered");
let history = child_thread.session.clone_history().await;
assert!(
history_contains_text(history.raw_items(), "parent task"),
"bounded fork should retain the requested recent parent turn"
);
assert!(
!history_contains_text(history.raw_items(), "Parent root guidance."),
"bounded fork should strip stale parent root hints before the child rebuilds startup context"
);
assert!(
!history_contains_text(history.raw_items(), "Parent developer instructions."),
"bounded fork should remove parent instructions before the child rebuilds startup context"
);
assert!(
!history_contains_text(history.raw_items(), "Child developer instructions."),
"bounded fork should not inject child instructions before its canonical context rebuild"
);
assert!(
!history_contains_text(history.raw_items(), persistent_fragment),
"bounded fork should remove persistent instructions before rebuilding context for the child's effort"
);
assert!(
history_contains_text(history.raw_items(), "Preserved bounded developer context."),
"bounded fork should preserve unrelated developer fragments"
);
let _ = harness
.control
.shutdown_live_agent(child_thread_id)
.await
.expect("child shutdown should submit");
let _ = parent_thread
.submit(Op::Shutdown {})
.await
.expect("parent shutdown should submit");
}
#[tokio::test]
async fn spawn_agent_respects_legacy_max_threads_alias() {
let max_threads = 1usize;
@@ -4537,7 +4022,7 @@ async fn spawn_thread_subagent_gets_random_nickname_in_session_source() {
}
#[tokio::test]
async fn spawn_thread_subagents_persist_parent_originator_across_new_and_truncated_fork() {
async fn spawn_thread_subagents_persist_parent_originator_across_new_and_full_fork() {
let harness = AgentControlHarness::new().await;
let parent = harness
.manager
@@ -4588,8 +4073,8 @@ async fn spawn_thread_subagents_persist_parent_originator_across_new_and_truncat
agent_role: Some("explorer".to_string()),
}),
SpawnAgentOptions {
fork_parent_spawn_call_id: Some("spawn-call-last-n".to_string()),
fork_mode: Some(SpawnAgentForkMode::LastNTurns(1)),
fork_parent_spawn_call_id: Some("spawn-call-full-history".to_string()),
fork_mode: Some(SpawnAgentForkMode::FullHistory),
..Default::default()
},
)
-1
View File
@@ -21,7 +21,6 @@ pub struct AgentMetadata {
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SpawnAgentForkMode {
FullHistory,
LastNTurns(usize),
}
#[derive(Clone, Debug, Default)]
@@ -14,7 +14,6 @@ use codex_protocol::error::Result as CodexResult;
use codex_protocol::items::TurnItem;
use codex_protocol::models::ResponseItem;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::InterAgentCommunication;
pub(crate) fn initial_history_has_prior_user_turns(conversation_history: &InitialHistory) -> bool {
conversation_history.scan_rollout_items(rollout_item_is_user_turn_boundary)
@@ -65,72 +64,6 @@ pub(crate) fn user_message_positions_in_rollout(items: &[RolloutItem]) -> Vec<us
user_positions
}
/// Return the indices of fork-turn boundaries in a rollout.
///
/// A fork-turn boundary is either:
/// - a real user message boundary, or
/// - an inter-agent communication whose `trigger_turn` is `true`, or
/// - a legacy assistant inter-agent envelope with the same flag.
///
/// Like `user_message_positions_in_rollout`, this applies `ThreadRolledBack` markers so indexing
/// reflects the effective post-rollback history. Rollback counts instruction turns, so a rollback
/// removes the stale suffix starting at the earliest rolled-back instruction-turn boundary instead
/// of simply truncating the mixed fork-boundary list.
pub(crate) fn fork_turn_positions_in_rollout(items: &[RolloutItem]) -> Vec<usize> {
let mut rollback_turn_positions = Vec::new();
let mut fork_turn_positions = Vec::new();
for (idx, item) in items.iter().enumerate() {
match item {
RolloutItem::ResponseItem(item) => {
let has_delivery_metadata = matches!(&item.item, ResponseItem::AgentMessage { .. })
&& idx.checked_sub(1).is_some_and(|previous_idx| {
matches!(
items.get(previous_idx),
Some(RolloutItem::InterAgentCommunicationMetadata { .. })
)
});
if is_user_turn_boundary(item) && !has_delivery_metadata {
rollback_turn_positions.push(idx);
}
if is_real_user_message_boundary(item) || is_trigger_turn_boundary(item) {
fork_turn_positions.push(idx);
}
}
RolloutItem::InterAgentCommunication(communication) => {
rollback_turn_positions.push(idx);
if communication.trigger_turn {
fork_turn_positions.push(idx);
}
}
RolloutItem::InterAgentCommunicationMetadata { trigger_turn } => {
rollback_turn_positions.push(idx);
if *trigger_turn {
fork_turn_positions.push(idx);
}
}
RolloutItem::EventMsg(EventMsg::ThreadRolledBack(rollback)) => {
let num_turns = usize::try_from(rollback.num_turns).unwrap_or(usize::MAX);
if num_turns == 0 {
continue;
}
let Some(rollback_start_idx) = rollback_turn_positions
.len()
.checked_sub(num_turns)
.map(|rollback_start| rollback_turn_positions[rollback_start])
.or_else(|| rollback_turn_positions.first().copied())
else {
continue;
};
let new_rollback_len = rollback_turn_positions.len().saturating_sub(num_turns);
rollback_turn_positions.truncate(new_rollback_len);
fork_turn_positions.retain(|position| *position < rollback_start_idx);
}
_ => {}
}
}
fork_turn_positions
}
/// Return a prefix of `items` obtained by cutting strictly before the nth user message.
///
/// The boundary index is 0-based from the start of `items` (so `n_from_start = 0` returns
@@ -259,47 +192,6 @@ pub fn truncate_rollout_before_turn_id(
Ok(items)
}
/// Return a suffix of `items` that keeps the last `n_from_end` fork turns.
///
/// If fewer than or equal to `n_from_end` fork turns exist, this keeps from the first fork-turn
/// boundary and still drops pre-turn startup context.
pub(crate) fn truncate_rollout_to_last_n_fork_turns(
mut items: Vec<RolloutItem>,
n_from_end: usize,
) -> Vec<RolloutItem> {
if n_from_end == 0 {
return Vec::new();
}
let fork_turn_positions = fork_turn_positions_in_rollout(&items);
let Some(keep_idx) = fork_turn_positions
.len()
.checked_sub(n_from_end)
.map(|position| fork_turn_positions[position])
.or_else(|| fork_turn_positions.first().copied())
else {
return Vec::new();
};
items.split_off(keep_idx)
}
fn is_real_user_message_boundary(item: &ResponseItem) -> bool {
matches!(
event_mapping::parse_turn_item(item),
Some(TurnItem::UserMessage(_))
)
}
fn is_trigger_turn_boundary(item: &ResponseItem) -> bool {
let ResponseItem::Message { role, content, .. } = item else {
return false;
};
role == "assistant"
&& InterAgentCommunication::from_message_content(content)
.is_some_and(|communication| communication.trigger_turn)
}
#[cfg(test)]
#[path = "thread_rollout_truncation_tests.rs"]
mod tests;
@@ -2,12 +2,10 @@ use super::*;
use crate::session::step_context::StepContext;
use crate::session::tests::build_world_state_from_turn_context;
use crate::session::tests::make_session_and_context;
use codex_protocol::AgentPath;
use codex_protocol::ResponseItemId;
use codex_protocol::error::CodexErrorDetails;
use codex_protocol::models::ContentItem;
use codex_protocol::models::ReasoningItemReasoningSummary;
use codex_protocol::protocol::InterAgentCommunication;
use codex_protocol::protocol::ThreadRolledBackEvent;
use codex_protocol::protocol::TurnCompleteEvent;
use codex_protocol::protocol::TurnStartedEvent;
@@ -43,39 +41,6 @@ fn assistant_msg(text: &str) -> ResponseItem {
}
}
fn developer_msg(text: &str) -> ResponseItem {
ResponseItem::Message {
id: None,
role: "developer".to_string(),
content: vec![ContentItem::InputText {
text: text.to_string(),
}],
phase: None,
internal_chat_message_metadata_passthrough: None,
}
}
fn inter_agent_msg(text: &str, trigger_turn: bool) -> ResponseItem {
let communication = InterAgentCommunication::new(
AgentPath::root(),
AgentPath::try_from("/root/worker").expect("agent path"),
Vec::new(),
text.to_string(),
trigger_turn,
);
communication.to_response_input_item().into()
}
fn inter_agent_communication(text: &str, trigger_turn: bool) -> RolloutItem {
RolloutItem::InterAgentCommunication(InterAgentCommunication::new(
AgentPath::root(),
AgentPath::try_from("/root/worker").expect("agent path"),
Vec::new(),
text.to_string(),
trigger_turn,
))
}
fn turn_started(turn_id: &str) -> RolloutItem {
RolloutItem::EventMsg(EventMsg::TurnStarted(TurnStartedEvent {
turn_id: turn_id.to_string(),
@@ -393,220 +358,3 @@ async fn ignores_session_prefix_messages_when_truncating_rollout_from_start() {
serde_json::to_value(&expected).unwrap()
);
}
#[test]
fn truncates_rollout_to_last_n_fork_turns_counts_trigger_turn_messages() {
let rollout = vec![
response_item(user_msg("u1")),
response_item(assistant_msg("a1")),
response_item(inter_agent_msg(
"queued message",
/*trigger_turn*/ false,
)),
response_item(assistant_msg("a2")),
response_item(inter_agent_msg(
"triggered task",
/*trigger_turn*/ true,
)),
response_item(assistant_msg("a3")),
response_item(user_msg("u2")),
response_item(assistant_msg("a4")),
];
let truncated = truncate_rollout_to_last_n_fork_turns(rollout.clone(), /*n_from_end*/ 2);
let expected = rollout[4..].to_vec();
assert_eq!(
serde_json::to_value(&truncated).unwrap(),
serde_json::to_value(&expected).unwrap()
);
}
#[test]
fn fork_turn_positions_use_inter_agent_delivery_metadata() {
let rollout = vec![
response_item(user_msg("user task")),
inter_agent_communication("queued during user turn", /*trigger_turn*/ false),
response_item(assistant_msg("first answer")),
inter_agent_communication("follow-up task", /*trigger_turn*/ true),
response_item(assistant_msg("second answer")),
response_item(user_msg("next user task")),
];
assert_eq!(fork_turn_positions_in_rollout(&rollout), vec![0, 3, 5]);
}
#[test]
fn fork_turn_positions_use_canonical_agent_messages_and_delivery_metadata() {
let queued = InterAgentCommunication::new(
AgentPath::root(),
AgentPath::try_from("/root/worker").expect("agent path"),
Vec::new(),
"queued during user turn".to_string(),
/*trigger_turn*/ false,
);
let triggered = InterAgentCommunication::new(
AgentPath::root(),
AgentPath::try_from("/root/worker").expect("agent path"),
Vec::new(),
"follow-up task".to_string(),
/*trigger_turn*/ true,
);
let mut rollout = vec![
response_item(user_msg("user task")),
RolloutItem::InterAgentCommunicationMetadata {
trigger_turn: false,
},
response_item(queued.to_model_input_item()),
response_item(assistant_msg("first answer")),
RolloutItem::InterAgentCommunicationMetadata { trigger_turn: true },
response_item(triggered.to_model_input_item()),
response_item(assistant_msg("second answer")),
response_item(user_msg("next user task")),
];
assert_eq!(fork_turn_positions_in_rollout(&rollout), vec![0, 4, 7]);
rollout.insert(
7,
RolloutItem::EventMsg(EventMsg::ThreadRolledBack(ThreadRolledBackEvent {
num_turns: 1,
})),
);
assert_eq!(fork_turn_positions_in_rollout(&rollout), vec![0, 8]);
}
#[test]
fn truncates_rollout_to_last_n_fork_turns_drops_startup_prefix_even_when_under_limit() {
let rollout = vec![
response_item(developer_msg("startup developer context")),
response_item(user_msg("current task")),
response_item(assistant_msg("answer")),
];
let truncated = truncate_rollout_to_last_n_fork_turns(rollout.clone(), /*n_from_end*/ 2);
let expected = rollout[1..].to_vec();
assert_eq!(
serde_json::to_value(&truncated).unwrap(),
serde_json::to_value(&expected).unwrap()
);
}
#[test]
fn truncates_rollout_to_last_n_fork_turns_applies_thread_rollback_markers() {
let rollout = vec![
response_item(user_msg("u1")),
response_item(assistant_msg("a1")),
response_item(inter_agent_msg(
"triggered task",
/*trigger_turn*/ true,
)),
response_item(assistant_msg("a2")),
RolloutItem::EventMsg(EventMsg::ThreadRolledBack(ThreadRolledBackEvent {
num_turns: 1,
})),
response_item(user_msg("u2")),
response_item(assistant_msg("a3")),
];
let truncated = truncate_rollout_to_last_n_fork_turns(rollout.clone(), /*n_from_end*/ 2);
assert_eq!(
serde_json::to_value(&truncated).unwrap(),
serde_json::to_value(&rollout).unwrap()
);
}
#[test]
fn fork_turn_positions_ignore_zero_turn_rollback_markers() {
let rollout = vec![
response_item(user_msg("u1")),
response_item(inter_agent_msg(
"triggered task",
/*trigger_turn*/ true,
)),
RolloutItem::EventMsg(EventMsg::ThreadRolledBack(ThreadRolledBackEvent {
num_turns: 0,
})),
response_item(user_msg("u2")),
];
assert_eq!(fork_turn_positions_in_rollout(&rollout), vec![0, 1, 3]);
}
#[test]
fn truncates_rollout_to_last_n_fork_turns_discards_trigger_boundaries_in_rolled_back_suffix() {
let rollout = vec![
response_item(user_msg("u1")),
response_item(user_msg("u2")),
response_item(inter_agent_msg(
"triggered task",
/*trigger_turn*/ true,
)),
response_item(assistant_msg("a1")),
RolloutItem::EventMsg(EventMsg::ThreadRolledBack(ThreadRolledBackEvent {
num_turns: 1,
})),
response_item(user_msg("u3")),
response_item(assistant_msg("a2")),
];
let truncated = truncate_rollout_to_last_n_fork_turns(rollout.clone(), /*n_from_end*/ 2);
let expected = rollout[1..].to_vec();
assert_eq!(
serde_json::to_value(&truncated).unwrap(),
serde_json::to_value(&expected).unwrap()
);
}
#[test]
fn truncates_rollout_to_last_n_fork_turns_discards_rolled_back_assistant_instruction_turns() {
let rollout = vec![
response_item(user_msg("u1")),
response_item(assistant_msg("a1")),
response_item(inter_agent_msg(
"triggered task 1",
/*trigger_turn*/ true,
)),
response_item(assistant_msg("a2")),
RolloutItem::EventMsg(EventMsg::ThreadRolledBack(ThreadRolledBackEvent {
num_turns: 1,
})),
response_item(inter_agent_msg(
"triggered task 2",
/*trigger_turn*/ true,
)),
response_item(assistant_msg("a3")),
];
let truncated = truncate_rollout_to_last_n_fork_turns(rollout.clone(), /*n_from_end*/ 1);
let expected = rollout[5..].to_vec();
assert_eq!(
serde_json::to_value(&truncated).unwrap(),
serde_json::to_value(&expected).unwrap()
);
}
#[test]
fn truncates_rollout_to_last_n_fork_turns_keeps_full_rollout_when_n_is_large() {
let rollout = vec![
response_item(user_msg("u1")),
response_item(assistant_msg("a1")),
response_item(inter_agent_msg(
"triggered task",
/*trigger_turn*/ true,
)),
response_item(assistant_msg("a2")),
];
let truncated = truncate_rollout_to_last_n_fork_turns(rollout.clone(), /*n_from_end*/ 10);
assert_eq!(
serde_json::to_value(&truncated).unwrap(),
serde_json::to_value(&rollout).unwrap()
);
}
@@ -92,7 +92,6 @@ pub(crate) fn record_collab_spawn_failure(
let fork_mode = match fork_mode {
None => "none",
Some(SpawnAgentForkMode::FullHistory) => "all",
Some(SpawnAgentForkMode::LastNTurns(_)) => "last_n",
};
let multi_agent_version = match multi_agent_version {
MultiAgentVersion::Disabled => "disabled",
@@ -664,8 +664,7 @@ fn spawn_agent_common_properties_v2(agent_type_description: &str) -> BTreeMap<St
(
"fork_turns".to_string(),
JsonSchema::string(Some(
"Optional number of turns to fork. Defaults to `all`. Use `none`, `all`, or a positive integer string such as `3` to fork only the most recent turns."
.to_string(),
"Parent history to inherit. Defaults to `all`; use `none` to start without parent history. Only `all` and `none` are supported.".to_string(),
)),
),
(
@@ -924,7 +924,7 @@ async fn multi_agent_v2_full_history_fork_inherits_root_service_tier() {
}
#[tokio::test]
async fn multi_agent_v2_spawn_partial_fork_turns_allows_agent_type_override() {
async fn multi_agent_v2_spawn_without_history_allows_agent_type_override() {
let (mut session, mut turn) = make_session_and_context().await;
let role_name = install_role_with_model_override(&mut turn).await;
let manager = thread_manager();
@@ -951,17 +951,17 @@ async fn multi_agent_v2_spawn_partial_fork_turns_allows_agent_type_override() {
"spawn_agent",
function_payload(json!({
"message": "inspect this repo",
"task_name": "partial_fork",
"task_name": "fresh_agent",
"agent_type": role_name,
"fork_turns": "1"
"fork_turns": "none"
})),
))
.await
.expect("partial fork should allow agent_type overrides");
.expect("fresh agent should allow agent_type overrides");
let (content, _) = expect_text_output(output);
let result: serde_json::Value =
serde_json::from_str(&content).expect("spawn_agent result should be json");
assert_eq!(result["task_name"], "/root/partial_fork");
assert_eq!(result["task_name"], "/root/fresh_agent");
let agent_id = manager
.captured_ops()
.into_iter()
@@ -1254,8 +1254,10 @@ async fn multi_agent_v2_spawn_rejects_legacy_fork_context() {
);
}
#[test_case::test_case("banana"; "invalid string")]
#[test_case::test_case("0"; "zero turns")]
#[tokio::test]
async fn multi_agent_v2_spawn_rejects_invalid_fork_turns_string() {
async fn multi_agent_v2_spawn_rejects_invalid_fork_turns_string(fork_turns: &str) {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
@@ -1279,7 +1281,7 @@ async fn multi_agent_v2_spawn_rejects_invalid_fork_turns_string() {
function_payload(json!({
"message": "inspect this repo",
"task_name": "worker",
"fork_turns": "banana"
"fork_turns": fork_turns
})),
))
.await
@@ -1288,49 +1290,7 @@ async fn multi_agent_v2_spawn_rejects_invalid_fork_turns_string() {
assert_eq!(
err,
FunctionCallError::RespondToModel(
"fork_turns must be `none`, `all`, or a positive integer string".to_string()
)
);
}
#[tokio::test]
async fn multi_agent_v2_spawn_rejects_zero_fork_turns() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread(StartThreadOptions::new((*turn.config).clone()))
.await
.expect("root thread should start");
set_agent_control(&mut session, manager.agent_control());
session.thread_id = root.thread_id;
let mut config = (*turn.config).clone();
config
.features
.enable(Feature::MultiAgentV2)
.expect("test config should allow feature update");
set_turn_config(&mut turn, config);
let err = SpawnAgentHandlerV2::default()
.handle(invocation(
Arc::new(session),
Arc::new(turn),
"spawn_agent",
function_payload(json!({
"message": "inspect this repo",
"task_name": "worker",
"fork_turns": "0"
})),
))
.await
.err()
.expect("zero turn count should be rejected");
assert_eq!(
err,
FunctionCallError::RespondToModel(
"fork_turns must be `none`, `all`, or a positive integer string".to_string()
)
FunctionCallError::RespondToModel("fork_turns must be `none` or `all`".to_string())
);
}
@@ -20,6 +20,7 @@ use codex_prompts::ResolvedModelMessages;
use codex_protocol::ThreadId;
use codex_protocol::protocol::MultiAgentVersion;
use codex_tools::ToolSpec;
use std::num::NonZeroUsize;
#[derive(Default)]
pub(crate) struct Handler {
@@ -288,22 +289,14 @@ impl SpawnAgentArgs {
if fork_turns.eq_ignore_ascii_case("none") {
return Ok(None);
}
if fork_turns.eq_ignore_ascii_case("all") {
// Accept legacy turn counts without limiting the inherited history.
if fork_turns.eq_ignore_ascii_case("all") || fork_turns.parse::<NonZeroUsize>().is_ok() {
return Ok(Some(SpawnAgentForkMode::FullHistory));
}
let last_n_turns = fork_turns.parse::<usize>().map_err(|_| {
FunctionCallError::RespondToModel(
"fork_turns must be `none`, `all`, or a positive integer string".to_string(),
)
})?;
if last_n_turns == 0 {
return Err(FunctionCallError::RespondToModel(
"fork_turns must be `none`, `all`, or a positive integer string".to_string(),
));
}
Ok(Some(SpawnAgentForkMode::LastNTurns(last_n_turns)))
Err(FunctionCallError::RespondToModel(
"fork_turns must be `none` or `all`".to_string(),
))
}
}
@@ -28,7 +28,7 @@ Scenario: Real paginated mid-turn compaction and cold resume from the bounded su
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...fork_turns` to `"none"` or a positive integer string. [hash=db3545796947884f]
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...uctions; when doing so, set `fork_turns` to `"none"`. [hash=18ccf7e93ecabab9]
03:message/developer:
<MULTI_AGENT_MODE>
04:message/user:
@@ -58,7 +58,7 @@ Scenario: Real paginated mid-turn compaction and cold resume from the bounded su
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...fork_turns` to `"none"` or a positive integer string. [hash=db3545796947884f]
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...uctions; when doing so, set `fork_turns` to `"none"`. [hash=18ccf7e93ecabab9]
03:message/developer:
<MULTI_AGENT_MODE>
04:message/user:
@@ -92,7 +92,7 @@ Scenario: Real paginated mid-turn compaction and cold resume from the bounded su
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...fork_turns` to `"none"` or a positive integer string. [hash=db3545796947884f]
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...uctions; when doing so, set `fork_turns` to `"none"`. [hash=18ccf7e93ecabab9]
03:message/developer:
<MULTI_AGENT_MODE>
04:message/user:
@@ -27,7 +27,7 @@ Scenario: An active agent posts a shared decision and fetches the text without a
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...fork_turns` to `"none"` or a positive integer string. [hash=db3545796947884f]
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...uctions; when doing so, set `fork_turns` to `"none"`. [hash=18ccf7e93ecabab9]
03:message/developer:
<MULTI_AGENT_MODE>
04:message/user:
@@ -33,7 +33,7 @@ Scenario: Remote board tools and active-turn notifications share the existing se
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...fork_turns` to `"none"` or a positive integer string. [hash=db3545796947884f]
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...uctions; when doing so, set `fork_turns` to `"none"`. [hash=18ccf7e93ecabab9]
05:message/developer:
<MULTI_AGENT_MODE>
06:message/user:
@@ -6,7 +6,7 @@ Scenario: Astra starts in one workspace, switches for the next step of the activ
## Window 1
-- request 1 (turn) --
00:additional_tools/developer (3; hash=6517f5ceb3554929):
00:additional_tools/developer (3; hash=62160ed28e692c35):
- namespace/functions; hash=4261ffb241688b3d
- custom/exec
- function/wait
@@ -14,7 +14,7 @@ Scenario: Astra starts in one workspace, switches for the next step of the activ
- function/request_user_input_async
- namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
- function/sleep
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=b263e3c84ad07c37
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
- function/followup_task
- function/interrupt_agent
- function/list_agents
@@ -90,7 +90,7 @@ Scenario: Astra starts in one workspace, switches for the next step of the activ
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...ne"` or a positive integer string.</multi_agent_role> [hash=c7f52e90688299ce]
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d... so, set `fork_turns` to `"none"`.</multi_agent_role> [hash=d5928243dc643bcc]
04:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
05:message/user:
@@ -7,7 +7,7 @@ Scenario: Astra uses the catalog question schema to ask who a launch update is f
## Window 1
-- request 1 (request) --
00:additional_tools/developer (3; hash=2b6cdd8f97e8814b):
00:additional_tools/developer (3; hash=f181967456befa67):
- namespace/functions; hash=7e611b8ddd5621cf
- custom/exec
- function/wait
@@ -15,7 +15,7 @@ Scenario: Astra uses the catalog question schema to ask who a launch update is f
- function/request_user_input_async
- namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
- function/sleep
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=b263e3c84ad07c37
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
- function/followup_task
- function/interrupt_agent
- function/list_agents
@@ -6,7 +6,7 @@ Scenario: Astra receives host and handler timings on completed and failed code-m
## Window 1
-- request 1 (turn) --
00:additional_tools/developer (3; hash=2c5a5b48dbcfce9b):
00:additional_tools/developer (3; hash=bf2e30605ff5dc17):
- namespace/functions; hash=fada4a476a3754dd
- custom/exec
- function/wait
@@ -14,7 +14,7 @@ Scenario: Astra receives host and handler timings on completed and failed code-m
- function/request_user_input_async
- namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
- function/sleep
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=b263e3c84ad07c37
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
- function/followup_task
- function/interrupt_agent
- function/list_agents
@@ -77,7 +77,7 @@ Scenario: Astra receives host and handler timings on completed and failed code-m
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...ne"` or a positive integer string.</multi_agent_role> [hash=c7f52e90688299ce]
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d... so, set `fork_turns` to `"none"`.</multi_agent_role> [hash=d5928243dc643bcc]
04:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
05:message/user:
@@ -16,7 +16,7 @@ Settings:
text: {"verbosity":"low"}
tool_choice: "auto"
-- request 1 (request) --
00:additional_tools/developer (3; hash=91d402a1b9d55c34):
00:additional_tools/developer (3; hash=5e8370540461db70):
- namespace/functions; hash=7d26e086c1dbba28
- custom/exec
- function/wait
@@ -24,7 +24,7 @@ Settings:
- function/request_user_input_async
- namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
- function/sleep
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=b263e3c84ad07c37
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
- function/followup_task
- function/interrupt_agent
- function/list_agents
@@ -109,7 +109,7 @@ Settings:
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...ne"` or a positive integer string.</multi_agent_role> [hash=c7f52e90688299ce]
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d... so, set `fork_turns` to `"none"`.</multi_agent_role> [hash=d5928243dc643bcc]
04:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
05:message/user:
@@ -6,7 +6,7 @@ Scenario: Astra receives new user input after its unfinished response is interru
## Window 1
-- request 1 (request) --
00:additional_tools/developer (3; hash=2c5a5b48dbcfce9b):
00:additional_tools/developer (3; hash=bf2e30605ff5dc17):
- namespace/functions; hash=fada4a476a3754dd
- custom/exec
- function/wait
@@ -14,7 +14,7 @@ Scenario: Astra receives new user input after its unfinished response is interru
- function/request_user_input_async
- namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
- function/sleep
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=b263e3c84ad07c37
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
- function/followup_task
- function/interrupt_agent
- function/list_agents
@@ -6,7 +6,7 @@ Scenario: Astra continues after new user input yields a running code cell, then
## Window 1
-- request 1 (turn) --
00:additional_tools/developer (3; hash=42b12a95deaea38e):
00:additional_tools/developer (3; hash=69c8f596545517aa):
- namespace/functions; hash=13a36c9bc9ada856
- custom/exec
- function/wait
@@ -14,7 +14,7 @@ Scenario: Astra continues after new user input yields a running code cell, then
- function/request_user_input_async
- namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
- function/sleep
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=b263e3c84ad07c37
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
- function/followup_task
- function/interrupt_agent
- function/list_agents
@@ -90,7 +90,7 @@ Scenario: Astra continues after new user input yields a running code cell, then
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...ne"` or a positive integer string.</multi_agent_role> [hash=c7f52e90688299ce]
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d... so, set `fork_turns` to `"none"`.</multi_agent_role> [hash=d5928243dc643bcc]
04:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
05:message/user:
@@ -16,7 +16,7 @@ Settings:
text: {"verbosity":"low"}
tool_choice: "auto"
-- request 1 (turn) --
00:additional_tools/developer (3; hash=2c5a5b48dbcfce9b):
00:additional_tools/developer (3; hash=bf2e30605ff5dc17):
- namespace/functions; hash=fada4a476a3754dd
- custom/exec
- function/wait
@@ -24,7 +24,7 @@ Settings:
- function/request_user_input_async
- namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
- function/sleep
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=b263e3c84ad07c37
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
- function/followup_task
- function/interrupt_agent
- function/list_agents
@@ -112,7 +112,7 @@ Settings:
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...ne"` or a positive integer string.</multi_agent_role> [hash=c7f52e90688299ce]
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d... so, set `fork_turns` to `"none"`.</multi_agent_role> [hash=d5928243dc643bcc]
04:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
05:message/user[2]:
@@ -187,7 +187,7 @@ Settings:
## Window 2 (after request 3: input diverged at item 02)
Settings: same as window 1
-- request 4 (turn) --
00:additional_tools/developer (3; hash=2c5a5b48dbcfce9b):
00:additional_tools/developer (3; hash=bf2e30605ff5dc17):
- namespace/functions; hash=fada4a476a3754dd
- custom/exec
- function/wait
@@ -195,7 +195,7 @@ Settings: same as window 1
- function/request_user_input_async
- namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
- function/sleep
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=b263e3c84ad07c37
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
- function/followup_task
- function/interrupt_agent
- function/list_agents
@@ -285,7 +285,7 @@ Settings: same as window 1
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...ne"` or a positive integer string.</multi_agent_role> [hash=c7f52e90688299ce]
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d... so, set `fork_turns` to `"none"`.</multi_agent_role> [hash=d5928243dc643bcc]
05:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
06:message/user[2]:
@@ -16,7 +16,7 @@ Settings:
text: {"verbosity":"low"}
tool_choice: "auto"
-- request 1 (turn) --
00:additional_tools/developer (3; hash=2c5a5b48dbcfce9b):
00:additional_tools/developer (3; hash=bf2e30605ff5dc17):
- namespace/functions; hash=fada4a476a3754dd
- custom/exec
- function/wait
@@ -24,7 +24,7 @@ Settings:
- function/request_user_input_async
- namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
- function/sleep
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=b263e3c84ad07c37
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
- function/followup_task
- function/interrupt_agent
- function/list_agents
@@ -87,7 +87,7 @@ Settings:
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...ne"` or a positive integer string.</multi_agent_role> [hash=c7f52e90688299ce]
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d... so, set `fork_turns` to `"none"`.</multi_agent_role> [hash=d5928243dc643bcc]
04:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
05:message/user:
@@ -16,7 +16,7 @@ Settings:
text: {"verbosity":"low"}
tool_choice: "auto"
-- request 1 (turn) --
00:additional_tools/developer (3; hash=2c5a5b48dbcfce9b):
00:additional_tools/developer (3; hash=bf2e30605ff5dc17):
- namespace/functions; hash=fada4a476a3754dd
- custom/exec
- function/wait
@@ -24,7 +24,7 @@ Settings:
- function/request_user_input_async
- namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
- function/sleep
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=b263e3c84ad07c37
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
- function/followup_task
- function/interrupt_agent
- function/list_agents
@@ -87,7 +87,7 @@ Settings:
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...ne"` or a positive integer string.</multi_agent_role> [hash=c7f52e90688299ce]
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d... so, set `fork_turns` to `"none"`.</multi_agent_role> [hash=d5928243dc643bcc]
04:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
05:message/user:
@@ -15,14 +15,14 @@ Settings:
stream: true
text: {"verbosity":"low"}
tool_choice: "auto"
tools (7; hash=7a45a45148e7ea6a):
tools (7; hash=7916dd640b46ec6e):
- custom/exec: Catalog exec instructions. Default yield: 1234 ms. `image(imageUrlOrItem: string | { image_url: s...; hash=a49b91fd82908e18
- function/wait: Catalog wait instructions: resume the yielded cell.; args=[cell_id, max_tokens, terminate, yield_time_ms]; hash=67bc0f04da3505b3
- function/request_user_input: Request user input for one to three short questions and wait for the response. This tool is only ...; args=[questions]; hash=64a729b25c424fc6
- function/request_user_input_async: Ask the user one or more questions during ongoing work. Use this tool only to request missing inf...; args=[questions]; hash=d6d74f33db330162
- namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
- function/sleep
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=b263e3c84ad07c37
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
- function/followup_task
- function/interrupt_agent
- function/list_agents
@@ -87,7 +87,7 @@ Settings:
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...ne"` or a positive integer string.</multi_agent_role> [hash=c7f52e90688299ce]
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d... so, set `fork_turns` to `"none"`.</multi_agent_role> [hash=d5928243dc643bcc]
03:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
04:message/user:
@@ -6,7 +6,7 @@ Scenario: Settlement helpers deliver fulfilled and rejected records with input i
## Window 1
-- request 1 (turn) --
00:additional_tools/developer (3; hash=2c5a5b48dbcfce9b):
00:additional_tools/developer (3; hash=bf2e30605ff5dc17):
- namespace/functions; hash=fada4a476a3754dd
- custom/exec
- function/wait
@@ -14,7 +14,7 @@ Scenario: Settlement helpers deliver fulfilled and rejected records with input i
- function/request_user_input_async
- namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
- function/sleep
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=b263e3c84ad07c37
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
- function/followup_task
- function/interrupt_agent
- function/list_agents
@@ -16,7 +16,7 @@ Settings:
text: {"verbosity":"low"}
tool_choice: "auto"
-- request 1 (turn; Before /compact) --
00:additional_tools/developer (3; hash=2c5a5b48dbcfce9b):
00:additional_tools/developer (3; hash=bf2e30605ff5dc17):
- namespace/functions; hash=fada4a476a3754dd
- custom/exec
- function/wait
@@ -24,7 +24,7 @@ Settings:
- function/request_user_input_async
- namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
- function/sleep
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=b263e3c84ad07c37
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
- function/followup_task
- function/interrupt_agent
- function/list_agents
@@ -87,7 +87,7 @@ Settings:
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...ne"` or a positive integer string.</multi_agent_role> [hash=c7f52e90688299ce]
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d... so, set `fork_turns` to `"none"`.</multi_agent_role> [hash=d5928243dc643bcc]
04:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
05:message/user:
@@ -146,7 +146,7 @@ Settings: unavailable (items only)
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...ne"` or a positive integer string.</multi_agent_role> [hash=c7f52e90688299ce]
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d... so, set `fork_turns` to `"none"`.</multi_agent_role> [hash=d5928243dc643bcc]
02:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
03:message/user:
@@ -164,7 +164,7 @@ Settings: unavailable (items only)
## Window 3 (after request 3: input diverged at item 00)
Settings: same as window 1
-- request 4 (turn; First turn after /compact) --
00:additional_tools/developer (3; hash=2c5a5b48dbcfce9b):
00:additional_tools/developer (3; hash=bf2e30605ff5dc17):
- namespace/functions; hash=fada4a476a3754dd
- custom/exec
- function/wait
@@ -172,7 +172,7 @@ Settings: same as window 1
- function/request_user_input_async
- namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
- function/sleep
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=b263e3c84ad07c37
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
- function/followup_task
- function/interrupt_agent
- function/list_agents
@@ -235,7 +235,7 @@ Settings: same as window 1
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...ne"` or a positive integer string.</multi_agent_role> [hash=c7f52e90688299ce]
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d... so, set `fork_turns` to `"none"`.</multi_agent_role> [hash=d5928243dc643bcc]
04:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
05:message/user:
@@ -28,7 +28,7 @@ Scenario: A root uses Code Mode to send a confirmation and receives a real user
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...fork_turns` to `"none"` or a positive integer string. [hash=db3545796947884f]
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...uctions; when doing so, set `fork_turns` to `"none"`. [hash=18ccf7e93ecabab9]
03:message/developer:
<MULTI_AGENT_MODE>
04:message/user:
@@ -76,7 +76,7 @@ Scenario: A root uses Code Mode to send a confirmation and receives a real user
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...fork_turns` to `"none"` or a positive integer string. [hash=db3545796947884f]
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...uctions; when doing so, set `fork_turns` to `"none"`. [hash=18ccf7e93ecabab9]
05:message/developer:
<MULTI_AGENT_MODE>
06:agent_message
@@ -15,8 +15,8 @@ Settings:
stream: true
text: {"verbosity":"low"}
tool_choice: "auto"
tools (4; hash=a9bf6ab2694395ea):
- custom/exec: Run JavaScript code to orchestrate/compose tool calls - Evaluates the provided JavaScript code in...; hash=046e4cb99a803db9
tools (4; hash=7707adf4d5cde816):
- custom/exec: Run JavaScript code to orchestrate/compose tool calls - Evaluates the provided JavaScript code in...; hash=66e0c24dd8b9010d
- function/wait: Waits on a yielded `exec` cell and returns new output or completion. - Use `wait` only after `exe...; args=[cell_id, max_tokens, terminate, yield_time_ms]; hash=6692d8f6c55be489
- function/request_user_input: Request user input for one to three short questions and wait for the response. This tool is only ...; args=[questions]; hash=64a729b25c424fc6
- web_search; hash=e214d4db1bc49e89
@@ -49,8 +49,8 @@ Settings:
## Window 2 (after request 2)
Settings: relative to window 1
model: "prefixes-cleared"
tools (4; hash=5aa602f8d0ed5702):
~ custom/exec: Run JavaScript code to orchestrate/compose tool calls - Evaluates the provided JavaScript code in...; hash=b1c19da7942f5c47
tools (4; hash=d878b195101dccae):
~ custom/exec: Run JavaScript code to orchestrate/compose tool calls - Evaluates the provided JavaScript code in...; hash=881b443f5068d3fb
-- request 3 (turn) --
00:message/developer:
<MODEL_INSTRUCTIONS>
@@ -15,14 +15,14 @@ Settings:
stream: true
text: {"verbosity":"low"}
tool_choice: "auto"
tools (7; hash=221a8b190f48eeef):
tools (7; hash=dac6a853a6177003):
- custom/exec: Run JavaScript code to orchestrate/compose tool calls - Evaluates the provided JavaScript code in...; hash=9ad48dbb5eac1f9b
- function/wait: Waits on a yielded `exec` cell and returns new output or completion. - Use `wait` only after `exe...; args=[cell_id, max_tokens, terminate, yield_time_ms]; hash=6692d8f6c55be489
- function/request_user_input: Request user input for one to three short questions and wait for the response. This tool is only ...; args=[questions]; hash=64a729b25c424fc6
- function/request_user_input_async: Ask the user one or more questions during ongoing work. Use this tool only to request missing inf...; args=[questions]; hash=d6d74f33db330162
- namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
- function/sleep
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=b263e3c84ad07c37
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
- function/followup_task
- function/interrupt_agent
- function/list_agents
@@ -87,7 +87,7 @@ Settings:
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...ne"` or a positive integer string.</multi_agent_role> [hash=c7f52e90688299ce]
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d... so, set `fork_turns` to `"none"`.</multi_agent_role> [hash=d5928243dc643bcc]
03:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
04:message/user:
@@ -16,7 +16,7 @@ Settings:
text: {"verbosity":"low"}
tool_choice: "auto"
-- request 1 (turn) --
00:additional_tools/developer (3; hash=cc5df79824bafc21):
00:additional_tools/developer (3; hash=2b84850bc279e5ed):
- namespace/functions; hash=fada4a476a3754dd
- custom/exec
- function/wait
@@ -24,7 +24,7 @@ Settings:
- function/request_user_input_async
- namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
- function/sleep
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=170baa4086bfe3cb
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=7e156cf57f85af87
- function/followup_task
- function/interrupt_agent
- function/list_agents
@@ -87,7 +87,7 @@ Settings:
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...ne"` or a positive integer string.</multi_agent_role> [hash=c7f52e90688299ce]
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d... so, set `fork_turns` to `"none"`.</multi_agent_role> [hash=d5928243dc643bcc]
04:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
05:message/user:
@@ -15,12 +15,12 @@ Settings:
stream: true
text: {"verbosity":"low"}
tool_choice: "auto"
tools (6; hash=3b8fa9a9ca9788e3):
tools (6; hash=dde875513b2c7cff):
- function/exec_command: Runs a command in a PTY, returning output or a session ID for ongoing interaction.; args=[cmd, justification, login, max_output_tokens, prefix_rule, sandbox_permissions, shell, tty, workdir, yield_time_ms]; hash=c04108e51a8548c5
- function/write_stdin: Writes characters to an existing unified exec session and returns recent output.; args=[chars, max_output_tokens, session_id, yield_time_ms]; hash=7fb62d8a3e2df4e2
- function/request_user_input: Request user input for one to three short questions and wait for the response. This tool is only ...; args=[questions]; hash=64a729b25c424fc6
- function/view_image: View a local image file from the filesystem when visual inspection is needed. Use this for images...; args=[detail, path]; hash=3b766d5bdec5d50e
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=3b8ac3e34b9c55bb
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=3cbf715dc154e1b7
- function/followup_task
- function/interrupt_agent
- function/list_agents
@@ -16,7 +16,7 @@ Settings:
text: {"verbosity":"low"}
tool_choice: "auto"
-- request 1 (turn) --
00:additional_tools/developer (3; hash=b6ebf543280ec271):
00:additional_tools/developer (3; hash=e80da11342c6e6dd):
- namespace/functions; hash=bd9e182b4235cdc1
- custom/exec
- function/wait
@@ -24,7 +24,7 @@ Settings:
- function/request_user_input_async
- namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
- function/sleep
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=b263e3c84ad07c37
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
- function/followup_task
- function/interrupt_agent
- function/list_agents
@@ -108,7 +108,7 @@ Settings:
There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d...ne"` or a positive integer string.</multi_agent_role> [hash=c7f52e90688299ce]
Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d... so, set `fork_turns` to `"none"`.</multi_agent_role> [hash=d5928243dc643bcc]
04:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
05:message/user:
@@ -24,7 +24,7 @@ Settings:
- function/request_user_input_async
- namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
- function/sleep
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=b263e3c84ad07c37
- namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
- function/followup_task
- function/interrupt_agent
- function/list_agents
@@ -1200,16 +1200,13 @@ async fn submit_turn_with_trigger(test: &TestCodex, prompt: &str, trigger: &str)
#[derive(Clone, Copy, Debug)]
enum GrandchildParentContext {
FullHistory,
LastTurn,
NoHistory,
Compacted,
}
#[test_case(GrandchildParentContext::FullHistory, ThreadHistoryMode::Legacy; "legacy full history")]
#[test_case(GrandchildParentContext::LastTurn, ThreadHistoryMode::Legacy; "legacy last turn")]
#[test_case(GrandchildParentContext::NoHistory, ThreadHistoryMode::Legacy; "legacy no history")]
#[test_case(GrandchildParentContext::FullHistory, ThreadHistoryMode::Paginated; "paginated full history")]
#[test_case(GrandchildParentContext::LastTurn, ThreadHistoryMode::Paginated; "paginated last turn")]
#[test_case(GrandchildParentContext::NoHistory, ThreadHistoryMode::Paginated; "paginated no history")]
#[test_case(GrandchildParentContext::Compacted, ThreadHistoryMode::Legacy; "legacy full history after compaction")]
#[test_case(GrandchildParentContext::Compacted, ThreadHistoryMode::Paginated; "paginated full history after compaction")]
@@ -1233,7 +1230,6 @@ async fn grandchild_full_fork_preserves_context_baseline(
let server = start_mock_server().await;
let (parent_fork_turns, compact_parent) = match parent_context {
GrandchildParentContext::FullHistory => ("all", false),
GrandchildParentContext::LastTurn => ("1", false),
GrandchildParentContext::NoHistory => ("none", false),
GrandchildParentContext::Compacted => ("all", true),
};
@@ -1457,7 +1453,7 @@ async fn grandchild_full_fork_preserves_context_baseline(
#[derive(Clone, Copy)]
enum FullHistoryV2ModelSelection {
ConfiguredDefault,
ExplicitOverride,
ExplicitOverride(&'static str),
WorldStateIdentity,
CurrentTimeReminders,
MultiAgentModeInstructions,
@@ -1465,7 +1461,9 @@ enum FullHistoryV2ModelSelection {
}
#[test_case(FullHistoryV2ModelSelection::ConfiguredDefault; "configured default with omitted fork_turns")]
#[test_case(FullHistoryV2ModelSelection::ExplicitOverride; "explicit override with fork_turns all")]
#[test_case(FullHistoryV2ModelSelection::ExplicitOverride("all"); "explicit override with fork_turns all")]
#[test_case(FullHistoryV2ModelSelection::ExplicitOverride("1"); "legacy one turn forks full history with explicit override")]
#[test_case(FullHistoryV2ModelSelection::ExplicitOverride("3"); "legacy three turns forks full history with explicit override")]
#[test_case(FullHistoryV2ModelSelection::WorldStateIdentity; "world state appends context window when agent identity changes")]
#[test_case(FullHistoryV2ModelSelection::CurrentTimeReminders; "full fork drops inherited current-time reminders")]
#[test_case(FullHistoryV2ModelSelection::MultiAgentModeInstructions; "full fork drops inherited multi-agent mode instructions")]
@@ -1500,11 +1498,11 @@ async fn spawned_full_history_v2_child_uses_model_precedence_without_dropping_co
V2_DEFAULT_MODEL,
V2_DEFAULT_REASONING_EFFORT,
),
FullHistoryV2ModelSelection::ExplicitOverride => (
FullHistoryV2ModelSelection::ExplicitOverride(fork_turns) => (
json!({
"message": CHILD_PROMPT,
"task_name": "worker",
"fork_turns": "all",
"fork_turns": fork_turns,
"model": V2_REQUESTED_MODEL,
"reasoning_effort": V2_REQUESTED_REASONING_EFFORT,
}),
@@ -1700,7 +1698,7 @@ async fn spawned_full_history_v2_child_uses_model_precedence_without_dropping_co
builder =
builder.with_external_time_provider(std::sync::Arc::new(FailFirstClockRead::default()));
}
let test = builder.build(&server).await?;
let test = builder.build_with_auto_env(&server).await?;
if matches!(selection, FullHistoryV2ModelSelection::WorldStateIdentity) {
test.codex.submit(Op::Compact).await?;
wait_for_event(&test.codex, |event| {
@@ -5,7 +5,7 @@ use crate::without_update_plan_instructions;
use codex_context_fragments::ContextualUserFragment;
use codex_protocol::models::ContentItemKind;
const DEFAULT_MULTI_AGENT_V2_MODEL_OVERRIDE_USAGE_HINT_TEXT: &str = "Full-history forks (`fork_turns` omitted or `\"all\"`) inherit the parent model and reasoning effort and do not accept overrides. Only set `model` or `reasoning_effort` when explicitly requested by the user, applicable `AGENTS.md` instructions, or skill instructions; when doing so, set `fork_turns` to `\"none\"` or a positive integer string.";
const DEFAULT_MULTI_AGENT_V2_MODEL_OVERRIDE_USAGE_HINT_TEXT: &str = "Full-history forks (`fork_turns` omitted or `\"all\"`) inherit the parent model and reasoning effort and do not accept overrides. Only set `model` or `reasoning_effort` when explicitly requested by the user, applicable `AGENTS.md` instructions, or skill instructions; when doing so, set `fork_turns` to `\"none\"`.";
const DEFAULT_MULTI_AGENT_V2_WAIT_AGENT_USAGE_HINT_TEXT: &str =
"When calling `wait_agent`, prefer longer waits (minutes) to avoid busy polling.";
const DEFAULT_MULTI_AGENT_V2_SHARED_USAGE_HINT_TEXT: &str = r#"Note that collaboration tools cannot be called from inside `functions.exec`. Call `spawn_agent`, `send_message`, `followup_task`, `wait_agent`, `interrupt_agent`, and `list_agents` only as direct tool calls using the recipient shown in their tool definitions, such as `to=functions.collaboration.spawn_agent`, since they are intentionally absent from the `functions.exec` `tools.*` namespace. Available tools in `functions.exec` are explicitly described with a `tools` namespace in the developer message.