Preserve agent state when unloading idle multi-agent v2 children (#52034)

## What changed

- Route app-server idle unloading of multi-agent v2 children through local eviction that preserves unread mail and environment selections for reload.
- Hold residency pins through input submission so newly reloaded children cannot be evicted before receiving their work.
- Guard listener cleanup against replacement runtimes and subscriptions, and reject local eviction of host-controlled threads.

## Testing

Add coverage for unsubscribe-driven eviction followed by `followup_task`, verifying restored conversation context and exactly-once delivery of queued mail. Extend tests to check that residency pins block eviction and that rejected host eviction preserves the thread and mailbox.

GitOrigin-RevId: e36723730af683d2081e495e637f0e6d23548b8d
This commit is contained in:
jif
2026-10-08 09:39:33 +00:00
committed by copyberry
parent ea27864f99
commit d2a6dac20b
12 changed files with 493 additions and 124 deletions
@@ -54,14 +54,16 @@ impl Handler {
.local_agent_runtime
.control(session.session_id());
let receiver_agent = local_agent_control.get_agent_metadata(receiver_thread_id);
if receiver_agent.is_some() {
let residency_pin = if receiver_agent.is_some() {
let resume_config = build_agent_resume_config(turn.as_ref())
.map_err(FunctionCallError::RespondToModel)?;
local_agent_control
.ensure_v2_agent_loaded(resume_config, receiver_thread_id, /*parent*/ None)
.await
.map_err(|err| collab_agent_error(receiver_thread_id, err))?;
}
.map_err(|err| collab_agent_error(receiver_thread_id, err))?
} else {
None
};
let receiver_agent = receiver_agent.unwrap_or_default();
if args.interrupt {
session
@@ -106,6 +108,7 @@ impl Handler {
)
.await
.map_err(|err| collab_agent_error(receiver_thread_id, err));
drop(residency_pin);
let status = local_agent_control.get_status(receiver_thread_id).await;
session
.emit_turn_item_completed(