From 0383166f4ecd42b4d76f298c173080c14ea1159b Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 20 Oct 2026 18:27:34 +0000 Subject: [PATCH] Use the active model context limit for provider compaction --- app/src/ai/blocklist/controller.rs | 5 +- .../ai/runtime/provider_run_coordinator.rs | 9 ++++ .../runtime/provider_run_coordinator_tests.rs | 50 +++++++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/app/src/ai/blocklist/controller.rs b/app/src/ai/blocklist/controller.rs index 7a23ffd3..bfafeb5a 100644 --- a/app/src/ai/blocklist/controller.rs +++ b/app/src/ai/blocklist/controller.rs @@ -6935,8 +6935,9 @@ impl BlocklistAIController { } let active_context_limit = active_run - .response_config - .max_context_tokens + .coordinator + .current_context_limit() + .or(active_run.response_config.max_context_tokens) .unwrap_or_else(|| { let preferences = LLMPreferences::as_ref(ctx); configured_llm_context_limit( diff --git a/app/src/ai/runtime/provider_run_coordinator.rs b/app/src/ai/runtime/provider_run_coordinator.rs index 435b4ac4..391e2138 100644 --- a/app/src/ai/runtime/provider_run_coordinator.rs +++ b/app/src/ai/runtime/provider_run_coordinator.rs @@ -251,6 +251,15 @@ impl ProviderRunCoordinator { self.profiles.get(profile).map(|profile| &profile.request) } + /// The pending model profile owns the context budget, including after a + /// preflight overflow has returned the run to its compaction boundary. + pub(crate) fn current_context_limit(&self) -> Option { + self.profiles + .get(self.run.profile().as_str()) + .and_then(|profile| profile.max_context_tokens) + .or(self.max_context_tokens) + } + /// Includes the current transcript, fixed request context, tools, and output reservation. /// Read at the model boundary so newly committed tool output is included. pub(crate) fn estimated_context_tokens(&self) -> Option<(u32, u32)> { diff --git a/app/src/ai/runtime/provider_run_coordinator_tests.rs b/app/src/ai/runtime/provider_run_coordinator_tests.rs index 88003166..fa685f81 100644 --- a/app/src/ai/runtime/provider_run_coordinator_tests.rs +++ b/app/src/ai/runtime/provider_run_coordinator_tests.rs @@ -1558,6 +1558,56 @@ fn context_estimate_tracks_live_transcript_and_reserves_fixed_context() { assert_eq!(fixed_before, fixed_after); } +#[tokio::test] +async fn compaction_boundary_keeps_the_monitor_context_limit() { + let base_runtime = Arc::new(ScriptedRuntime::new(vec![])); + let cli_runtime = Arc::new(ScriptedRuntime::new(vec![])); + let run = ProviderRun::new( + "monitor-compaction-budget", + vec![ConversationMessage { + role: MessageRole::User, + content: MessageContent::Text("history".repeat(5_000)), + }], + CLI_MONITOR_PROVIDER_PROFILE, + ProviderRunLimits::default(), + ); + let mut profiles = BTreeMap::new(); + profiles.insert( + BASE_PROVIDER_PROFILE.to_string(), + ProviderRunProfile::new(base_runtime.clone(), request()), + ); + profiles.insert( + CLI_MONITOR_PROVIDER_PROFILE.to_string(), + ProviderRunProfile::new(cli_runtime.clone(), request()) + .with_max_context_tokens(Some(8_000)), + ); + let mut coordinator = ProviderRunCoordinator::new(run, profiles).unwrap(); + coordinator.set_max_context_tokens(Some(100_000)); + let (_sender, control) = turn_control(); + + let block = coordinator + .drive_until_blocked(control, |_| Ok(())) + .await + .unwrap(); + + assert_eq!(block, ProviderRunBlock::ContextWindowExceeded); + assert_eq!(coordinator.run().state(), &ProviderRunState::ReadyToCallModel); + assert_eq!(coordinator.current_context_limit(), Some(8_000)); + assert!(base_runtime.requests().is_empty()); + assert!(cli_runtime.requests().is_empty()); + + let work_id = coordinator.run().ready_work_id().unwrap(); + coordinator + .run_mut() + .continue_ready_with_observation( + &work_id, + MessageContent::Text("Command finished".to_string()), + BASE_PROVIDER_PROFILE, + ) + .unwrap(); + assert_eq!(coordinator.current_context_limit(), Some(100_000)); +} + #[tokio::test] async fn monitor_request_uses_its_profile_context_limit() { let base_runtime = Arc::new(ScriptedRuntime::new(vec![answer_turn()]));