Skip to content
Open
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 src/agents/memory/openai_responses_compaction_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,11 @@ async def _ensure_compaction_candidates(
if self._compaction_candidate_items is not None and self._session_items is not None:
return (self._compaction_candidate_items[:], self._session_items[:])

history = _normalize_compaction_session_items(await self.underlying_session.get_items())
# Bypass SessionSettings.limit so compaction sees stored history, not just the
# retrieval window. Replacement still writes over the full store.
history = _normalize_compaction_session_items(
await self._get_all_underlying_session_items()
)
Comment on lines +447 to +449

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid triggering response-ID compaction from hidden rows

When the underlying session has SessionSettings(limit=N) with N below the threshold but more than ten candidates stored, this full read now triggers default "auto" compaction; however, _resolve_compaction_mode() selects previous_response_id for a normally stored response, so the compaction request does not include these full session_items. Because that response was created using only the limited session window, its compacted output cannot represent the older rows, yet the replacement clears the entire local store. This therefore newly deletes the hidden history in the default mode; either switch to input-mode compaction when the full store differs from the retrieval window or keep hidden rows from triggering response-ID compaction.

AGENTS.md reference: AGENTS.md:L201-L203

Useful? React with 👍 / 👎.

candidates = select_compaction_candidate_items(history)
self._compaction_candidate_items = candidates
self._session_items = history
Expand Down
44 changes: 44 additions & 0 deletions tests/memory/test_openai_responses_compaction_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,50 @@ async def clear_session(self) -> None:
assert failing_session.clear_calls == 2
assert failing_session.add_calls == 2

@pytest.mark.asyncio
async def test_run_compaction_input_uses_full_history_when_session_limit_applies(
self, tmp_path
) -> None:
history: list[TResponseInputItem] = [
cast(TResponseInputItem, {"type": "message", "role": "user", "content": "oldest"}),
cast(
TResponseInputItem,
{"type": "message", "role": "assistant", "content": "middle"},
),
cast(TResponseInputItem, {"type": "message", "role": "user", "content": "newest"}),
]
compacted_items: list[TResponseInputItem] = [
cast(
TResponseInputItem,
{"type": "message", "role": "assistant", "content": "compacted"},
)
]

underlying = SQLiteSession(
"limited-compact",
str(tmp_path / "limited_compact.db"),
session_settings=SessionSettings(limit=2),
)
await underlying.add_items(history)
assert len(await underlying.get_items()) == 2

mock_compact_response = MagicMock()
mock_compact_response.output = compacted_items
mock_client = MagicMock()
mock_client.responses.compact = AsyncMock(return_value=mock_compact_response)

session = OpenAIResponsesCompactionSession(
session_id="test",
underlying_session=underlying,
client=mock_client,
compaction_mode="input",
)

await session.run_compaction({"force": True})

compact_input = mock_client.responses.compact.call_args.kwargs["input"]
assert compact_input == history

@pytest.mark.asyncio
async def test_run_compaction_does_not_restore_when_clear_fails_without_mutation(
self,
Expand Down