diff --git a/nanobot/agent/memory.py b/nanobot/agent/memory.py index 263d77a9..e46d6cda 100644 --- a/nanobot/agent/memory.py +++ b/nanobot/agent/memory.py @@ -799,23 +799,22 @@ class Consolidator: messages: list[dict], *, session_key: str | None = None, - summary_context: list[dict] | None = None, + summary_messages: list[dict] | None = None, ) -> str | None: """Summarize messages via LLM and append to history.jsonl. ``messages`` are the messages being archived (removed from the live session); they are what gets raw-dumped if the LLM call fails. - ``summary_context``, when given, is fed to the summarizer in place of - ``messages`` so a caller can summarize over a wider window (e.g. the - full conversation tail) while still only archiving ``messages``. + ``summary_messages``, when given, lets callers include retained + messages in the summary without archiving them. Returns the summary text on success, None if nothing to archive. """ if not messages: return None - context = summary_context if summary_context is not None else messages + messages_to_summarize = summary_messages if summary_messages is not None else messages try: - formatted = MemoryStore._format_messages(context) + formatted = MemoryStore._format_messages(messages_to_summarize) formatted = self._truncate_to_token_budget(formatted) response = await self.provider.chat_with_retry( model=self.model, @@ -972,42 +971,38 @@ class Consolidator: self.sessions.invalidate(session_key) session = self.sessions.get_or_create(session_key) - tail = list(session.messages[session.last_consolidated:]) - if not tail: + messages_to_summarize = list(session.messages[session.last_consolidated:]) + if not messages_to_summarize: session.updated_at = datetime.now() self.sessions.save(session) return "" probe = Session( key=session.key, - messages=tail.copy(), + messages=messages_to_summarize.copy(), created_at=session.created_at, updated_at=session.updated_at, metadata={}, last_consolidated=0, ) dropped, already_consolidated = probe.retain_recent_legal_suffix(max_suffix) - kept = probe.messages - archive_msgs = dropped[already_consolidated:] + messages_to_keep = probe.messages + messages_to_remove = dropped[already_consolidated:] - if not archive_msgs and not kept: + if not messages_to_remove and not messages_to_keep: session.updated_at = datetime.now() self.sessions.save(session) return "" last_active = session.updated_at summary: str | None = "" - if archive_msgs: - # Summarize over the full unconsolidated tail — including the - # recent suffix we retain — not just the dropped prefix. Idle - # compaction usually runs on a finished conversation, so a late - # user correction or final result that landed in the kept suffix - # must still reach the persisted summary; otherwise history keeps - # the stale pre-correction conclusion that never gets fixed - # (#4264). Only archive_msgs are removed/raw-dumped; kept stays - # in the session. + if messages_to_remove: + # Summarize the retained suffix too, but only remove/raw-dump + # the messages that are no longer kept in the live session. summary = await self.archive( - archive_msgs, session_key=session_key, summary_context=tail + messages_to_remove, + session_key=session_key, + summary_messages=messages_to_summarize, ) if summary and summary != "(nothing)": @@ -1016,17 +1011,17 @@ class Consolidator: "last_active": last_active.isoformat(), } - session.messages = kept + session.messages = messages_to_keep session.last_consolidated = 0 session.updated_at = datetime.now() self.sessions.save(session) - if archive_msgs: + if messages_to_remove: logger.info( "Idle-session compact for {}: archived={}, kept={}, summary={}", session_key, - len(archive_msgs), - len(kept), + len(messages_to_remove), + len(messages_to_keep), bool(summary), )