Is your feature request related to a problem? Please describe.
MemoryInterface.get_scenario_run_history_page() combines filter normalization, cursor pagination, SQL column selection, row conversion, and aggregate lookup inside the same large class that handles messages, scores, seeds, and other persistence operations. The history-page portion is a cohesive unit that can be understood separately without changing the public API.
This is M1, the first of three small scenario-history extraction steps. It is ready for implementation. Later steps cover attempt-to-work-unit matching and aggregate counters; do not include those extractions here.
Describe the solution you'd like
Extract the history-page query and row-to-record conversion into a private, typed component in the memory package. Keep MemoryInterface.get_scenario_run_history_page() as the public entry point, with its existing signature and return shape. It should delegate page retrieval and continue using the existing aggregate method for now.
- Move only page-specific filtering, validation, cursor handling, compact row selection, and record construction, including the directly related start-time parsing helper where appropriate.
- Reuse the existing backend-specific expressions through a narrow internal boundary. Do not copy SQL implementations or expose a new public API.
- Keep database access in memory. Do not move queries into the backend service or move scenario execution/presentation policy into memory.
- Wire the extracted implementation immediately. This is not a scaffolding-only change.
Acceptance criteria:
Describe alternatives you've considered, if relevant
Moving the whole scenario-history subsystem at once makes a much larger review. Extracting every conditional into a separate helper adds navigation without clarifying ownership. Prefer one cohesive internal query module that the later matching and aggregation steps can extend, not a separate service per step.
Additional context
Starting points: pyrit/memory/memory_interface.py, get_scenario_run_history_page, _parse_scenario_started_at, and the scenario-history backend expression hooks. Consumers and coverage: pyrit/backend/services/scenario_run_service.py, tests/unit/memory/memory_interface/test_interface_scenario_history.py, and tests/unit/backend/test_scenario_run_service.py.
Follow doc/code/framework.md and the applicable database, Python, and test instructions. This is a behavior-preserving extraction, not a database migration, history API redesign, or counter-policy change. The separate observation-aware LLM scoring proposal is deferred and out of scope.
Series: #2766 (history pages), #2768 (attempt-to-work-unit matching), then #2769 (aggregate queries and records).
Is your feature request related to a problem? Please describe.
MemoryInterface.get_scenario_run_history_page()combines filter normalization, cursor pagination, SQL column selection, row conversion, and aggregate lookup inside the same large class that handles messages, scores, seeds, and other persistence operations. The history-page portion is a cohesive unit that can be understood separately without changing the public API.This is M1, the first of three small scenario-history extraction steps. It is ready for implementation. Later steps cover attempt-to-work-unit matching and aggregate counters; do not include those extractions here.
Describe the solution you'd like
Extract the history-page query and row-to-record conversion into a private, typed component in the memory package. Keep
MemoryInterface.get_scenario_run_history_page()as the public entry point, with its existing signature and return shape. It should delegate page retrieval and continue using the existing aggregate method for now.Acceptance criteria:
has_morevalue.limit + 1, without hydrating full result objects or introducing per-row queries.Describe alternatives you've considered, if relevant
Moving the whole scenario-history subsystem at once makes a much larger review. Extracting every conditional into a separate helper adds navigation without clarifying ownership. Prefer one cohesive internal query module that the later matching and aggregation steps can extend, not a separate service per step.
Additional context
Starting points:
pyrit/memory/memory_interface.py,get_scenario_run_history_page,_parse_scenario_started_at, and the scenario-history backend expression hooks. Consumers and coverage:pyrit/backend/services/scenario_run_service.py,tests/unit/memory/memory_interface/test_interface_scenario_history.py, andtests/unit/backend/test_scenario_run_service.py.Follow
doc/code/framework.mdand the applicable database, Python, and test instructions. This is a behavior-preserving extraction, not a database migration, history API redesign, or counter-policy change. The separate observation-aware LLM scoring proposal is deferred and out of scope.Series: #2766 (history pages), #2768 (attempt-to-work-unit matching), then #2769 (aggregate queries and records).