Honor per-thread unload delays in app-server (#55270)

## Why

Thread unload timing used the server-wide delay, ignoring per-thread configuration overrides. Clients need to control how long individual idle, unsubscribed threads remain loaded.

## What changed

- Resolve unload timing from each loaded thread's `thread_unload_delay_secs` configuration. Setting it to `0` allows immediate unloading once the thread has no subscribers or activity.
- Allow this runtime retention override in `experimentalPredictionMode` forks while continuing to reject other configuration overrides. Reject negative values and delays too large to schedule.
- Replace the source-specific immediate-unload behavior with explicit configuration.

## Testing

Extend integration coverage for per-thread delays, unloading after active turns finish, fork overrides, and invalid prediction-fork delays. Verify that immediate unloading preserves persistent history and that cold resumes and ordinary nested forks use their default delay instead of retaining the previous runtime's override.

GitOrigin-RevId: fcb82bd73a101260c51e2a8476f94320c23ac929
This commit is contained in:
jif
2026-10-19 12:30:33 +00:00
committed by copyberry
parent 1b89beea82
commit d28be12577
12 changed files with 157 additions and 31 deletions
@@ -547,8 +547,8 @@ pub struct ThreadForkParams {
/// Start from the loaded parent's effective config, tools, environments, and
/// persisted history to maximize prompt-cache reuse. Requires a loaded,
/// persisted parent and `ephemeral: true`. Enables `features.reasoning_effort_override`
/// on the fork. Cannot be combined with configuration overrides, `path`, `lastTurnId`,
/// or `beforeTurnId`.
/// on the fork. Only `config.thread_unload_delay_secs` may override the inherited
/// config. Cannot be combined with `path`, `lastTurnId`, or `beforeTurnId`.
#[experimental("thread/fork.experimentalPredictionMode")]
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub experimental_prediction_mode: bool,