mirror of
https://github.com/openai/codex.git
synced 2026-11-13 21:30:47 +00:00
agents
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
use super::definition::SubagentDefinition;
|
||||
use super::definition::SubagentSource;
|
||||
use include_dir::Dir;
|
||||
use include_dir::include_dir;
|
||||
|
||||
@@ -20,7 +21,10 @@ pub(crate) fn embedded_defs() -> Vec<SubagentDefinition> {
|
||||
{
|
||||
match file.contents_utf8() {
|
||||
Some(contents) => match SubagentDefinition::from_json_str(contents) {
|
||||
Ok(def) => defs.push(def),
|
||||
Ok(mut def) => {
|
||||
def.source = SubagentSource::EmbeddedDefault;
|
||||
defs.push(def)
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
"failed to parse embedded default subagent '{}': {}",
|
||||
|
||||
@@ -4,17 +4,22 @@ use serde::Deserialize;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum SubagentSource {
|
||||
#[default]
|
||||
EmbeddedDefault,
|
||||
User,
|
||||
Project,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct SubagentDefinition {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
/// Base instructions for this subagent.
|
||||
pub instructions: String,
|
||||
/// When not set, inherits the parent agent's tool set. When set to an
|
||||
/// empty list, no tools are available to the subagent.
|
||||
#[serde(default)]
|
||||
pub tools: Option<Vec<String>>, // None => inherit; Some(vec) => allow-list
|
||||
|
||||
//TODO: add allowed tools. we inherit the parent agent's tools for now.
|
||||
/// Optional structured output schema. When set, the subagent must return a
|
||||
/// single JSON value that validates against this schema. The schema will be
|
||||
/// embedded into the subagent's instructions so the model can adhere to it.
|
||||
@@ -30,6 +35,12 @@ pub struct SubagentDefinition {
|
||||
/// inherits the parent session's configured reasoning effort.
|
||||
#[serde(default)]
|
||||
pub reasoning_effort: Option<ReasoningEffort>,
|
||||
|
||||
/// Where this definition was loaded from; used for precedence rules and
|
||||
/// behavior differences (e.g., instruction composition).
|
||||
/// Not serialized; defaults to EmbeddedDefault.
|
||||
#[serde(skip)]
|
||||
pub(crate) source: SubagentSource,
|
||||
}
|
||||
|
||||
impl SubagentDefinition {
|
||||
@@ -51,5 +62,4 @@ impl SubagentDefinition {
|
||||
pub(crate) fn output_schema(&self) -> Option<&JsonSchema> {
|
||||
self.output_schema.as_ref()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::default_agents;
|
||||
use super::definition::SubagentDefinition;
|
||||
use super::definition::SubagentSource;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
@@ -35,11 +36,11 @@ impl SubagentRegistry {
|
||||
|
||||
// Load user definitions first
|
||||
if let Some(dir) = &self.user_dir {
|
||||
Self::load_from_dir_into(dir, &mut map);
|
||||
Self::load_from_dir_into(dir, &mut map, SubagentSource::User);
|
||||
}
|
||||
// Then load project definitions which override on conflicts
|
||||
if let Some(dir) = &self.project_dir {
|
||||
Self::load_from_dir_into(dir, &mut map);
|
||||
Self::load_from_dir_into(dir, &mut map, SubagentSource::Project);
|
||||
}
|
||||
|
||||
// No ad-hoc fallback here; embedded defaults already include "hello".
|
||||
@@ -55,7 +56,11 @@ impl SubagentRegistry {
|
||||
self.map.keys().cloned().collect()
|
||||
}
|
||||
|
||||
fn load_from_dir_into(dir: &Path, out: &mut HashMap<String, SubagentDefinition>) {
|
||||
fn load_from_dir_into(
|
||||
dir: &Path,
|
||||
out: &mut HashMap<String, SubagentDefinition>,
|
||||
source: SubagentSource,
|
||||
) {
|
||||
let Ok(iter) = fs::read_dir(dir) else {
|
||||
return;
|
||||
};
|
||||
@@ -69,7 +74,8 @@ impl SubagentRegistry {
|
||||
.unwrap_or(false)
|
||||
{
|
||||
match SubagentDefinition::from_file(&path) {
|
||||
Ok(def) => {
|
||||
Ok(mut def) => {
|
||||
def.source = source;
|
||||
out.insert(def.name.clone(), def);
|
||||
}
|
||||
Err(e) => {
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::codex::Codex;
|
||||
use crate::error::Result as CodexResult;
|
||||
|
||||
use super::definition::SubagentDefinition;
|
||||
use super::definition::SubagentSource;
|
||||
use super::registry::SubagentRegistry;
|
||||
use crate::openai_tools::JsonSchema;
|
||||
use serde_json::Value as JsonValue;
|
||||
@@ -15,6 +16,40 @@ pub struct RunSubagentArgs {
|
||||
pub context: Option<String>,
|
||||
}
|
||||
|
||||
/// Build the effective base instructions for a subagent run.
|
||||
///
|
||||
/// For user- and project-scoped subagents, we append their instructions to the
|
||||
/// parent session's base instructions. For embedded defaults, we use only the
|
||||
/// subagent's instructions. If an output schema is present, we augment the
|
||||
/// subagent instructions with strict JSON output requirements.
|
||||
fn compose_base_instructions_for_subagent(
|
||||
def: &SubagentDefinition,
|
||||
parent_base_instructions: Option<&str>,
|
||||
) -> String {
|
||||
// Start with the subagent's own instructions, optionally augmented with
|
||||
// structured output requirements.
|
||||
let child_instructions = if let Some(schema) = def.output_schema() {
|
||||
let schema_json = serde_json::to_string_pretty(schema).unwrap_or_else(|_| "{}".to_string());
|
||||
format!(
|
||||
"{instructions}\n\nOutput format requirements:\n- Reply with a single JSON value that strictly matches the following JSON Schema.\n- Do not include any commentary, markdown, or extra text.\n- Do not include trailing explanations.\n\nSchema:\n{schema_json}\n",
|
||||
instructions = def.instructions,
|
||||
schema_json = schema_json
|
||||
)
|
||||
} else {
|
||||
def.instructions.clone()
|
||||
};
|
||||
|
||||
match def.source {
|
||||
SubagentSource::User | SubagentSource::Project => match parent_base_instructions {
|
||||
Some(parent) if !parent.trim().is_empty() => {
|
||||
format!("{parent}\n\n{child}", child = child_instructions)
|
||||
}
|
||||
_ => child_instructions,
|
||||
},
|
||||
SubagentSource::EmbeddedDefault => child_instructions,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run a subagent in a nested Codex session and return the final message.
|
||||
pub(crate) async fn run(
|
||||
sess: &crate::codex::Session,
|
||||
@@ -28,17 +63,8 @@ pub(crate) async fn run(
|
||||
})?;
|
||||
|
||||
let mut nested_cfg = (*sess.base_config()).clone();
|
||||
// Compose base instructions with structured output schema instructions, if present.
|
||||
let base_instructions = if let Some(schema) = def.output_schema() {
|
||||
let schema_json = serde_json::to_string_pretty(schema).unwrap_or_else(|_| "{}".to_string());
|
||||
format!(
|
||||
"{instructions}\n\nOutput format requirements:\n- Reply with a single JSON value that strictly matches the following JSON Schema.\n- Do not include any commentary, markdown, or extra text.\n- Do not include trailing explanations.\n\nSchema:\n{schema_json}\n",
|
||||
instructions = def.instructions,
|
||||
schema_json = schema_json
|
||||
)
|
||||
} else {
|
||||
def.instructions.clone()
|
||||
};
|
||||
let base_instructions =
|
||||
compose_base_instructions_for_subagent(def, turn_context.base_instructions.as_deref());
|
||||
nested_cfg.base_instructions = Some(base_instructions);
|
||||
nested_cfg.user_instructions = None;
|
||||
// Apply subagent-specific overrides for model and reasoning effort.
|
||||
|
||||
Reference in New Issue
Block a user