mirror of
https://github.com/openai/codex.git
synced 2026-04-30 17:36:40 +00:00
Add role-specific subagent nickname overrides (#13218)
## Summary - add `nickname_candidates` to agent role config - use role-specific nickname pools for spawned and resumed subagents - validate and schema-generate the new config surface ## Testing - `just fmt` - `just write-config-schema` - `just fix -p codex-core` - `cargo test -p codex-core` - `cargo test` --------- Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
use crate::agent::AgentStatus;
|
||||
use crate::agent::guards::Guards;
|
||||
use crate::agent::role::DEFAULT_ROLE_NAME;
|
||||
use crate::agent::role::resolve_role_config;
|
||||
use crate::agent::status::is_final;
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::Result as CodexResult;
|
||||
@@ -32,7 +34,7 @@ pub(crate) struct SpawnAgentOptions {
|
||||
pub(crate) fork_parent_spawn_call_id: Option<String>,
|
||||
}
|
||||
|
||||
fn agent_nickname_list() -> Vec<&'static str> {
|
||||
fn default_agent_nickname_list() -> Vec<&'static str> {
|
||||
AGENT_NAMES
|
||||
.lines()
|
||||
.map(str::trim)
|
||||
@@ -40,6 +42,23 @@ fn agent_nickname_list() -> Vec<&'static str> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn agent_nickname_candidates(
|
||||
config: &crate::config::Config,
|
||||
role_name: Option<&str>,
|
||||
) -> Vec<String> {
|
||||
let role_name = role_name.unwrap_or(DEFAULT_ROLE_NAME);
|
||||
if let Some(candidates) =
|
||||
resolve_role_config(config, role_name).and_then(|role| role.nickname_candidates.clone())
|
||||
{
|
||||
return candidates;
|
||||
}
|
||||
|
||||
default_agent_nickname_list()
|
||||
.into_iter()
|
||||
.map(ToOwned::to_owned)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Control-plane handle for multi-agent operations.
|
||||
/// `AgentControl` is held by each session (via `SessionServices`). It provides capability to
|
||||
/// spawn new agents and the inter-agent communication layer.
|
||||
@@ -94,7 +113,10 @@ impl AgentControl {
|
||||
agent_role,
|
||||
..
|
||||
})) => {
|
||||
let agent_nickname = reservation.reserve_agent_nickname(&agent_nickname_list())?;
|
||||
let candidate_names = agent_nickname_candidates(&config, agent_role.as_deref());
|
||||
let candidate_name_refs: Vec<&str> =
|
||||
candidate_names.iter().map(String::as_str).collect();
|
||||
let agent_nickname = reservation.reserve_agent_nickname(&candidate_name_refs)?;
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id,
|
||||
depth,
|
||||
@@ -225,8 +247,12 @@ impl AgentControl {
|
||||
let reserved_agent_nickname = resumed_agent_nickname
|
||||
.as_deref()
|
||||
.map(|agent_nickname| {
|
||||
let candidate_names =
|
||||
agent_nickname_candidates(&config, resumed_agent_role.as_deref());
|
||||
let candidate_name_refs: Vec<&str> =
|
||||
candidate_names.iter().map(String::as_str).collect();
|
||||
reservation.reserve_agent_nickname_with_preference(
|
||||
&agent_nickname_list(),
|
||||
&candidate_name_refs,
|
||||
Some(agent_nickname),
|
||||
)
|
||||
})
|
||||
@@ -467,6 +493,7 @@ mod tests {
|
||||
use crate::CodexThread;
|
||||
use crate::ThreadManager;
|
||||
use crate::agent::agent_status_from_event;
|
||||
use crate::config::AgentRoleConfig;
|
||||
use crate::config::Config;
|
||||
use crate::config::ConfigBuilder;
|
||||
use crate::config_loader::LoaderOverrides;
|
||||
@@ -1378,6 +1405,49 @@ mod tests {
|
||||
assert_eq!(agent_role, Some("explorer".to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn spawn_thread_subagent_uses_role_specific_nickname_candidates() {
|
||||
let mut harness = AgentControlHarness::new().await;
|
||||
harness.config.agent_roles.insert(
|
||||
"researcher".to_string(),
|
||||
AgentRoleConfig {
|
||||
description: Some("Research role".to_string()),
|
||||
config_file: None,
|
||||
nickname_candidates: Some(vec!["Atlas".to_string()]),
|
||||
},
|
||||
);
|
||||
let (parent_thread_id, _parent_thread) = harness.start_thread().await;
|
||||
|
||||
let child_thread_id = harness
|
||||
.control
|
||||
.spawn_agent(
|
||||
harness.config.clone(),
|
||||
text_input("hello child"),
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id,
|
||||
depth: 1,
|
||||
agent_nickname: None,
|
||||
agent_role: Some("researcher".to_string()),
|
||||
})),
|
||||
)
|
||||
.await
|
||||
.expect("child spawn should succeed");
|
||||
|
||||
let child_thread = harness
|
||||
.manager
|
||||
.get_thread(child_thread_id)
|
||||
.await
|
||||
.expect("child thread should be registered");
|
||||
let snapshot = child_thread.config_snapshot().await;
|
||||
|
||||
let SessionSource::SubAgent(SubAgentSource::ThreadSpawn { agent_nickname, .. }) =
|
||||
snapshot.session_source
|
||||
else {
|
||||
panic!("expected thread-spawn sub-agent source");
|
||||
};
|
||||
assert_eq!(agent_nickname, Some("Atlas".to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resume_thread_subagent_restores_stored_nickname_and_role() {
|
||||
let (home, mut config) = test_config().await;
|
||||
|
||||
Reference in New Issue
Block a user