@@ -650,6 +650,12 @@ async def _run_consolidation_job(
650650 # the per-batch log so it can still report processed/total under parallelism.
651651 # Mutable container so the inner closure can update without a `nonlocal`.
652652 cumulative_progress = {"processed" : 0 }
653+ # Semantic-dedup caches, keyed by tag group (scope), persisting across ALL fetch
654+ # iterations of this run — so a later create dedups against an earlier same-run create
655+ # even when they land in different fetch batches (the DB probe misses same-run rows).
656+ # A scope's batches run serially across iterations, so a per-scope list is race-free;
657+ # distinct scopes get distinct lists (and may run in parallel) so there's no shared state.
658+ run_dedup_caches : dict [tuple [str , ...], list [_RunObs ]] = {}
653659 while True :
654660 # Cap fetch size by remaining round budget
655661 fetch_limit = (
@@ -926,15 +932,18 @@ async def _process_one_llm_batch(
926932 llm_batch_num += 1
927933 numbered .append ((b , llm_batch_num ))
928934 numbered_groups .append (numbered )
935+ # Aligned with numbered_groups (both derive from tag_groups in insertion order).
936+ group_keys = list (tag_groups .keys ())
929937
930938 async def _process_tag_group (
931939 group_batches : list [tuple [list [dict [str , Any ]], int ]],
940+ group_key : tuple [str , ...],
932941 ) -> list [_BatchDeltas ]:
933942 # Batches within a group share a tag set and observation scope, so
934943 # they MUST run serially. Stop early if the op was cancelled mid-group.
935- # One semantic-dedup cache per group: its batches run serially (no race) and
936- # share a scope , so a create can dedup against earlier same-run creates.
937- group_dedup_cache : list [ _RunObs ] = []
944+ # The semantic-dedup cache is per scope and persists across fetch iterations
945+ # (run_dedup_caches) , so creates dedup against earlier same-run creates.
946+ group_dedup_cache = run_dedup_caches . setdefault ( group_key , [])
938947 deltas : list [_BatchDeltas ] = []
939948 for b , n in group_batches :
940949 d = await _process_one_llm_batch (b , n , group_dedup_cache )
@@ -958,21 +967,24 @@ async def _process_tag_group(
958967 async def _run_group (
959968 group_batches : list [tuple [list [dict [str , Any ]], int ]],
960969 scopes : list [frozenset [str ]],
970+ group_key : tuple [str , ...],
961971 ) -> list [_BatchDeltas ]:
962972 async with sem :
963973 async with AsyncExitStack () as stack :
964974 for s in scopes :
965975 await stack .enter_async_context (scope_locks [s ])
966- return await _process_tag_group (group_batches )
976+ return await _process_tag_group (group_batches , group_key )
967977
968- group_results = await asyncio .gather (* (_run_group (g , s ) for g , s in zip (numbered_groups , group_scopes )))
978+ group_results = await asyncio .gather (
979+ * (_run_group (g , s , k ) for g , s , k in zip (numbered_groups , group_scopes , group_keys ))
980+ )
969981 batch_results : list [_BatchDeltas ] = [d for gd in group_results for d in gd ]
970982 any_cancelled = any (d .cancelled for d in batch_results )
971983 else :
972984 batch_results = []
973985 any_cancelled = False
974- for g in numbered_groups :
975- group_deltas = await _process_tag_group (g )
986+ for g , k in zip ( numbered_groups , group_keys ) :
987+ group_deltas = await _process_tag_group (g , k )
976988 batch_results .extend (group_deltas )
977989 if any (d .cancelled for d in group_deltas ):
978990 any_cancelled = True
0 commit comments