chore: morpheus to path (#18353)

Make the morpheus agent (which is the phase 2 memories agent) follow the
agent-v2 path system by naming it `/morpheus`. To maintain the path
primitive this means moving it to a dedicated `AgentControl`

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
jif-oai
2026-04-20 10:32:20 +01:00
committed by GitHub
Unverified
parent e404c4e910
commit b528ff02b6
5 changed files with 50 additions and 7 deletions
+9
View File
@@ -148,6 +148,15 @@ impl AgentControl {
}
}
/// Create a control-plane handle over the same thread manager with an independent live-agent
/// registry.
pub(crate) fn detached_registry(&self) -> Self {
Self {
manager: self.manager.clone(),
..Default::default()
}
}
/// Spawn a new agent thread and submit the initial prompt.
pub(crate) async fn spawn_agent(
&self,
+4 -4
View File
@@ -139,9 +139,8 @@ pub(super) async fn run(session: &Arc<Session>, config: Arc<Config>) {
// 5. Spawn the agent
let prompt = agent::get_prompt(config, &selection, &removed_extension_resources);
let source = SessionSource::SubAgent(SubAgentSource::MemoryConsolidation);
let thread_id = match session
.services
.agent_control
let agent_control = session.services.agent_control.detached_registry();
let thread_id = match agent_control
.spawn_agent(agent_config, prompt.into(), Some(source))
.await
{
@@ -182,6 +181,7 @@ pub(super) async fn run(session: &Arc<Session>, config: Arc<Config>) {
raw_memories.clone(),
pending_extension_resource_removals,
thread_id,
agent_control,
phase_two_e2e_timer,
);
@@ -369,6 +369,7 @@ mod agent {
selected_outputs: Vec<codex_state::Stage1Output>,
pending_extension_resource_removals: Vec<PendingExtensionResourceRemoval>,
thread_id: ThreadId,
agent_control: crate::agent::AgentControl,
phase_two_e2e_timer: Option<codex_otel::Timer>,
) {
let Some(db) = session.services.state_db.clone() else {
@@ -378,7 +379,6 @@ mod agent {
tokio::spawn(async move {
let _phase_two_e2e_timer = phase_two_e2e_timer;
let agent_control = session.services.agent_control.clone();
// TODO(jif) we might have a very small race here.
let rx = match agent_control.subscribe_status(thread_id).await {
+14
View File
@@ -429,6 +429,7 @@ mod phase2 {
use codex_config::Constrained;
use codex_features::Feature;
use codex_login::CodexAuth;
use codex_protocol::AgentPath;
use codex_protocol::ThreadId;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
@@ -703,6 +704,19 @@ mod phase2 {
}
other => panic!("unexpected sandbox policy: {other:?}"),
}
pretty_assertions::assert_eq!(
config_snapshot.session_source.get_agent_path(),
Some(AgentPath::morpheus())
);
assert!(
harness
.session
.services
.agent_control
.get_agent_metadata(thread_id)
.is_none(),
"memory consolidation should not be registered in the root collab agent registry"
);
let turn_context = subagent.codex.session.new_default_turn().await;
pretty_assertions::assert_eq!(
turn_context.file_system_sandbox_policy,
+20 -3
View File
@@ -16,12 +16,17 @@ pub struct AgentPath(String);
impl AgentPath {
pub const ROOT: &str = "/root";
pub const MORPHEUS: &str = "/morpheus";
const ROOT_SEGMENT: &str = "root";
pub fn root() -> Self {
Self(Self::ROOT.to_string())
}
pub fn morpheus() -> Self {
Self(Self::MORPHEUS.to_string())
}
pub fn from_string(path: String) -> Result<Self, String> {
validate_absolute_path(path.as_str())?;
Ok(Self(path))
@@ -142,15 +147,19 @@ fn validate_agent_name(agent_name: &str) -> Result<(), String> {
}
fn validate_absolute_path(path: &str) -> Result<(), String> {
if path == AgentPath::MORPHEUS {
return Ok(());
}
let Some(stripped) = path.strip_prefix('/') else {
return Err("absolute agent paths must start with `/root`".to_string());
return Err("absolute agent paths must start with `/root` or be `/morpheus`".to_string());
};
let mut segments = stripped.split('/');
let Some(root) = segments.next() else {
return Err("absolute agent path must not be empty".to_string());
};
if root != AgentPath::ROOT_SEGMENT {
return Err("absolute agent paths must start with `/root`".to_string());
return Err("absolute agent paths must start with `/root` or be `/morpheus`".to_string());
}
if stripped.ends_with('/') {
return Err("absolute agent path must not end with `/`".to_string());
@@ -184,6 +193,14 @@ mod tests {
assert!(root.is_root());
}
#[test]
fn morpheus_has_expected_name() {
let morpheus = AgentPath::morpheus();
assert_eq!(morpheus.as_str(), AgentPath::MORPHEUS);
assert_eq!(morpheus.name(), "morpheus");
assert!(!morpheus.is_root());
}
#[test]
fn join_builds_child_paths() {
let root = AgentPath::root();
@@ -213,7 +230,7 @@ mod tests {
);
assert_eq!(
AgentPath::try_from("/not-root"),
Err("absolute agent paths must start with `/root`".to_string())
Err("absolute agent paths must start with `/root` or be `/morpheus`".to_string())
);
assert_eq!(
AgentPath::root().resolve("../sibling"),
+3
View File
@@ -2723,6 +2723,9 @@ impl SessionSource {
SessionSource::SubAgent(SubAgentSource::ThreadSpawn { agent_path, .. }) => {
agent_path.clone()
}
SessionSource::SubAgent(SubAgentSource::MemoryConsolidation) => {
Some(AgentPath::morpheus())
}
_ => None,
}
}