mirror of
https://github.com/openai/codex.git
synced 2026-10-30 19:18:49 +00:00
V2
This commit is contained in:
+1
-2
@@ -13,7 +13,7 @@ The focus is on:
|
||||
|
||||
## 1. Mental Model
|
||||
|
||||
- A **Codex session** remains the top‑level unit, with one running `SessionTask` (typically `RegularTask`) at a time.
|
||||
- A **Codex session** remains the top‑level unit, with one running `SessionTask` at a time.
|
||||
- Within a session we introduce a **Collaboration Graph**: a set of logical agents that can exchange messages and call tools, orchestrated entirely within the same session.
|
||||
- The **main agent** corresponds to the existing single agent Codex exposes today. It gets the user’s prompt and controls collaboration via the new `collaboration.*` tools.
|
||||
- **Child agents** are lightweight logical agents:
|
||||
@@ -624,7 +624,6 @@ Add collaboration‑related helpers on `Session`:
|
||||
|
||||
The single‑agent path (today’s behavior) remains:
|
||||
|
||||
- `RegularTask::run` → `run_task` loop.
|
||||
- `run_task` builds `prompt` from *session* history and calls `run_turn`.
|
||||
|
||||
For collaboration, we introduce a **CollaborationTask**:
|
||||
|
||||
@@ -18,6 +18,18 @@ You are Codex, based on GPT-5. You are running as a coding agent in the Codex CL
|
||||
- While you are working, you might notice unexpected changes that you didn't make. If this happens, STOP IMMEDIATELY and ask the user how they would like to proceed.
|
||||
- **NEVER** use destructive commands like `git reset --hard` or `git checkout --` unless specifically requested or approved by the user.
|
||||
|
||||
## Collaboration
|
||||
You can spawn and coordinate child agents using these tools (only on this model):
|
||||
- `collaboration_init_agent`: create a direct child with optional instructions/model; depth/agent limits apply. No initial message is sent at this point.
|
||||
- `collaboration_send`: send a user-message to your direct children by id.
|
||||
- `collaboration_wait`: run children for up to `max_duration` tokens and surface their last message/status.
|
||||
- `collaboration_get_state`: see all agents, parents, statuses, and last messages.
|
||||
- `collaboration_close`: close specific children (and their descendants).
|
||||
|
||||
Children inherit your instructions unless overridden. Always `wait` after `send` to drive children forward; keep communication concise and include the expected output format. Use `get_state` if unsure about child ids/status.
|
||||
|
||||
A `collaboration_send` is always necessary after a `collaboration_init_agent` to start the child agent working.
|
||||
|
||||
## Plan tool
|
||||
|
||||
When using the planning tool:
|
||||
|
||||
@@ -398,10 +398,6 @@ impl SessionConfiguration {
|
||||
self.user_instructions.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn approval_policy(&self) -> AskForApproval {
|
||||
self.approval_policy
|
||||
}
|
||||
|
||||
pub(crate) fn sandbox_policy(&self) -> SandboxPolicy {
|
||||
self.sandbox_policy.clone()
|
||||
}
|
||||
|
||||
@@ -196,10 +196,6 @@ pub fn find_family_for_model(slug: &str) -> Option<ModelFamily> {
|
||||
base_instructions: GPT_5_1_CODEX_MAX_INSTRUCTIONS.to_string(),
|
||||
apply_patch_tool_type: Some(ApplyPatchToolType::Freeform),
|
||||
experimental_supported_tools: vec![
|
||||
"grep_files".to_string(),
|
||||
"list_dir".to_string(),
|
||||
"read_file".to_string(),
|
||||
"test_sync_tool".to_string(),
|
||||
"collaboration".to_string(),
|
||||
],
|
||||
shell_type: ConfigShellToolType::ShellCommand,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
mod collaboration;
|
||||
mod compact;
|
||||
mod ghost_snapshot;
|
||||
mod regular;
|
||||
mod review;
|
||||
mod undo;
|
||||
mod user_shell;
|
||||
@@ -30,6 +29,7 @@ use crate::state::TaskKind;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
|
||||
pub(crate) use collaboration::CollaborationSupervisor;
|
||||
pub(crate) use collaboration::CollaborationTask;
|
||||
pub(crate) use compact::CompactTask;
|
||||
pub(crate) use ghost_snapshot::GhostSnapshotTask;
|
||||
pub(crate) use review::ReviewTask;
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::codex::TurnContext;
|
||||
use crate::codex::run_task;
|
||||
use crate::state::TaskKind;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
|
||||
use super::SessionTask;
|
||||
use super::SessionTaskContext;
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub(crate) struct RegularTask;
|
||||
|
||||
#[async_trait]
|
||||
impl SessionTask for RegularTask {
|
||||
fn kind(&self) -> TaskKind {
|
||||
TaskKind::Regular
|
||||
}
|
||||
|
||||
async fn run(
|
||||
self: Arc<Self>,
|
||||
session: Arc<SessionTaskContext>,
|
||||
ctx: Arc<TurnContext>,
|
||||
input: Vec<UserInput>,
|
||||
cancellation_token: CancellationToken,
|
||||
) -> Option<String> {
|
||||
let sess = session.clone_session();
|
||||
run_task(sess, ctx, input, cancellation_token).await
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use serde_json::json;
|
||||
use std::path::PathBuf;
|
||||
use tracing::info;
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub(crate) struct ExtraMetadata(pub HashMap<String, serde_json::Value>);
|
||||
@@ -235,7 +236,7 @@ fn build_agent_system_message(
|
||||
|
||||
ResponseItem::Message {
|
||||
id: None,
|
||||
role: "system".to_string(),
|
||||
role: "user".to_string(),
|
||||
content: vec![ContentItem::InputText {
|
||||
text: lines.join("\n"),
|
||||
}],
|
||||
@@ -269,21 +270,6 @@ fn build_history_for_child(
|
||||
history
|
||||
}
|
||||
|
||||
async fn record_session_summary(
|
||||
session: &crate::codex::Session,
|
||||
turn: &crate::codex::TurnContext,
|
||||
text: String,
|
||||
) {
|
||||
let summary = ResponseItem::Message {
|
||||
id: None,
|
||||
role: "assistant".to_string(),
|
||||
content: vec![ContentItem::InputText { text }],
|
||||
};
|
||||
session
|
||||
.record_conversation_items(turn, std::slice::from_ref(&summary))
|
||||
.await;
|
||||
}
|
||||
|
||||
pub struct CollaborationHandler;
|
||||
|
||||
impl Default for CollaborationHandler {
|
||||
@@ -456,29 +442,18 @@ async fn handle_init_agent(
|
||||
extra: ExtraMetadata(extra),
|
||||
};
|
||||
|
||||
let content = format!(
|
||||
"Initialized agent {} at depth {} in cwd {}",
|
||||
assigned_id.0,
|
||||
depth,
|
||||
turn.cwd.display()
|
||||
);
|
||||
let content = format!("Initialized agent {} at depth {}", assigned_id.0, depth);
|
||||
|
||||
if !cfg!(test) {
|
||||
let supervisor = session.ensure_collaboration_supervisor().await;
|
||||
supervisor.kick_agent(assigned_id).await;
|
||||
}
|
||||
|
||||
record_session_summary(
|
||||
session.as_ref(),
|
||||
turn,
|
||||
format!(
|
||||
"Agent {} initialized (child of {})",
|
||||
assigned_id.0, parent.id.0
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
let output = CollaborationInitAgentOutput { content, metadata };
|
||||
info!(
|
||||
"collaboration_init_agent: assigned_id={}, parent_id={}, depth={}",
|
||||
assigned_id.0, parent.id.0, depth
|
||||
);
|
||||
serialize_function_output(&output, true)
|
||||
}
|
||||
|
||||
@@ -591,19 +566,14 @@ async fn handle_send(
|
||||
|
||||
let success = metadata.message_tool_call_success;
|
||||
|
||||
if success {
|
||||
record_session_summary(
|
||||
session.as_ref(),
|
||||
turn,
|
||||
format!(
|
||||
"Agent {} sent to {:?}: {}",
|
||||
sender_id.0, valid_recipients, input.message
|
||||
),
|
||||
)
|
||||
.await;
|
||||
if success && !valid_recipients.is_empty() {
|
||||
let supervisor = session.ensure_collaboration_supervisor().await;
|
||||
for recipient in &valid_recipients {
|
||||
supervisor.kick_agent(*recipient).await;
|
||||
for recipient in valid_recipients
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|recipient| recipient.0 != 0)
|
||||
{
|
||||
supervisor.kick_agent(recipient).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -710,9 +680,56 @@ async fn handle_wait(
|
||||
return serialize_function_output(&output, false);
|
||||
}
|
||||
|
||||
let mut agents_ran = Vec::new();
|
||||
let supervisor = session.ensure_collaboration_supervisor().await;
|
||||
let mut rx = supervisor.subscribe();
|
||||
|
||||
let snapshot = |agent: &AgentState, delta_tokens: i32, sub_id: Option<String>| {
|
||||
json!({
|
||||
"agent_idx": agent.id.0,
|
||||
"delta_tokens": delta_tokens,
|
||||
"status": status_label(&agent.status),
|
||||
"status_detail": status_detail(&agent.status),
|
||||
"last_agent_message": last_agent_message(&agent.status),
|
||||
"sub_id": sub_id,
|
||||
})
|
||||
};
|
||||
|
||||
let mut agents_ran = Vec::new();
|
||||
{
|
||||
// Short-circuit if all targets are already idle/terminal.
|
||||
let collab = session.collaboration_state().lock().await;
|
||||
if targets.iter().all(|id| {
|
||||
collab
|
||||
.agent(*id)
|
||||
.map(|agent| !matches!(agent.status, AgentLifecycleState::Running))
|
||||
.unwrap_or(false)
|
||||
}) {
|
||||
let mut extra = HashMap::new();
|
||||
extra.insert(
|
||||
"agents_ran".to_string(),
|
||||
json!(
|
||||
targets
|
||||
.iter()
|
||||
.filter_map(|id| collab.agent(*id))
|
||||
.map(|agent| snapshot(agent, 0, None))
|
||||
.collect::<Vec<_>>()
|
||||
),
|
||||
);
|
||||
extra.insert("token_budget_exhausted".to_string(), json!(false));
|
||||
let metadata = CollaborationWaitMetadata {
|
||||
message_tool_call_success: true,
|
||||
message_tool_call_error_should_penalize_model: false,
|
||||
is_wait_success_msg: Some(true),
|
||||
extra: ExtraMetadata(extra),
|
||||
};
|
||||
let output = CollaborationWaitOutput {
|
||||
content: "Finished waiting.".to_string(),
|
||||
metadata,
|
||||
};
|
||||
return serialize_function_output(&output, true);
|
||||
}
|
||||
}
|
||||
|
||||
supervisor
|
||||
.start_agents(targets.clone(), input.max_duration)
|
||||
.await
|
||||
@@ -731,29 +748,6 @@ async fn handle_wait(
|
||||
}
|
||||
remaining = remaining.saturating_sub(event.delta_tokens);
|
||||
|
||||
if let Some(message) = event.last_message.clone() {
|
||||
record_session_summary(
|
||||
session.as_ref(),
|
||||
turn,
|
||||
format!("Agent {} replied: {}", event.agent.0, message),
|
||||
)
|
||||
.await;
|
||||
} else if let AgentLifecycleState::Error { error } = &event.status {
|
||||
record_session_summary(
|
||||
session.as_ref(),
|
||||
turn,
|
||||
format!("Agent {} failed: {error}", event.agent.0),
|
||||
)
|
||||
.await;
|
||||
} else if !matches!(event.status, AgentLifecycleState::Error { .. }) {
|
||||
record_session_summary(
|
||||
session.as_ref(),
|
||||
turn,
|
||||
format!("Agent {} finished turn.", event.agent.0),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
let detail = status_detail(&event.status);
|
||||
agents_ran.push(json!({
|
||||
"agent_idx": event.agent.0,
|
||||
@@ -949,15 +943,6 @@ async fn handle_close(
|
||||
)
|
||||
.await;
|
||||
}
|
||||
record_session_summary(
|
||||
session.as_ref(),
|
||||
turn,
|
||||
format!(
|
||||
"Agent {} closed agents {:?}",
|
||||
caller_id.0, closed_for_summary
|
||||
),
|
||||
)
|
||||
.await;
|
||||
serialize_function_output(&output, true)
|
||||
}
|
||||
|
||||
@@ -1173,7 +1158,7 @@ mod tests {
|
||||
assert_eq!(items.len(), 2);
|
||||
assert!(matches!(
|
||||
items.first(),
|
||||
Some(ResponseItem::Message { role, .. }) if role == "system"
|
||||
Some(ResponseItem::Message { role, .. }) if role == "user"
|
||||
));
|
||||
assert!(matches!(
|
||||
items.get(1),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user