feat: implement MAGI 3-agent arbiter system for deadlock resolution

Introduces a third agent role (arbiter) that is summoned on-demand
when owner and reviewer reach a deadlock (same verdict 3+ rounds).

Architecture:
- 3 Discord bots: owner (codex), reviewer (claude), arbiter (claude/codex)
- Arbiter is NOT always-on — only invoked when deadlock detected
- Arbiter renders binding verdict: PROCEED/REVISE/RESET/ESCALATE
- Non-escalate verdicts reset round_trip_count and resume ping-pong
- Backward compatible: ARBITER_AGENT_TYPE unset = existing 2-agent mode

Changes across 13 source files + 7 test files:
- types.ts: PairedRoomRole += 'arbiter', new statuses, ArbiterVerdict type
- config.ts: ARBITER_AGENT_TYPE, ARBITER_SERVICE_ID, ARBITER_DEADLOCK_THRESHOLD
- db.ts: schema migration (arbiter columns in channel_owner + paired_tasks)
- service-routing.ts: arbiter_service_id in lease
- room-role-context.ts: arbiter role detection
- paired-execution-context.ts: deadlock->arbiter, verdict handling
- message-runtime.ts: arbiter turn routing, cursor, sender labeling
- message-agent-executor.ts: arbiter mode, failover
- agent-runner.ts + environment.ts: arbiter container mode
- platform-prompts.ts: arbiter prompt loading

New files:
- prompts/arbiter-paired-room.md: arbiter system prompt
- src/arbiter-context.ts: builds conversation context for arbiter judgment
This commit is contained in:
Eyejoker
2026-03-30 22:20:42 +09:00
parent 58ed721d6e
commit a043f2b79a
20 changed files with 587 additions and 53 deletions

View File

@@ -110,6 +110,28 @@ export const REVIEWER_SERVICE_ID_FOR_TYPE =
? CLAUDE_SERVICE_ID
: CODEX_REVIEW_SERVICE_ID;
/** Arbiter agent type. Disabled by default. Set ARBITER_AGENT_TYPE=codex or claude-code to enable. */
const rawArbiterAgentType = getEnv('ARBITER_AGENT_TYPE');
export const ARBITER_AGENT_TYPE: AgentType | undefined =
rawArbiterAgentType === 'codex' || rawArbiterAgentType === 'claude-code'
? rawArbiterAgentType
: undefined;
/** Service ID for the arbiter. Re-uses codex-review bot by default when arbiter is enabled. */
export const ARBITER_SERVICE_ID = ARBITER_AGENT_TYPE
? (getEnv('ARBITER_SERVICE_ID') || CODEX_REVIEW_SERVICE_ID)
: null;
/** Number of consecutive owner↔reviewer round trips before arbiter is auto-requested. */
export const ARBITER_DEADLOCK_THRESHOLD = parseInt(
getEnv('ARBITER_DEADLOCK_THRESHOLD') || '3',
10,
);
export function isArbiterEnabled(): boolean {
return ARBITER_AGENT_TYPE !== undefined;
}
// Max owner↔reviewer round trips per task. 0 = unlimited.
const rawMaxRoundTrips = getEnv('PAIRED_MAX_ROUND_TRIPS') || '10';
export const PAIRED_MAX_ROUND_TRIPS =