-
Notifications
You must be signed in to change notification settings - Fork 15.4k
core: reset context for token budget compaction #29743
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| use std::sync::Arc; | ||
|
|
||
| use crate::compact::InitialContextInjection; | ||
| use crate::context::world_state::WorldState; | ||
| use crate::hook_runtime::PostCompactHookOutcome; | ||
| use crate::hook_runtime::PreCompactHookOutcome; | ||
| use crate::hook_runtime::run_post_compact_hooks; | ||
| use crate::hook_runtime::run_pre_compact_hooks; | ||
| use crate::session::session::Session; | ||
| use crate::session::turn_context::TurnContext; | ||
| use codex_analytics::CompactionTrigger; | ||
| use codex_protocol::error::CodexErr; | ||
| use codex_protocol::error::Result as CodexResult; | ||
| use codex_protocol::items::ContextCompactionItem; | ||
| use codex_protocol::items::TurnItem; | ||
| use codex_protocol::protocol::EventMsg; | ||
| use codex_protocol::protocol::TurnStartedEvent; | ||
|
|
||
| /// Runs token-budget manual compaction as a normal compaction lifecycle. | ||
| /// | ||
| /// Token-budget compaction skips model/server summarization and installs a fresh context window | ||
| /// instead. It is still modeled as compaction so compact hooks and `ContextCompaction` turn items | ||
| /// observe the same lifecycle as local or remote compaction. | ||
| pub(crate) async fn run_manual_compact_task( | ||
| sess: Arc<Session>, | ||
| turn_context: Arc<TurnContext>, | ||
| ) -> CodexResult<()> { | ||
| let start_event = EventMsg::TurnStarted(TurnStartedEvent { | ||
| turn_id: turn_context.sub_id.clone(), | ||
| trace_id: turn_context.trace_id.clone(), | ||
| started_at: turn_context.turn_timing_state.started_at_unix_secs().await, | ||
| model_context_window: turn_context.model_context_window(), | ||
| collaboration_mode_kind: turn_context.collaboration_mode.mode, | ||
| }); | ||
| sess.send_event(&turn_context, start_event).await; | ||
|
|
||
| let world_state = Arc::new( | ||
| sess.build_world_state_for_environments(&turn_context, &turn_context.environments) | ||
| .await, | ||
| ); | ||
| run_compact_task_inner(&sess, &turn_context, world_state, CompactionTrigger::Manual).await | ||
| } | ||
|
|
||
| /// Runs token-budget inline auto-compaction as a normal compaction lifecycle. | ||
| /// | ||
| /// Token-budget compaction skips model/server summarization and installs a fresh context window | ||
| /// instead. It is still modeled as compaction so compact hooks and `ContextCompaction` turn items | ||
| /// observe the same lifecycle as local or remote compaction. | ||
| pub(crate) async fn run_inline_auto_compact_task( | ||
| sess: Arc<Session>, | ||
| turn_context: Arc<TurnContext>, | ||
| initial_context_injection: InitialContextInjection, | ||
| ) -> CodexResult<()> { | ||
| let world_state = match initial_context_injection { | ||
| InitialContextInjection::BeforeLastUserMessage(world_state) => world_state, | ||
| InitialContextInjection::DoNotInject => Arc::new( | ||
| sess.build_world_state_for_environments(&turn_context, &turn_context.environments) | ||
| .await, | ||
| ), | ||
| }; | ||
| run_compact_task_inner(&sess, &turn_context, world_state, CompactionTrigger::Auto).await | ||
| } | ||
|
|
||
| async fn run_compact_task_inner( | ||
| sess: &Arc<Session>, | ||
| turn_context: &Arc<TurnContext>, | ||
| world_state: Arc<WorldState>, | ||
| trigger: CompactionTrigger, | ||
| ) -> CodexResult<()> { | ||
| let pre_compact_outcome = run_pre_compact_hooks(sess, turn_context, trigger).await; | ||
| match pre_compact_outcome { | ||
| PreCompactHookOutcome::Continue => {} | ||
| PreCompactHookOutcome::Stopped => return Err(CodexErr::TurnAborted), | ||
| } | ||
|
|
||
| let compaction_item = TurnItem::ContextCompaction(ContextCompactionItem::new()); | ||
| sess.emit_turn_item_started(turn_context, &compaction_item) | ||
| .await; | ||
| sess.start_new_context_window(turn_context.as_ref(), world_state) | ||
| .await; | ||
| sess.emit_turn_item_completed(turn_context, compaction_item) | ||
| .await; | ||
|
|
||
| let post_compact_outcome = run_post_compact_hooks(sess, turn_context, trigger).await; | ||
| if let PostCompactHookOutcome::Stopped = post_compact_outcome { | ||
| return Err(CodexErr::TurnAborted); | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -331,22 +331,14 @@ pub(crate) async fn run_turn( | |
| ) | ||
| .await; | ||
|
|
||
| let started_new_context_window = sess | ||
| .maybe_start_new_context_window(turn_context.as_ref(), Arc::clone(&world_state)) | ||
| .await | ||
| .is_some(); | ||
| if started_new_context_window && needs_follow_up { | ||
| can_drain_pending_input = !model_needs_follow_up; | ||
| continue; | ||
| } | ||
|
|
||
| // as long as compaction works well in getting us way below the token limit, we shouldn't worry about being in an infinite loop. | ||
| if turn_context | ||
| let auto_compact_needed = turn_context | ||
| .config | ||
| .features | ||
| .enabled(Feature::AutoCompaction) | ||
| && token_limit_reached | ||
| && needs_follow_up | ||
| && token_limit_reached; | ||
| if needs_follow_up | ||
| && (sess.take_new_context_window_request().await || auto_compact_needed) | ||
| { | ||
| if let Err(err) = run_auto_compact( | ||
| &sess, | ||
|
|
@@ -928,6 +920,18 @@ async fn run_auto_compact( | |
| phase: CompactionPhase, | ||
| ) -> CodexResult<()> { | ||
| let turn_context = &step_context.turn; | ||
| if turn_context.config.features.enabled(Feature::TokenBudget) { | ||
| // Compaction is the reset request, so force a new context window | ||
| // instead of consuming a pending `new_context` tool request. | ||
| crate::compact_token_budget::run_inline_auto_compact_task( | ||
| Arc::clone(sess), | ||
| Arc::clone(turn_context), | ||
| initial_context_injection, | ||
| ) | ||
|
Comment on lines
+926
to
+930
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When pre-turn compaction is triggered by a comp-hash change or model downshift, Useful? React with 👍 / 👎.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. true, we should take as a followup. |
||
| .await?; | ||
| return Ok(()); | ||
| } | ||
|
|
||
| if should_use_remote_compact_task(turn_context.provider.info()) { | ||
| if turn_context | ||
| .config | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When this path is used by standalone
/compact, it writes a replacement history that already contains full initial context, but rollout reconstruction only adoptsTurnContextbaselines from real user-turn segments. After resuming a thread that ended with token-budget/compact(especially before any user turn), the replacement history is restored whilereference_context_itemremains missing or stale, so the next turn reinjects full initial context on top of the compacted window and duplicates token-budget/world-state context.AGENTS.md reference: AGENTS.md:L103-L111
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true, we should take as a followup.