mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
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:
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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()
|
||||
},
|
||||
)
|
||||
|
||||
@@ -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(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -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:
|
||||
|
||||
+1
-1
@@ -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:
|
||||
|
||||
+1
-1
@@ -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:
|
||||
|
||||
+3
-3
@@ -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:
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+3
-3
@@ -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:
|
||||
|
||||
+3
-3
@@ -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:
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+3
-3
@@ -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:
|
||||
|
||||
+6
-6
@@ -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]:
|
||||
|
||||
+3
-3
@@ -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:
|
||||
|
||||
+3
-3
@@ -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:
|
||||
|
||||
+3
-3
@@ -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:
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+7
-7
@@ -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:
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+4
-4
@@ -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>
|
||||
|
||||
+3
-3
@@ -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:
|
||||
|
||||
+3
-3
@@ -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:
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+3
-3
@@ -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:
|
||||
|
||||
+1
-1
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user