|
Download plans/agent-context-compaction-2025-03-25-concise.md from SaylorTwift/forgecode: direct link, hf CLI and curl.
- Browser
- Download file 7.89 kB
-
https://huggingface.co/SaylorTwift/forgecode/resolve/main/plans/agent-context-compaction-2025-03-25-concise.md
- Command line
-
hf download hf://SaylorTwift/forgecode/plans/agent-context-compaction-2025-03-25-concise.md
-
curl -L -o agent-context-compaction-2025-03-25-concise.md https://huggingface.co/SaylorTwift/forgecode/resolve/main/plans/agent-context-compaction-2025-03-25-concise.md
7.89 kB
| # Context Compaction Logic Update Plan (Revised) | |
| ## Objective | |
| Modify the context compaction logic in `crates/forge_domain/src/compaction.rs` to identify and compress only one continuous sequence of assistant messages at a time, rather than processing all sequences at once or compressing the entire context. | |
| ## Current Implementation Analysis | |
| The current implementation in `compaction.rs` performs compaction on the entire context as a single unit: | |
| 1. It checks if compaction is needed via `should_perform_compaction` | |
| 2. If needed, it generates a summary of the entire context | |
| 3. It then builds a new compacted context with just the summary | |
| This approach doesn't distinguish between different message roles or sequences, treating all messages equally in the compaction process. | |
| ## Revised Implementation Plan | |
| ### 1. Identify Single Compressible Sequence | |
| Add functionality to identify only the first sequence of assistant messages that qualify for compression: | |
| ```rust | |
| /// Identifies the first sequence of assistant messages that can be compressed (2+ consecutive messages) | |
| fn identify_first_compressible_sequence(&self, context: &Context) -> Option<(usize, usize)> { | |
| let messages = context.messages(); | |
| let mut current_sequence_start: Option<usize> = None; | |
| for (i, message) in messages.iter().enumerate() { | |
| if message.is_assistant() { | |
| // Start a new sequence or continue current one | |
| if current_sequence_start.is_none() { | |
| current_sequence_start = Some(i); | |
| } | |
| } else { | |
| // End of a potential sequence | |
| if let Some(start) = current_sequence_start { | |
| // Only compress sequences with more than 1 assistant message | |
| if i - start > 1 { | |
| return Some((start, i - 1)); | |
| } | |
| current_sequence_start = None; | |
| } | |
| } | |
| } | |
| // Check for a sequence at the end | |
| if let Some(start) = current_sequence_start { | |
| let end = messages.len() - 1; | |
| if end - start > 0 { // More than 1 message | |
| return Some((start, end)); | |
| } | |
| } | |
| None // No compressible sequence found | |
| } | |
| ``` | |
| ### 2. Update Main Compaction Method | |
| Modify the main compaction method to identify and compress just one sequence: | |
| ```rust | |
| pub async fn compact_context(&self, agent: &Agent, context: Context) -> Result<Context> { | |
| if !self.should_perform_compaction(agent, &context) { | |
| return Ok(context); | |
| } | |
| debug!( | |
| agent_id = %agent.id, | |
| "Context compaction triggered" | |
| ); | |
| // Identify the first compressible sequence | |
| if let Some(sequence) = self.identify_first_compressible_sequence(&context) { | |
| debug!( | |
| agent_id = %agent.id, | |
| sequence_start = sequence.0, | |
| sequence_end = sequence.1, | |
| "Compressing assistant message sequence" | |
| ); | |
| // Compress just this sequence | |
| self.compress_single_sequence(agent, context, sequence).await | |
| } else { | |
| debug!(agent_id = %agent.id, "No compressible sequences found"); | |
| Ok(context) | |
| } | |
| } | |
| ``` | |
| ### 3. Implement Single Sequence Compression | |
| Create a method to handle the compression of a single identified sequence: | |
| ```rust | |
| async fn compress_single_sequence( | |
| &self, | |
| agent: &Agent, | |
| original_context: Context, | |
| sequence: (usize, usize) | |
| ) -> Result<Context> { | |
| let messages = original_context.messages(); | |
| let (start, end) = sequence; | |
| // Extract the sequence to summarize | |
| let sequence_messages = &messages[start..=end]; | |
| // Generate summary for this sequence | |
| let summary = self.generate_summary_for_sequence(agent, sequence_messages).await?; | |
| // Build a new context with the sequence replaced by the summary | |
| let mut compacted_messages = Vec::new(); | |
| // Add messages before the sequence | |
| compacted_messages.extend(messages[0..start].to_vec()); | |
| // Add the summary as a single assistant message | |
| compacted_messages.push(ContextMessage::assistant(summary, None)); | |
| // Add messages after the sequence | |
| if end + 1 < messages.len() { | |
| compacted_messages.extend(messages[end+1..].to_vec()); | |
| } | |
| // Build the new context | |
| let mut compacted_context = Context::default(); | |
| // Add system message if present in original context | |
| if let Some(system_msg) = original_context.system_message() { | |
| compacted_context = compacted_context.set_first_system_message(system_msg.clone()); | |
| } | |
| // Add all the processed messages | |
| for msg in compacted_messages { | |
| compacted_context = compacted_context.add_message(msg); | |
| } | |
| Ok(compacted_context) | |
| } | |
| ``` | |
| ### 4. Implement Sequence-Based Summary Generation | |
| Create a method to generate summaries for a specific sequence: | |
| ```rust | |
| async fn generate_summary_for_sequence( | |
| &self, | |
| agent: &Agent, | |
| messages: &[ContextMessage] | |
| ) -> Result<String> { | |
| let compact = agent.compact.as_ref().unwrap(); | |
| // Create a temporary context with just the sequence for summarization | |
| let mut sequence_context = Context::default(); | |
| for msg in messages { | |
| sequence_context = sequence_context.add_message(msg.clone()); | |
| } | |
| // Render the summarization prompt | |
| let prompt = self | |
| .services | |
| .template_service() | |
| .render_summarization(agent, &sequence_context) | |
| .await?; | |
| let message = ContextMessage::user(prompt); | |
| let summary_context = Context::default().add_message(message); | |
| // Get summary from the provider | |
| let response = self | |
| .services | |
| .provider_service() | |
| .chat(&compact.model, summary_context) | |
| .await?; | |
| self.collect_completion_stream_content(response).await | |
| } | |
| ``` | |
| ### 5. Update Helper Methods | |
| The `should_perform_compaction` method remains unchanged since it's still applicable. The `collect_completion_stream_content` also remains as is. | |
| ### 6. Remove Unused Methods | |
| The original `generate_summary` and `build_compacted_context` methods will be replaced by our new sequence-based methods, so they can be removed or repurposed. | |
| ## Implementation Considerations | |
| 1. **Processing Only One Sequence**: This approach only compresses one sequence at a time, which means that if there are multiple compressible sequences, only the first one will be compressed in a single call to `compact_context`. | |
| 2. **Repeated Compaction**: If desired, the caller can repeatedly call `compact_context` to compress additional sequences over multiple iterations. | |
| 3. **Processing Order**: Sequences are identified from the beginning of the context, so the first eligible sequence found will be compressed first. | |
| 4. **Message Order Preservation**: This approach preserves the order of all non-compressed messages, maintaining the conversation flow. | |
| ## Verification Criteria | |
| 1. **Correctness**: The compacted context should: | |
| - Preserve all user messages in their original positions | |
| - Replace only the first sequence of 2+ assistant messages with a summary | |
| - Keep single assistant messages unchanged | |
| - Keep other sequences of assistant messages unchanged (they are not processed in this call) | |
| 2. **Functionality**: | |
| - The compaction should trigger under the same conditions as before | |
| - Summary generation should work properly for the identified sequence | |
| - The context structure should be preserved | |
| 3. **Edge Cases**: | |
| - Empty contexts should be handled properly | |
| - Contexts with no compressible sequences should be returned unchanged | |
| - Boundary conditions (sequence at start/end of context) should work correctly | |
| 4. **Performance**: | |
| - This approach should be more efficient as it only processes one sequence per call | |
| - Only one API call for summarization is made per call to `compact_context` |