iop/agent-ops/skills/project/orchestrate-agent-task-loop/scripts/execution_target_policy.py
toki deea59d035 feat: task routing, orchestration, quota probe, and skill updates
- add finalize-task-routing skill with policy and test scripts
- add execution target selection scripts and tests for orchestrate-agent-task-loop
- add quota probe adapter and CLI status for node app
- update plan, finalize-task-routing, and orchestration skill files
- add agent-task archive for runtime target selector
- update stream-evidence-gate-core milestone
2026-07-25 19:33:17 +09:00

134 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""Pure execution-target policy for Agent Task worker and review stages."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, time
from zoneinfo import ZoneInfo
KST = ZoneInfo("Asia/Seoul")
DAY_START = time(7, 0)
NIGHT_START = time(23, 0)
VALID_STAGES = {"worker", "review"}
VALID_LANES = {"local", "cloud"}
@dataclass(frozen=True)
class RouteTarget:
adapter: str
target: str
execution_class: str
selfcheck_required: bool
@dataclass(frozen=True)
class PolicyDecision:
rule_id: str
policy_priority: int
reason_codes: tuple[str, ...]
time_window: str
candidates: tuple[RouteTarget, ...]
PI_ORNITH = RouteTarget("pi", "iop/ornith-fast", "local_model", True)
PI_LAGUNA = RouteTarget("pi", "iop/laguna-s:2.1", "local_model", True)
AGY_GEMINI_MEDIUM = RouteTarget(
"agy", "Gemini 3.6 Flash Medium", "cloud_model", False
)
CLAUDE_SONNET = RouteTarget("claude", "sonnet", "cloud_model", False)
CLAUDE_OPUS = RouteTarget("claude", "claude-opus-4-8", "cloud_model", False)
CODEX_SOL_XHIGH = RouteTarget("codex", "gpt-5.6-sol", "cloud_model", False)
def _validate(stage: str, lane: str, grade: int, evaluated_at: datetime) -> None:
if stage not in VALID_STAGES:
raise ValueError(f"unsupported stage: {stage}")
if lane not in VALID_LANES:
raise ValueError(f"unsupported lane: {lane}")
if not 1 <= grade <= 10:
raise ValueError(f"grade must be in G01..G10: {grade}")
if evaluated_at.tzinfo is None or evaluated_at.utcoffset() is None:
raise ValueError("evaluated_at must be timezone-aware")
def _kst_window(evaluated_at: datetime) -> tuple[str, bool]:
current = evaluated_at.astimezone(KST).time().replace(tzinfo=None)
is_day = DAY_START <= current < NIGHT_START
if is_day:
return "kst-day-[07:00,23:00)", True
return "kst-night-[23:00,07:00)", False
def select_policy(
*, stage: str, lane: str, grade: int, evaluated_at: datetime
) -> PolicyDecision:
"""Return the ordered target policy for one initial route evaluation."""
_validate(stage, lane, grade, evaluated_at)
if stage == "review":
return PolicyDecision(
rule_id="official-review-codex",
policy_priority=10,
reason_codes=("official_review_fixed",),
time_window="not_applicable",
candidates=(CODEX_SOL_XHIGH,),
)
if lane == "local":
if grade <= 6:
return PolicyDecision(
rule_id="worker-local-g01-g06",
policy_priority=30,
reason_codes=("local_low_grade",),
time_window="not_applicable",
candidates=(PI_ORNITH,),
)
if grade <= 8:
window, is_day = _kst_window(evaluated_at)
return PolicyDecision(
rule_id=(
"worker-local-g07-g08-day"
if is_day
else "worker-local-g07-g08-night"
),
policy_priority=20,
reason_codes=(
"kst_day_window" if is_day else "kst_night_window",
),
time_window=window,
candidates=(
(AGY_GEMINI_MEDIUM, PI_LAGUNA)
if is_day
else (PI_LAGUNA, AGY_GEMINI_MEDIUM)
),
)
return PolicyDecision(
rule_id="worker-local-g09-g10",
policy_priority=30,
reason_codes=("local_high_grade_cloud_target",),
time_window="not_applicable",
candidates=(CLAUDE_OPUS,),
)
if grade <= 6:
target = CLAUDE_SONNET
rule_id = "worker-cloud-g01-g06"
reason_code = "cloud_sonnet_grade"
elif grade <= 8:
target = CLAUDE_OPUS
rule_id = "worker-cloud-g07-g08"
reason_code = "cloud_opus_grade"
else:
target = CODEX_SOL_XHIGH
rule_id = "worker-cloud-g09-g10"
reason_code = "cloud_codex_grade"
return PolicyDecision(
rule_id=rule_id,
policy_priority=30,
reason_codes=(reason_code,),
time_window="not_applicable",
candidates=(target,),
)