From 63eaf605599bf2c6fbb1a59c302c34790801149e Mon Sep 17 00:00:00 2001 From: Stephen Schoettler Date: Thu, 14 May 2026 18:45:31 -0700 Subject: [PATCH] test: cover ci-unblocker production regressions --- run_agent.py | 7 ++- ...t_context_compressor_summary_continuity.py | 18 ++++++ tests/run_agent/test_background_review.py | 62 +++++++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/run_agent.py b/run_agent.py index 85c1128d68e..08a99e59dfb 100644 --- a/run_agent.py +++ b/run_agent.py @@ -4386,6 +4386,12 @@ class AIAgent: 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 @@ -4398,7 +4404,6 @@ class AIAgent: 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 diff --git a/tests/agent/test_context_compressor_summary_continuity.py b/tests/agent/test_context_compressor_summary_continuity.py index d797b661f01..f3101913ceb 100644 --- a/tests/agent/test_context_compressor_summary_continuity.py +++ b/tests/agent/test_context_compressor_summary_continuity.py @@ -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) diff --git a/tests/run_agent/test_background_review.py b/tests/run_agent/test_background_review.py index 2e79b10b346..91eee9a798c 100644 --- a/tests/run_agent/test_background_review.py +++ b/tests/run_agent/test_background_review.py @@ -132,6 +132,68 @@ def test_background_review_installs_auto_deny_approval_callback(monkeypatch): ) +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( + AIAgent, + "_summarize_background_review_actions", + staticmethod(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.