From 69c129739628c14a5b57297d986402da0e981f77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Mayoral=20Vilches?= Date: Fri, 20 Jun 2025 09:54:00 +0000 Subject: [PATCH] Integrate changes and various fixes, towards 0.5.1 --- src/cai/repl/commands/compact.py | 45 ++++++++++--------- src/cai/repl/commands/memory.py | 34 +++++++++----- .../agents/models/openai_chatcompletions.py | 25 +++++++++-- src/cai/sdk/agents/simple_agent_manager.py | 23 ++++++++-- 4 files changed, 88 insertions(+), 39 deletions(-) diff --git a/src/cai/repl/commands/compact.py b/src/cai/repl/commands/compact.py index 2db5d219..f18d182e 100644 --- a/src/cai/repl/commands/compact.py +++ b/src/cai/repl/commands/compact.py @@ -598,42 +598,47 @@ class CompactCommand(Command): original_model = os.environ.get("CAI_MODEL", "alias0") os.environ["CAI_MODEL"] = self.compact_model try: - result = MEMORY_COMMAND_INSTANCE.handle_save([memory_name]) + result = MEMORY_COMMAND_INSTANCE.handle_save([memory_name], preserve_history=False) finally: os.environ["CAI_MODEL"] = original_model else: - result = MEMORY_COMMAND_INSTANCE.handle_save([memory_name]) + result = MEMORY_COMMAND_INSTANCE.handle_save([memory_name], preserve_history=False) if result: console.print(f"\n[green]✓ Conversation compacted successfully![/green]") console.print("[dim]The memory has been saved and applied to the agent[/dim]") console.print("[dim]Use '/memory list' to see all saved memories[/dim]") - # Clear the agent's message history after successful compaction + # IMPORTANT: Explicitly clear the history after compaction + # The handle_save with preserve_history=False doesn't always clear properly console.print("\n[cyan]Clearing conversation history...[/cyan]") - # Find the matching model instance - model_instance = None - for (name, inst_id), model_ref in ACTIVE_MODEL_INSTANCES.items(): - if name == agent_name: - model = model_ref() if model_ref else None - if model: - model_instance = model - break - - if model_instance: - # Clear the model's message history - model_instance.message_history.clear() - # Reset context usage since we cleared the history - os.environ['CAI_CONTEXT_USAGE'] = '0.0' - console.print("[green]✓ Conversation history cleared[/green]") + # Clear using AGENT_MANAGER (this uses .clear() to maintain reference) + AGENT_MANAGER.clear_history(agent_name) # Also clear persistent history if agent_name in PERSISTENT_MESSAGE_HISTORIES: PERSISTENT_MESSAGE_HISTORIES[agent_name].clear() - # Clear in AGENT_MANAGER as well using the proper method - AGENT_MANAGER.clear_history(agent_name) + # Get the current active agent and clear its model history too + current_agent = AGENT_MANAGER.get_active_agent() + if current_agent and hasattr(current_agent, 'model') and hasattr(current_agent.model, 'message_history'): + current_agent.model.message_history.clear() + + # Reset context usage since we cleared the history + os.environ['CAI_CONTEXT_USAGE'] = '0.0' + console.print("[green]✓ Conversation history cleared[/green]") + + # Debug: Verify histories are actually cleared + if os.getenv("CAI_DEBUG", "1") == "2": + # Check AGENT_MANAGER + manager_history = AGENT_MANAGER.get_message_history(agent_name) + console.print(f"[dim]Debug: AGENT_MANAGER history length: {len(manager_history)}[/dim]") + + # Check active agent (re-fetch to ensure we have the current one) + current_active_agent = AGENT_MANAGER.get_active_agent() + if current_active_agent and hasattr(current_active_agent, 'model') and hasattr(current_active_agent.model, 'message_history'): + console.print(f"[dim]Debug: Active agent model history length: {len(current_active_agent.model.message_history)}[/dim]") else: console.print(f"[red]Failed to compact conversation[/red]") diff --git a/src/cai/repl/commands/memory.py b/src/cai/repl/commands/memory.py index 36298649..5efdbbac 100644 --- a/src/cai/repl/commands/memory.py +++ b/src/cai/repl/commands/memory.py @@ -396,7 +396,7 @@ class MemoryCommand(Command): return True - def handle_save(self, args: Optional[List[str]] = None) -> bool: + def handle_save(self, args: Optional[List[str]] = None, preserve_history: bool = True) -> bool: """Save current agent history as memory.""" if not args: # Use current active agent @@ -471,7 +471,7 @@ Model: {get_compact_model() or os.environ.get("CAI_MODEL", "gpt-4")} os.environ['CAI_MEMORY'] = 'true' # Reload the agent with the new memory - self._reload_agent_with_memory(agent_name) + self._reload_agent_with_memory(agent_name, preserve_history=preserve_history) # Show memory panel console.print(Panel( @@ -1085,7 +1085,7 @@ Model: {get_compact_model() or os.environ.get("CAI_MODEL", "gpt-4")} console.print(f"[green]✓ Cleared history for {agent_name}[/green]") # Reload the agent with the new memory - self._reload_agent_with_memory(agent_name) + self._reload_agent_with_memory(agent_name, preserve_history=preserve_history) # Show memory panel console.print(Panel( @@ -1357,8 +1357,14 @@ This session is being continued from a previous conversation that ran out of con return None - def _reload_agent_with_memory(self, agent_name: str): - """Reload an agent to apply memory changes while preserving message history.""" + def _reload_agent_with_memory(self, agent_name: str, preserve_history: bool = True): + """Reload an agent to apply memory changes. + + Args: + agent_name: Name of the agent to reload + preserve_history: Whether to preserve message history (default True). + Set to False when called from compact to avoid restoring cleared history. + """ try: # Get the current agent instance and its history from cai.sdk.agents.simple_agent_manager import AGENT_MANAGER @@ -1385,12 +1391,15 @@ This session is being continued from a previous conversation that ran out of con return # Get the current agent's message history before reloading - current_history = get_agent_message_history(agent_name) - if current_history: - # Store a copy of the history - history_backup = list(current_history) + history_backup = [] + if preserve_history: + current_history = get_agent_message_history(agent_name) + if current_history: + # Store a copy of the history + history_backup = list(current_history) else: - history_backup = [] + # When not preserving history (e.g., from compact), clear it before creating new agent + AGENT_MANAGER.clear_history(agent_name) # Get the agent ID agent_id = AGENT_MANAGER.get_id_by_name(agent_name) @@ -1412,7 +1421,7 @@ This session is being continued from a previous conversation that ran out of con AGENT_MANAGER.set_parallel_agent(agent_id, new_agent, agent_name) # Restore the message history to the new agent instance - if hasattr(new_agent, 'model') and hasattr(new_agent.model, 'message_history'): + if preserve_history and hasattr(new_agent, 'model') and hasattr(new_agent.model, 'message_history'): # The switch_to_single_agent might have already transferred history # Only restore if the new agent's history is empty or different if not new_agent.model.message_history and history_backup: @@ -1425,7 +1434,8 @@ This session is being continued from a previous conversation that ran out of con # Also update PERSISTENT_MESSAGE_HISTORIES if needed if agent_name in PERSISTENT_MESSAGE_HISTORIES: PERSISTENT_MESSAGE_HISTORIES[agent_name].clear() - PERSISTENT_MESSAGE_HISTORIES[agent_name].extend(history_backup) + if preserve_history: + PERSISTENT_MESSAGE_HISTORIES[agent_name].extend(history_backup) # Update the global active agent in CLI if we're in single agent mode if agent_id == "P1": diff --git a/src/cai/sdk/agents/models/openai_chatcompletions.py b/src/cai/sdk/agents/models/openai_chatcompletions.py index 23a2a9e9..e7ae54e6 100644 --- a/src/cai/sdk/agents/models/openai_chatcompletions.py +++ b/src/cai/sdk/agents/models/openai_chatcompletions.py @@ -404,8 +404,17 @@ class OpenAIChatCompletionsModel(Model): else: self.message_history = [] else: - # Start with empty history - do not inherit from previous agents - self.message_history = [] + # Get or create history from AGENT_MANAGER to ensure we share the same list reference + # This is critical for proper history clearing to work + existing_history = AGENT_MANAGER.get_message_history(self.agent_name) + if existing_history is not None and isinstance(existing_history, list): + # Use the existing list reference from AGENT_MANAGER + self.message_history = existing_history + else: + # Create new history and ensure AGENT_MANAGER has it too + self.message_history = [] + if self.agent_name not in AGENT_MANAGER._message_history: + AGENT_MANAGER._message_history[self.agent_name] = self.message_history # Register with SimpleAgentManager only when explicitly created # This prevents phantom instances during module imports @@ -415,6 +424,11 @@ class OpenAIChatCompletionsModel(Model): AGENT_MANAGER.set_parallel_agent(agent_id, self, self.agent_name) else: AGENT_MANAGER.set_active_agent(self, self.agent_name, agent_id) + + # CRITICAL: Ensure AGENT_MANAGER uses the same list reference as the model + # This is necessary for proper history clearing to work + if not PARALLEL_ISOLATION.is_parallel_mode(): + AGENT_MANAGER._message_history[self.agent_name] = self.message_history # Instance-based converter self._converter = _Converter() @@ -488,8 +502,11 @@ class OpenAIChatCompletionsModel(Model): if not is_duplicate: self.message_history.append(msg) - # Also update SimpleAgentManager - AGENT_MANAGER.add_to_history(self.agent_name, msg) + # Also update SimpleAgentManager ONLY if they're not the same list reference + # This avoids double-adding when they share the same list + manager_history = AGENT_MANAGER.get_message_history(self.agent_name) + if manager_history is not self.message_history: + AGENT_MANAGER.add_to_history(self.agent_name, msg) # Update isolated history if in parallel mode if PARALLEL_ISOLATION.is_parallel_mode() and self.agent_id: PARALLEL_ISOLATION.update_isolated_history(self.agent_id, msg) diff --git a/src/cai/sdk/agents/simple_agent_manager.py b/src/cai/sdk/agents/simple_agent_manager.py index 73930aba..0ea4a5f4 100644 --- a/src/cai/sdk/agents/simple_agent_manager.py +++ b/src/cai/sdk/agents/simple_agent_manager.py @@ -75,7 +75,11 @@ class SimpleAgentManager: # Initialize message history for this agent if needed if agent_name not in self._message_history: - self._message_history[agent_name] = [] + # Check if the agent's model already has a history and use that reference + if hasattr(agent, 'model') and hasattr(agent.model, 'message_history'): + self._message_history[agent_name] = agent.model.message_history + else: + self._message_history[agent_name] = [] def get_active_agent(self): """Get the active agent instance.""" @@ -100,7 +104,16 @@ class SimpleAgentManager: def clear_history(self, agent_name: str): """Clear history for an agent.""" if agent_name in self._message_history: - self._message_history[agent_name] = [] + # Clear the list in-place to maintain the same reference + # This is critical when the model and manager share the same list + self._message_history[agent_name].clear() + + # Also clear the active agent's model instance history if it matches + # This handles cases where they don't share the same reference + if self._active_agent and self._active_agent_name == agent_name: + agent = self._active_agent() + if agent and hasattr(agent, 'model') and hasattr(agent.model, 'message_history'): + agent.model.message_history.clear() def clear_all_histories(self): """Clear all message histories.""" @@ -186,7 +199,11 @@ class SimpleAgentManager: # Initialize message history for this agent if needed if agent_name not in self._message_history: - self._message_history[agent_name] = [] + # Check if the agent's model already has a history and use that reference + if hasattr(agent, 'model') and hasattr(agent.model, 'message_history'): + self._message_history[agent_name] = agent.model.message_history + else: + self._message_history[agent_name] = [] def clear_parallel_agents(self): """Clear all parallel agents (when switching to single agent mode)."""