Files
agents/contracts/coders/RunContext.py
T

103 lines
3.8 KiB
Python
Raw Normal View History

2026-09-22 23:21:59 +03:00
# orchestrator/run_context.py
from __future__ import annotations
import secrets
from dataclasses import dataclass
from datetime import datetime, timezone
2026-09-23 22:42:21 +03:00
from pathlib import Path
2026-09-22 23:21:59 +03:00
from typing import Any
from agents.coders.state import AgentState
from common.gitea_mcp_client import GiteaMCPClient
from common.llm_client import LLMClient
from common.time import now_iso, now_iso_plus
from common.youtrack_mcp_client import YouTrackMCPClient
@dataclass(frozen=True)
class AgentConfig:
2026-09-23 22:42:21 +03:00
agent_dir: Path
2026-09-22 23:21:59 +03:00
# ── project-level ──────────────────────────────────────────
youtrack_project: str
2026-09-23 22:42:21 +03:00
git_name: str
git_email: str
gitea_repo: str = ""
2026-09-22 23:21:59 +03:00
gitea_target_branch: str = "main"
# ── limits (snapshotted into state at run start) ───────────
max_plan_attempts: int = 3
max_act_attempts: int = 5
max_ci_self_resolve_attempts: int = 2
max_clarification_rounds: int = 3
max_rework_rounds: int = 2
wall_clock_minutes: int = 60
token_budget: int = 500_000
# ── human-await timers ─────────────────────────────────────
re_ping_after_days: int = 3
abandon_after_days: int = 5
# ── reviewer policy ────────────────────────────────────────
reviewer_fallback: str = "codeowners" # codeowners | blame | on_call
# ── language hints (for Plan) ──────────────────────────────
language_markers: dict[str, list[str]] = None # filled from project
2026-09-23 22:42:21 +03:00
def project_mem_dir(self):
p = self.agent_dir / "memory" / f"{self.youtrack_project}"
if not p.exists():
p.mkdir(parents=True, exist_ok=True)
return p
2026-09-22 23:21:59 +03:00
@dataclass
class RunContext:
"""
Everything a handler needs to do its job: the ports it talks to, the
persisted state for this issue, and the identifiers it must stamp into
comments and state.
"""
# ── identifiers ────────────────────────────────────────────
agent_id: str
issue_id: str
run_id: str # one logical attempt; stable across calls
attempt: int # retry counter within the run
# ── ports (outbound systems) ───────────────────────────────
llm: LLMClient
youtrack: YouTrackMCPClient
gitea: GiteaMCPClient
# ── persisted pipeline state for this issue ────────────────
state: AgentState
# ── static config for this run ─────────────────────────────
config: AgentConfig
# ── convenience (thin, no hidden behavior) ─────────────────
def now(self) -> str:
return now_iso()
def now_plus(self, **delta) -> str:
return now_iso_plus(**delta)
def new_run_id() -> str:
"""
Generate a run id: one logical attempt at a task.
Format: run-YYYYMMDD-HHMMSS-<6 hex chars>
Example: run-20260921-114500-a3f1c2
- Timestamp prefix makes ids sortable and human-readable in logs.
- Random suffix prevents collisions when two runs start in the same
second (e.g., parallel issues, fast retries, tests).
- The "run-" prefix makes it obvious what kind of id this is when it
appears next to a comment marker or in a state file.
"""
ts = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
suffix = secrets.token_hex(3) # 3 bytes -> 6 hex chars
return f"run-{ts}-{suffix}"