Support metadata staging for reserved thread IDs (#38819)

## Why

Callers need to associate host-owned state with a thread before Core starts it.

## What changed

- Add `ThreadManager::reserve_thread_id` and allow new threads to start with the reserved ID. Reject reserved IDs when resuming an existing thread.
- Let thread stores stage metadata for a reserved ID. The local store merges staged values into the first successful metadata update and then removes them.
- Clear staged metadata when an unmaterialized thread shuts down or is discarded, and require the state database for staging.

## Testing

- Cover reserved ID generation and resume validation.
- Cover metadata persistence, merging, rollout-compatible updates, cleanup, and invalid staging requests.

GitOrigin-RevId: 3f9bc48f0f7764011cc90ea01349e497dda0a9e3
This commit is contained in:
Anton Panasenko authored and copyberry committed 2026-08-16 00:57:28 +00:00
1 parent 5ba12929f8
commit fcdae21073
17 files changed
+650 -21

No files matched your search

+13 -3
View File
@@ -605,6 +605,7 @@ impl Session {
mut thread_extension_init: ExtensionDataInit,
client_mcp_extensions: ClientMcpExtensions,
agent_control: AgentControl,
reserved_thread_id: Option<ThreadId>,
environment_manager: Arc<EnvironmentManager>,
inherited_environments: Option<TurnEnvironmentSnapshot>,
analytics_events_client: Option<AnalyticsEventsClient>,
@@ -663,11 +664,20 @@ impl Session {
let multi_agent_version = multi_agent_version.map(OnceLock::from).unwrap_or_default();
let initial_multi_agent_version = multi_agent_version.get().copied();
let thread_id = match &initial_history {
InitialHistory::New | InitialHistory::Cleared | InitialHistory::Forked(_) => {
let thread_id = match (&initial_history, reserved_thread_id) {
(
InitialHistory::New | InitialHistory::Cleared | InitialHistory::Forked(_),
Some(thread_id),
) => thread_id,
(InitialHistory::New | InitialHistory::Cleared | InitialHistory::Forked(_), None) => {
agent_control.generate_thread_id()
}
InitialHistory::Resumed(resumed_history) => resumed_history.conversation_id,
(InitialHistory::Resumed(resumed_history), None) => resumed_history.conversation_id,
(InitialHistory::Resumed(_), Some(_)) => {
return Err(anyhow::anyhow!(
"reserved thread ID cannot be used when resuming a thread"
));
}
};
let resumed_session_id = match &initial_history {
InitialHistory::Resumed(resumed) => {