Register agent tasks behind use_agent_identity (#17387)

## Summary

Stack PR3 for feature-gated agent identity support.

This PR adds per-thread agent task registration behind
`features.use_agent_identity`. Tasks are minted on the first real user
turn and cached in thread runtime state for later turns.

## Stack

- PR1: https://github.com/openai/codex/pull/17385 - add
`features.use_agent_identity`
- PR2: https://github.com/openai/codex/pull/17386 - register agent
identities when enabled
- PR3: https://github.com/openai/codex/pull/17387 - this PR, original
task registration slice
- PR3.1: https://github.com/openai/codex/pull/17978 - persist and
prewarm registered tasks per thread
- PR4: https://github.com/openai/codex/pull/17980 - use `AgentAssertion`
downstream when enabled

## Validation

Covered as part of the local stack validation pass:

- `just fmt`
- `cargo test -p codex-core --lib agent_identity`
- `cargo test -p codex-core --lib agent_assertion`
- `cargo test -p codex-core --lib websocket_agent_task`
- `cargo test -p codex-api api_bridge`
- `cargo build -p codex-cli --bin codex`

## Notes

The full local app-server E2E path is still being debugged after PR
creation. The current branch stack is directionally ready for review
while that follow-up continues.
This commit is contained in:
Adrian
2026-04-16 14:30:02 -07:00
committed by GitHub
parent 0708cc78cb
commit 55c3de75cb
14 changed files with 804 additions and 43 deletions

View File

@@ -15,6 +15,7 @@ use codex_protocol::protocol::ConversationStartTransport;
use codex_protocol::protocol::ConversationTextParams;
use codex_protocol::protocol::ErrorEvent;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::GitInfo;
use codex_protocol::protocol::InitialHistory;
use codex_protocol::protocol::Op;
use codex_protocol::protocol::RealtimeAudioFrame;
@@ -24,7 +25,11 @@ use codex_protocol::protocol::RealtimeEvent;
use codex_protocol::protocol::RealtimeOutputModality;
use codex_protocol::protocol::RealtimeVoice;
use codex_protocol::protocol::RolloutItem;
use codex_protocol::protocol::RolloutLine;
use codex_protocol::protocol::SessionMeta;
use codex_protocol::protocol::SessionMetaLine;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::UserMessageEvent;
use codex_protocol::user_input::UserInput;
use codex_utils_output_truncation::approx_token_count;
use core_test_support::responses;
@@ -65,6 +70,7 @@ const MEMORY_PROMPT_PHRASE: &str =
"You have access to a memory folder with guidance from prior runs.";
const REALTIME_CONVERSATION_TEST_SUBPROCESS_ENV_VAR: &str =
"CODEX_REALTIME_CONVERSATION_TEST_SUBPROCESS";
const WEBSOCKET_REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
#[derive(Debug, Clone)]
struct RealtimeCallRequestCapture {
@@ -141,7 +147,7 @@ async fn wait_for_matching_websocket_request<F>(
where
F: Fn(&core_test_support::responses::WebSocketRequest) -> bool,
{
let deadline = tokio::time::Instant::now() + Duration::from_secs(10);
let deadline = tokio::time::Instant::now() + WEBSOCKET_REQUEST_TIMEOUT;
loop {
if let Some(request) = server
.connections()
@@ -196,16 +202,18 @@ async fn seed_recent_thread(
let db = test.codex.state_db().context("state db enabled")?;
let thread_id = ThreadId::new();
let updated_at = Utc::now();
let rollout_path = test
let rollout_dir = test
.codex_home_path()
.join(format!("rollout-{thread_id}.jsonl"));
// This helper seeds SQLite metadata directly. Local listing drops stale metadata rows whose
// rollout path no longer exists, so create the placeholder path that the test metadata points
// at without exercising rollout writing in this realtime-context test.
std::fs::write(&rollout_path, "")?;
.join("sessions")
.join(updated_at.format("%Y/%m/%d").to_string());
fs::create_dir_all(&rollout_dir)?;
let rollout_path = rollout_dir.join(format!(
"rollout-{}-{thread_id}.jsonl",
updated_at.format("%Y-%m-%dT%H-%M-%S")
));
let mut metadata_builder = codex_state::ThreadMetadataBuilder::new(
thread_id,
rollout_path,
rollout_path.clone(),
updated_at,
SessionSource::Cli,
);
@@ -215,6 +223,45 @@ async fn seed_recent_thread(
let mut metadata = metadata_builder.build("test-provider");
metadata.title = title.to_string();
metadata.first_user_message = Some(first_user_message.to_string());
let timestamp = updated_at.to_rfc3339();
let session_meta = RolloutLine {
timestamp: timestamp.clone(),
item: RolloutItem::SessionMeta(SessionMetaLine {
meta: SessionMeta {
id: thread_id,
timestamp: timestamp.clone(),
cwd: metadata.cwd.clone(),
originator: "cli".to_string(),
cli_version: "0.0.0".to_string(),
source: SessionSource::Cli,
model_provider: Some("test-provider".to_string()),
..Default::default()
},
git: Some(GitInfo {
commit_hash: None,
branch: metadata.git_branch.clone(),
repository_url: None,
}),
}),
};
let user_message = RolloutLine {
timestamp,
item: RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent {
message: first_user_message.to_string(),
images: None,
local_images: Vec::new(),
text_elements: Vec::new(),
})),
};
fs::write(
&rollout_path,
format!(
"{}\n{}\n",
serde_json::to_string(&session_meta)?,
serde_json::to_string(&user_message)?
),
)?;
db.upsert_thread(&metadata).await?;
Ok(())