fix(paired): bound arbiter interventions to stop infinite arbiter loop

The owner↔reviewer↔arbiter loop could repeat forever: when the arbiter
ruled PROCEED/REVISE/RESET it reset round_trip_count to 0, and nothing
tracked how many times the arbiter had already intervened. A re-deadlock
re-invoked the arbiter without bound.

Add a persistent arbiter_intervention_count (new column + migration 020)
that survives the round-trip reset, and a configurable cap
ARBITER_MAX_INTERVENTIONS (default 1). Once the arbiter has intervened
that many times and the loop still deadlocks, requestArbiterOrEscalate
escalates straight to the user instead of re-invoking the arbiter.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Codex
2026-06-09 02:15:21 +09:00
parent 5f06673ac6
commit de54f13469
15 changed files with 197 additions and 5 deletions

View File

@@ -78,9 +78,19 @@ export function handleArbiterCompletion(args: {
const { task, taskId, summary } = args;
const now = new Date().toISOString();
const arbiterVerdict = classifyArbiterVerdict(summary);
// Persist a running count of arbiter interventions so the loop can be bounded.
// This must NOT be reset by the round_trip_count reset below — otherwise the
// owner↔reviewer loop could re-trigger the arbiter without limit.
const nextArbiterInterventionCount =
(task.arbiter_intervention_count ?? 0) + 1;
logger.info(
{ taskId, arbiterVerdict, summary: summary?.slice(0, 200) },
{
taskId,
arbiterVerdict,
arbiterInterventionCount: nextArbiterInterventionCount,
summary: summary?.slice(0, 200),
},
'Arbiter verdict rendered',
);
@@ -98,6 +108,7 @@ export function handleArbiterCompletion(args: {
owner_step_done_streak: 0,
finalize_step_done_count: 0,
empty_step_done_streak: 0,
arbiter_intervention_count: nextArbiterInterventionCount,
arbiter_verdict: arbiterVerdict,
arbiter_requested_at: null,
},
@@ -121,12 +132,17 @@ export function handleArbiterCompletion(args: {
owner_step_done_streak: 0,
finalize_step_done_count: 0,
empty_step_done_streak: 0,
arbiter_intervention_count: nextArbiterInterventionCount,
arbiter_verdict: arbiterVerdict,
arbiter_requested_at: null,
},
});
logger.info(
{ taskId, arbiterVerdict },
{
taskId,
arbiterVerdict,
arbiterInterventionCount: nextArbiterInterventionCount,
},
'Arbiter requested owner changes — resuming owner flow',
);
return;