Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion agent/background_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,11 @@ def _bg_review_auto_deny(command, description, **kwargs):
finally:
clear_thread_tool_whitelist()

# Snapshot review actions before teardown. close() is allowed to
# clean per-session state, but the user-visible self-improvement
# summary still needs the completed review agent's tool results.
review_messages = list(getattr(review_agent, "_session_messages", []))

# Tear down memory providers while stdout is still
# redirected so background thread teardown (Honcho flush,
# Hindsight sync, etc.) stays silent. The finally block
Expand All @@ -489,7 +494,6 @@ def _bg_review_auto_deny(command, description, **kwargs):
review_agent.close()
except Exception:
pass
review_messages = list(getattr(review_agent, "_session_messages", []))
review_agent = None

# Scan the review agent's messages for successful tool actions
Expand Down
18 changes: 18 additions & 0 deletions tests/agent/test_context_compressor_summary_continuity.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,21 @@ def test_resume_rehydrates_previous_summary_from_handoff_message():
assert "TURNS TO SUMMARIZE:" not in prompt
assert prompt.count(old_summary) == 1
assert f"[USER]: {SUMMARY_PREFIX}" not in prompt


def test_handoff_in_protected_head_populates_previous_summary_before_update():
"""A resumed protected-head handoff should restore iterative-summary state."""
compressor = _compressor()
old_summary = "PROTECTED-HEAD-SUMMARY durable facts from before restart"
seen_turns = []

def fake_generate_summary(turns_to_summarize, focus_topic=None):
seen_turns.extend(turns_to_summarize)
return "new summary from resumed turns"

with patch.object(compressor, "_generate_summary", side_effect=fake_generate_summary):
compressor.compress(_messages_with_handoff(old_summary))

assert compressor._previous_summary == old_summary
assert seen_turns
assert all(old_summary not in str(msg.get("content", "")) for msg in seen_turns)
63 changes: 63 additions & 0 deletions tests/run_agent/test_background_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

import agent.background_review as background_review_module
import run_agent as run_agent_module
from run_agent import AIAgent

Expand Down Expand Up @@ -132,6 +133,68 @@ def close(self):
)


def test_background_review_summarizer_receives_captured_messages_after_close(monkeypatch):
"""The action summarizer must see review messages even after close cleanup."""
import json

review_tool_message = {
"role": "tool",
"tool_call_id": "call_bg",
"content": json.dumps(
{"success": True, "message": "Entry added", "target": "memory"}
),
}
captured: dict = {}
events: list[str] = []

class FakeReviewAgent:
def __init__(self, **kwargs):
self._session_messages = []

def run_conversation(self, **kwargs):
events.append("run_conversation")
self._session_messages = [review_tool_message]

def shutdown_memory_provider(self):
events.append("shutdown_memory_provider")

def close(self):
events.append("close")
self._session_messages = []

def fake_summarize(review_messages, prior_snapshot):
events.append("summarize")
captured["review_messages"] = list(review_messages)
captured["prior_snapshot"] = list(prior_snapshot)
return []

monkeypatch.setattr(run_agent_module, "AIAgent", FakeReviewAgent)
monkeypatch.setattr(run_agent_module.threading, "Thread", ImmediateThread)
monkeypatch.setattr(
background_review_module,
"summarize_background_review_actions",
fake_summarize,
)

messages_snapshot = [{"role": "user", "content": "hi"}]
agent = _bare_agent()

AIAgent._spawn_background_review(
agent,
messages_snapshot=messages_snapshot,
review_memory=True,
)

assert events == [
"run_conversation",
"shutdown_memory_provider",
"close",
"summarize",
]
assert captured["review_messages"] == [review_tool_message]
assert captured["prior_snapshot"] == messages_snapshot


def test_background_review_summary_is_attributed_to_self_improvement_loop(monkeypatch):
"""The CLI/gateway emission must identify the self-improvement loop.

Expand Down
Loading