88#include " utils/text_file_buffer.hpp"
99#include " commands/compact.hpp"
1010#include " commands/micro_compact.hpp"
11+ #include " session/compact_checkpoint.hpp"
1112#include " session/tool_metadata_codec.hpp"
1213#include " session/tool_result_storage.hpp"
1314#include " session/output_attachments.hpp"
@@ -267,6 +268,11 @@ nlohmann::json build_transcript_replace_payload(
267268 const CompactResult& result) {
268269 nlohmann::json arr = nlohmann::json::array ();
269270 for (const auto & msg : messages) {
271+ if (is_file_checkpoint_message (msg)) continue ;
272+ if (is_compact_checkpoint_message (msg)) continue ;
273+ if (is_content_replacement_message (msg)) continue ;
274+ if (is_turn_timing_message (msg)) continue ;
275+ if (web::is_hidden_goal_context_message (msg)) continue ;
270276 arr.push_back (web::chat_message_to_payload_json (msg));
271277 }
272278 return nlohmann::json{
@@ -661,24 +667,29 @@ void AgentLoop::emit_transcript_system_message(const std::string& content,
661667}
662668
663669bool AgentLoop::active_estimate_exceeds_auto_threshold () const {
664- auto [start, count] = get_messages_after_compact_boundary (messages_);
665- std::vector<ChatMessage> active (messages_.begin () + start,
666- messages_.begin () + start + count);
667- return estimate_message_tokens (active) > get_auto_compact_threshold (context_window_);
670+ return estimate_message_tokens (provider_relevant_messages (messages_)) >
671+ get_auto_compact_threshold (context_window_);
668672}
669673
670- void AgentLoop::apply_compact_result (const CompactResult& result) {
671- messages_ = result.compacted_messages ;
674+ void AgentLoop::apply_compact_result (const CompactResult& result, const std::string& trigger) {
675+ const int pre_tokens = estimate_message_tokens (provider_relevant_messages (messages_));
676+ std::vector<ChatMessage> replacement_history =
677+ provider_relevant_messages (result.compacted_messages );
678+ const int post_tokens = estimate_message_tokens (replacement_history);
679+
672680 last_api_prompt_tokens_.store (0 );
673681 if (session_manager_) {
674- session_manager_->replace_active_messages (messages_);
675- }
676-
677- events_.emit (SessionEventKind::TranscriptReplace,
678- build_transcript_replace_payload (messages_, result));
679- if (callbacks_.on_transcript_replace ) {
680- callbacks_.on_transcript_replace (messages_, result);
682+ CompactCheckpoint checkpoint;
683+ checkpoint.trigger = trigger;
684+ checkpoint.summary = result.summary_text ;
685+ checkpoint.messages_compressed = result.messages_compressed ;
686+ checkpoint.estimated_tokens_saved = result.estimated_tokens_saved ;
687+ checkpoint.pre_tokens = pre_tokens;
688+ checkpoint.post_tokens = post_tokens;
689+ checkpoint.replacement_history = replacement_history;
690+ session_manager_->append_compact_checkpoint (checkpoint);
681691 }
692+ messages_ = std::move (replacement_history);
682693}
683694
684695bool AgentLoop::maybe_run_auto_compact () {
@@ -690,16 +701,15 @@ bool AgentLoop::maybe_run_auto_compact() {
690701 " context_window=" + std::to_string (context_window_) +
691702 " last_api_prompt_tokens=" +
692703 std::to_string (last_api_prompt_tokens_.load ()));
693- dispatch_message (" system" ,
694- " [Auto-compact] Skipped after repeated compaction failures." ,
695- false );
704+ emit_transcript_system_message (
705+ " [Auto-compact] Skipped after repeated compaction failures." );
696706 return false ;
697707 }
698708
699- auto [ boundary_start, boundary_count] = get_messages_after_compact_boundary (messages_) ;
700- ( void )boundary_count ;
701- int pre_tokens = estimate_message_tokens (
702- std::vector<ChatMessage>(messages_. begin () + boundary_start, messages_. end ()) );
709+ const int boundary_start = 0 ;
710+ const auto active_for_estimate = provider_relevant_messages (messages_) ;
711+ const int boundary_count = static_cast < int >(active_for_estimate. size ());
712+ int pre_tokens = estimate_message_tokens (active_for_estimate );
703713 const int threshold = get_auto_compact_threshold (context_window_);
704714 LOG_INFO (" Auto-compact preflight; messages=" + std::to_string (messages_.size ()) +
705715 " active_start=" + std::to_string (boundary_start) +
@@ -718,9 +728,7 @@ bool AgentLoop::maybe_run_auto_compact() {
718728 auto outcome = dispatch_codex_hook (kCodexHookEventPreCompact , " auto" , payload);
719729 apply_hook_side_effects (outcome);
720730 if (outcome.continue_false || outcome.blocked || outcome.denied ) {
721- dispatch_message (" system" ,
722- " [Auto-compact] Stopped by hook." ,
723- false );
731+ emit_transcript_system_message (" [Auto-compact] Stopped by hook." );
724732 return false ;
725733 }
726734 }
@@ -732,30 +740,22 @@ bool AgentLoop::maybe_run_auto_compact() {
732740 micro_result.estimated_tokens_saved ,
733741 micro_result.cleared_tool_call_ids ));
734742 last_api_prompt_tokens_.store (0 );
735- if (session_manager_) {
736- session_manager_->replace_active_messages (messages_);
737- }
738-
739743 CompactResult replace_result;
740744 replace_result.performed = true ;
745+ replace_result.messages_compressed = micro_result.tool_results_cleared ;
741746 replace_result.estimated_tokens_saved = micro_result.estimated_tokens_saved ;
747+ replace_result.summary_text = " [Micro-compact] Cleared old tool results." ;
742748 replace_result.compacted_messages = messages_;
743- events_.emit (SessionEventKind::TranscriptReplace,
744- build_transcript_replace_payload (messages_, replace_result));
749+ apply_compact_result (replace_result, " auto" );
745750
746- dispatch_message (
747- " system" ,
751+ emit_transcript_system_message (
748752 " [Micro-compact] Cleared " +
749753 std::to_string (micro_result.tool_results_cleared ) +
750754 " old tool results, saved ~" +
751755 TokenTracker::format_tokens (micro_result.estimated_tokens_saved ) +
752- " tokens" ,
753- false );
756+ " tokens" );
754757
755- auto [after_start, after_count] = get_messages_after_compact_boundary (messages_);
756- std::vector<ChatMessage> active_after (messages_.begin () + after_start,
757- messages_.begin () + after_start + after_count);
758- const int after_tokens = estimate_message_tokens (active_after);
758+ const int after_tokens = estimate_message_tokens (provider_relevant_messages (messages_));
759759 const bool still_above_threshold = after_tokens > threshold;
760760 LOG_INFO (" Auto micro-compact result; cleared_tool_results=" +
761761 std::to_string (micro_result.tool_results_cleared ) +
@@ -789,9 +789,8 @@ bool AgentLoop::maybe_run_auto_compact() {
789789 {" label" , " Compacting conversation" },
790790 {" started_at_ms" , now_epoch_ms ()},
791791 });
792- dispatch_message (" system" ,
793- " [Auto-compact] Context approaching limit, compacting..." ,
794- false );
792+ emit_transcript_system_message (
793+ " [Auto-compact] Context approaching limit, compacting..." );
795794
796795 std::shared_ptr<LlmProvider> provider_snapshot;
797796 if (provider_accessor_) provider_snapshot = provider_accessor_ ();
@@ -802,9 +801,8 @@ bool AgentLoop::maybe_run_auto_compact() {
802801 std::to_string (auto_compact_consecutive_failures_) +
803802 " active_estimated_tokens=" + std::to_string (pre_tokens) +
804803 " threshold=" + std::to_string (threshold));
805- dispatch_message (" system" ,
806- " [Auto-compact] provider unavailable for compaction" ,
807- false );
804+ emit_transcript_system_message (
805+ " [Auto-compact] provider unavailable for compaction" );
808806 return false ;
809807 }
810808
@@ -826,7 +824,7 @@ bool AgentLoop::maybe_run_auto_compact() {
826824 " error=" + log_truncate (result.error , 500 ) +
827825 " active_estimated_tokens=" + std::to_string (pre_tokens) +
828826 " threshold=" + std::to_string (threshold));
829- dispatch_message ( " system " , " [Auto-compact] " + result.error , false );
827+ emit_transcript_system_message ( " [Auto-compact] " + result.error );
830828 return false ;
831829 }
832830
@@ -839,7 +837,7 @@ bool AgentLoop::maybe_run_auto_compact() {
839837 " estimated_tokens_saved=" +
840838 std::to_string (result.estimated_tokens_saved ) +
841839 " compacted_estimated_tokens=" + std::to_string (compacted_tokens));
842- apply_compact_result (result);
840+ apply_compact_result (result, " auto " );
843841 if (hook_manager_) {
844842 auto fields = build_hook_common_fields (kCodexHookEventPostCompact );
845843 auto payload = build_compact_hook_payload (fields, " auto" );
@@ -849,12 +847,10 @@ bool AgentLoop::maybe_run_auto_compact() {
849847 }
850848 auto_compact_consecutive_failures_ = 0 ;
851849
852- dispatch_message (
853- " system" ,
850+ emit_transcript_system_message (
854851 " [Auto-compact] Compacted " + std::to_string (result.messages_compressed ) +
855852 " messages, saved ~" +
856- TokenTracker::format_tokens (result.estimated_tokens_saved ) + " tokens" ,
857- false );
853+ TokenTracker::format_tokens (result.estimated_tokens_saved ) + " tokens" );
858854 return true ;
859855}
860856
@@ -1182,8 +1178,8 @@ AgentLoop::ApiRequestBundle AgentLoop::build_api_request_messages() {
11821178 bundle.tool_defs = tools_.get_tool_definitions ();
11831179 LOG_DEBUG (" Registered tools: " + std::to_string (bundle.tool_defs .size ()));
11841180
1185- // Prepare messages with system prompt at front, filtering out meta messages
1186- auto api_messages = normalize_messages_for_api (messages_);
1181+ // Prepare provider-facing messages with system prompt at front.
1182+ auto api_messages = provider_relevant_messages (messages_);
11871183 std::string session_context = cached_context_for_api (
11881184 build_session_context_prompt (
11891185 cwd_, memory_registry_, memory_cfg_, project_instructions_cfg_,
@@ -1329,8 +1325,10 @@ AgentLoop::ProviderCallResult AgentLoop::call_provider_and_collect(
13291325 callbacks_.on_stream_retry_reset ();
13301326 }
13311327 CompactResult reset_result;
1328+ std::vector<ChatMessage> visible_reset_messages =
1329+ session_manager_ ? session_manager_->load_active_messages () : messages_;
13321330 events_.emit (SessionEventKind::TranscriptReplace,
1333- build_transcript_replace_payload (messages_ , reset_result));
1331+ build_transcript_replace_payload (visible_reset_messages , reset_result));
13341332 }
13351333 emit_progress (
13361334 " model_retry" ,
@@ -1447,7 +1445,7 @@ AgentLoop::HandleErrorResult AgentLoop::handle_provider_error(
14471445 std::max (0 , rescue.estimated_tokens_before - rescue.estimated_tokens_after );
14481446 replace_result.summary_text = rescue.marker_text ;
14491447 replace_result.compacted_messages = std::move (rescue.compacted_messages );
1450- apply_compact_result (replace_result);
1448+ apply_compact_result (replace_result, " rescue " );
14511449
14521450 ++context_rescue_attempts;
14531451 last_context_rescue_tokens = rescue.estimated_tokens_after ;
@@ -1469,14 +1467,12 @@ AgentLoop::HandleErrorResult AgentLoop::handle_provider_error(
14691467 std::to_string (rescue.estimated_tokens_after ) +
14701468 " request_estimated_tokens_before=" +
14711469 std::to_string (request_tokens));
1472- dispatch_message (
1473- " system" ,
1470+ emit_transcript_system_message (
14741471 " [Rescue compact] Provider rejected the request as too large; compacted " +
14751472 std::to_string (rescue.messages_removed ) +
14761473 " messages and retrying with the most recent " +
14771474 std::to_string (rescue.protected_user_turns ) +
1478- " user turn(s)." ,
1479- false );
1475+ " user turn(s)." );
14801476 return HandleErrorResult::Continue;
14811477 }
14821478
@@ -2575,7 +2571,7 @@ void AgentLoop::run_compact() {
25752571 {" label" , " Compacting conversation" },
25762572 {" started_at_ms" , now_epoch_ms ()},
25772573 });
2578- dispatch_message ( " system " , " Compacting conversation..." , false );
2574+ emit_transcript_system_message ( " Compacting conversation..." );
25792575
25802576 auto finish = [this ]() {
25812577 if (callbacks_.on_busy_changed ) {
@@ -2592,7 +2588,7 @@ void AgentLoop::run_compact() {
25922588 auto outcome = dispatch_codex_hook (kCodexHookEventPreCompact , " manual" , payload);
25932589 apply_hook_side_effects (outcome);
25942590 if (outcome.continue_false || outcome.blocked || outcome.denied ) {
2595- dispatch_message ( " system " , " [Compact] Stopped by hook." , false );
2591+ emit_transcript_system_message ( " [Compact] Stopped by hook." );
25962592 finish ();
25972593 return ;
25982594 }
@@ -2620,7 +2616,7 @@ void AgentLoop::run_compact() {
26202616 return ;
26212617 }
26222618
2623- apply_compact_result (result);
2619+ apply_compact_result (result, " manual " );
26242620 if (hook_manager_) {
26252621 auto fields = build_hook_common_fields (kCodexHookEventPostCompact );
26262622 auto payload = build_compact_hook_payload (fields, " manual" );
@@ -2632,12 +2628,10 @@ void AgentLoop::run_compact() {
26322628 }
26332629 }
26342630
2635- dispatch_message (
2636- " system" ,
2631+ emit_transcript_system_message (
26372632 " Compacted " + std::to_string (result.messages_compressed ) +
26382633 " messages, saved ~" +
2639- std::to_string (result.estimated_tokens_saved ) + " tokens." ,
2640- false );
2634+ std::to_string (result.estimated_tokens_saved ) + " tokens." );
26412635 finish ();
26422636}
26432637
0 commit comments