diff --git a/agent.py b/agent.py index 0477c3af..21e06b24 100644 --- a/agent.py +++ b/agent.py @@ -98,7 +98,7 @@ def _append_tool_result( "content": content, } ) - session.history_dirty = True + session.mark_dirty() def _turn_state(user_msg: Optional[Any], session: Session, memory_service: Any) -> dict: @@ -155,7 +155,7 @@ def _turn_state(user_msg: Optional[Any], session: Session, memory_service: Any) state["experience_update_start_index"] = len(session.message_history) session.message_history.append({"role": "user", "content": user_msg}) - session.history_dirty = True + session.mark_dirty() log_agent(session.user_id, "IN", user_msg, Fore.CYAN) if _is_gui_interaction_request(user_msg): @@ -316,7 +316,7 @@ async def run_and_store(name=func_name, arguments=args, call_id=tc_id): results = await asyncio.gather(*tasks) for r in results: session.message_history.append(r) - session.history_dirty = True + session.mark_dirty() if session.pending_confirmations: if signal_handler: @@ -582,7 +582,7 @@ def _append_assistant_round(session: Session, round_result: dict) -> bool: msg_dict["tool_calls"] = valid_tool_calls session.message_history.append(msg_dict) - session.history_dirty = True + session.mark_dirty() return bool(valid_tool_calls) @@ -616,7 +616,7 @@ async def _finalize_or_request_more_work( "If `validation_pytest` is missing, run at least one `pytest` command via `run_bash`." ) session.message_history.append({"role": "user", "content": guidance}) - session.history_dirty = True + session.mark_dirty() return None if turn_state["experience_update_active"]: @@ -630,7 +630,7 @@ async def _finalize_or_request_more_work( "Then provide confirmation that the experience was actually stored." ) session.message_history.append({"role": "user", "content": guidance}) - session.history_dirty = True + session.mark_dirty() return None if memory_service and user_msg: @@ -777,7 +777,7 @@ async def resolve_confirmations( "content": result, } ) - session.history_dirty = True + session.mark_dirty() # After resolving, run the agent turn again to process results return await run_agent_turn(None, session, signal_handler=signal_handler) @@ -814,7 +814,7 @@ async def deny_confirmations(session: Session, deny_all: bool = True) -> None: "content": "Action denied by user.", } ) - session.history_dirty = True + session.mark_dirty() def main(): diff --git a/bot.py b/bot.py index 010995ab..5b13f797 100644 --- a/bot.py +++ b/bot.py @@ -529,7 +529,7 @@ async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE): "content": "Action denied by user.", } ) - session.history_dirty = True + session.mark_dirty() count = len(session.pending_confirmations) session.pending_confirmations = [] session_manager.save(user_id) diff --git a/cli.py b/cli.py index 4711ee2e..18da349e 100644 --- a/cli.py +++ b/cli.py @@ -152,7 +152,7 @@ async def cli_signal_handler(msg: str): "content": result }) - session.history_dirty = True + session.mark_dirty() session_manager.save(user_id) is_resume = True # Continue the same turn continue @@ -180,7 +180,7 @@ async def cli_signal_handler(msg: str): "name": e.action, "content": "Action denied by user." }) - session.history_dirty = True + session.mark_dirty() session_manager.save(user_id) is_resume = True continue diff --git a/commit_message.txt b/commit_message.txt new file mode 100644 index 00000000..dcb9ca86 --- /dev/null +++ b/commit_message.txt @@ -0,0 +1,3 @@ +refactor: use mark_dirty() method instead of direct attribute assignment + +Replaces all direct assignments to `session.history_dirty = True` with the encapsulated `session.mark_dirty()` method call to properly enforce the performance semantics documented in the Session dataclass. diff --git a/desktop/api_bridge.py b/desktop/api_bridge.py index 253a1733..b3e7d353 100644 --- a/desktop/api_bridge.py +++ b/desktop/api_bridge.py @@ -633,7 +633,7 @@ async def handle_command(request: web.Request) -> web.Response: }) count = len(session.pending_confirmations) session.pending_confirmations = [] - session.history_dirty = True + session.mark_dirty() session_manager.save(user_id) result = f"✅ {count} pending action(s) cancelled." else: diff --git a/prompt_builder.py b/prompt_builder.py index d57fe2b0..2ea8dceb 100644 --- a/prompt_builder.py +++ b/prompt_builder.py @@ -255,7 +255,7 @@ async def _compact_history(session: Session, router: LLMRouter) -> None: new_history.extend(last_messages) session.message_history = new_history - session.history_dirty = True + session.mark_dirty() log_agent( session.user_id, "COMPACT", "History successfully compacted.", Fore.GREEN ) @@ -397,7 +397,7 @@ def _inject_system_directive(session: Session, directive: str) -> None: if directive not in content: session.message_history[0]["content"] = content + "\n\n" + directive # System prompt content changed; sanitize output may differ. - session.history_dirty = True + session.mark_dirty() def _extract_memory_lines(results: Any, limit: int = 6) -> List[str]: @@ -547,7 +547,7 @@ def _sync_basic_facts_into_system_prompt( ) if updated != content: session.message_history[0]["content"] = updated - session.history_dirty = True + session.mark_dirty() def _build_memory_context( @@ -874,7 +874,7 @@ def _merge_memory_context_into_system_prompt( if session.message_history[0].get("role") != "system": session.message_history.insert(0, get_initial_messages()[0]) - session.history_dirty = True + session.mark_dirty() base_content = str(session.message_history[0].get("content") or "") @@ -890,7 +890,7 @@ def _merge_memory_context_into_system_prompt( ) if merged != str(session.message_history[0].get("content") or ""): session.message_history[0]["content"] = merged - session.history_dirty = True + session.mark_dirty() def _normalize_single_system_message(session: Session) -> None: @@ -951,7 +951,7 @@ def _normalize_single_system_message(session: Session) -> None: if changed: session.message_history = normalized - session.history_dirty = True + session.mark_dirty() def _extract_tool_path(args: dict) -> str: