diff --git a/apps/api/src/services/platform_operator_service.py b/apps/api/src/services/platform_operator_service.py index 2b80335f..e929bd17 100644 --- a/apps/api/src/services/platform_operator_service.py +++ b/apps/api/src/services/platform_operator_service.py @@ -158,7 +158,12 @@ async def list_runs( if remediation_status: result = await db.execute(stmt) candidate_rows = list(result.scalars().all()) - inbound_by_run, outbound_by_run = await _load_run_message_context(db, candidate_rows) + context_limit = _list_filter_context_limit(len(candidate_rows)) + inbound_by_run, outbound_by_run = await _load_run_message_context( + db, + candidate_rows, + limit=context_limit, + ) remediation_summaries = await _build_run_remediation_summaries( runs=candidate_rows, inbound_by_run=inbound_by_run, @@ -351,6 +356,8 @@ def _collect_run_incident_ids( async def _load_run_message_context( db: Any, runs: list[AwoooPRunState], + *, + limit: int = _MAX_LIST_CONTEXT_ROWS, ) -> tuple[ dict[UUID, list[AwoooPConversationEvent]], dict[UUID, list[AwoooPOutboundMessage]], @@ -384,7 +391,7 @@ async def _load_run_message_context( select(AwoooPConversationEvent) .where(sa_or(*inbound_filters)) .order_by(AwoooPConversationEvent.received_at.desc()) - .limit(_MAX_LIST_CONTEXT_ROWS) + .limit(limit) ) inbound_by_run: dict[UUID, list[AwoooPConversationEvent]] = defaultdict(list) for event in inbound_result.scalars().all(): @@ -400,7 +407,7 @@ async def _load_run_message_context( select(AwoooPOutboundMessage) .where(AwoooPOutboundMessage.run_id.in_(run_ids)) .order_by(AwoooPOutboundMessage.queued_at.desc()) - .limit(_MAX_LIST_CONTEXT_ROWS) + .limit(limit) ) outbound_by_run: dict[UUID, list[AwoooPOutboundMessage]] = defaultdict(list) for message in outbound_result.scalars().all(): @@ -409,6 +416,10 @@ async def _load_run_message_context( return dict(inbound_by_run), dict(outbound_by_run) +def _list_filter_context_limit(candidate_count: int) -> int: + return min(max(candidate_count * 4, _MAX_LIST_CONTEXT_ROWS), 20_000) + + def _route_label_from_remediation(item: dict[str, Any]) -> str: """Render remediation MCP route consistently with Telegram / Work Items.""" return "/".join( diff --git a/apps/api/tests/test_awooop_operator_timeline_labels.py b/apps/api/tests/test_awooop_operator_timeline_labels.py index d38bbab1..86ffe2ec 100644 --- a/apps/api/tests/test_awooop_operator_timeline_labels.py +++ b/apps/api/tests/test_awooop_operator_timeline_labels.py @@ -3,6 +3,7 @@ from types import SimpleNamespace from src.services.platform_operator_service import ( _collect_run_incident_ids, + _list_filter_context_limit, _outbound_timeline_title, _run_remediation_list_summary, _remediation_summary_matches_status, @@ -171,6 +172,12 @@ def test_remediation_summary_matches_status_filter() -> None: assert _remediation_summary_matches_status(None, "no_evidence") +def test_list_filter_context_limit_scales_with_candidate_rows() -> None: + assert _list_filter_context_limit(2) == 500 + assert _list_filter_context_limit(4176) == 16704 + assert _list_filter_context_limit(10000) == 20000 + + def test_timeline_sort_key_normalizes_datetime_and_iso_string() -> None: fallback = datetime(2026, 5, 14, 10, 0, 0) keys = [