mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Persist session IDs across thread resume (#29327)
## Summary
A cold-resumed subagent kept its durable thread ID but could receive a
new session ID, splitting one agent tree across multiple sessions after
a restart.
Persist the root session ID in every rollout `SessionMeta`, carry it
through thread creation, and restore it before initializing the resumed
`Session` and `AgentControl`.
## Behavior
For a nested agent tree:
```text
root session R
parent thread P
child thread C
```
The child rollout stores:
```text
session_id: R
parent_thread_id: P
id: C
```
After a cold resume, the child still belongs to root session `R` while
its immediate parent remains `P`. The integration coverage uses distinct
values for all three IDs so it catches restoring the session from
`parent_thread_id`.
## Legacy rollouts
Previous rollouts have `id` but no `session_id`. `SessionMetaLine`
deserialization treats a missing `session_id` as `id`, keeping those
files readable, listable, and resumable. When a legacy subagent is
resumed through its root, that synthesized child ID no longer overrides
the inherited root-scoped `AgentControl`. New rollouts always persist
the explicit root session ID.
This commit is contained in:
@@ -1900,6 +1900,7 @@ fn session_meta_item(
|
||||
) -> RolloutItem {
|
||||
RolloutItem::SessionMeta(SessionMetaLine {
|
||||
meta: SessionMeta {
|
||||
session_id: thread_id.into(),
|
||||
id: thread_id,
|
||||
multi_agent_version,
|
||||
..SessionMeta::default()
|
||||
@@ -3750,6 +3751,7 @@ async fn attach_thread_persistence(session: &mut Session) -> PathBuf {
|
||||
let live_thread = LiveThread::create(
|
||||
Arc::clone(&session.services.thread_store),
|
||||
CreateThreadParams {
|
||||
session_id: session.session_id(),
|
||||
thread_id: session.thread_id,
|
||||
extra_config: None,
|
||||
forked_from_id: None,
|
||||
@@ -5453,7 +5455,7 @@ async fn resumed_root_session_uses_thread_id_as_session_id() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resumed_subagent_session_keeps_inherited_session_id() {
|
||||
async fn resumed_subagent_session_restores_persisted_session_id() {
|
||||
let parent_thread_id = ThreadId::new();
|
||||
let parent_session_id = SessionId::from(parent_thread_id);
|
||||
let thread_id = ThreadId::new();
|
||||
@@ -5467,11 +5469,19 @@ async fn resumed_subagent_session_keeps_inherited_session_id() {
|
||||
let (session, rx_event) = make_session_with_history_source_and_agent_control_and_rx(
|
||||
InitialHistory::Resumed(ResumedHistory {
|
||||
conversation_id: thread_id,
|
||||
history: Vec::new(),
|
||||
history: vec![RolloutItem::SessionMeta(SessionMetaLine {
|
||||
meta: SessionMeta {
|
||||
session_id: parent_session_id,
|
||||
id: thread_id,
|
||||
source: session_source.clone(),
|
||||
..SessionMeta::default()
|
||||
},
|
||||
git: None,
|
||||
})],
|
||||
rollout_path: None,
|
||||
}),
|
||||
session_source,
|
||||
AgentControl::default().with_session_id(parent_session_id, /*max_threads*/ usize::MAX),
|
||||
AgentControl::default(),
|
||||
)
|
||||
.await
|
||||
.expect("resume should succeed");
|
||||
@@ -6590,6 +6600,7 @@ async fn shutdown_complete_does_not_append_to_thread_store_after_shutdown() {
|
||||
let live_thread = LiveThread::create(
|
||||
Arc::clone(&thread_store),
|
||||
CreateThreadParams {
|
||||
session_id: session.session_id(),
|
||||
thread_id: session.thread_id,
|
||||
extra_config: None,
|
||||
forked_from_id: None,
|
||||
|
||||
Reference in New Issue
Block a user