This commit is contained in:
Ahmed Ibrahim
2025-08-23 23:59:38 -07:00
parent bfe99e4396
commit 113c87e8ae
4 changed files with 67 additions and 21 deletions
@@ -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 '{}': {}",
+15 -5
View File
@@ -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()
}
}
+10 -4
View File
@@ -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) => {
+37 -11
View File
@@ -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.