rein-aharness/agent_harness/runner.py
tegwick cfc1b75157 Harness foundation: INTENT, ADR-001, architecture, prototype adoption
- INTENT.md: three-layer model (blueprint/instance/harness), single
  shared runtime, never-become boundaries
- ADR-001 (accepted): DEC-2026-002 resolution — one harness repo for
  all projects; instances are declarative state in consuming repos
- docs/architecture.md: components, contracts (manifest, tool
  profiles, completion events, credential lanes), deployment shape
- agent_harness/: executor-worker prototype adopted and renamed
  (6/6 tests green); HARNESS-WP-0001 initial workplan (7 tasks)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 23:35:27 +02:00

126 lines
3.8 KiB
Python

"""One-task run orchestration.
Flow: lock target repo → snapshot HEAD → persona bundle → prompt → agentic
session → verify a new commit exists → hub progress event (+ task close).
The run *fails* if the session pushed anywhere or left the repo dirty in a
way it should not — the worker never pushes; publishing is a separate,
explicitly-granted lane (see integrations/executor-worker-secrets.md in
binky-control).
"""
from __future__ import annotations
import subprocess
from dataclasses import dataclass
from pathlib import Path
from agent_harness import hub
from agent_harness.persona import load_persona_bundle
from agent_harness.taskspec import TaskSpec
PROMPT_TEMPLATE = """\
You are an unattended executor session (agent persona below, if any).
Operating rules, non-negotiable:
- Work ONLY inside the current repository working directory.
- Green/Blue lane: file edits and local git add/commit only. Never push,
never touch the network, never run destructive commands.
- Bounded effort: complete the single task below, commit with a clear
message, then stop. If the task cannot be completed, commit nothing and
say why in your final output.
{persona}
## Task: {title}
{description}
"""
@dataclass
class RunResult:
ok: bool
committed: bool
head_before: str
head_after: str
persona_source: str
session_output: str
reason: str = ""
def _git(repo: Path, *args: str) -> str:
result = subprocess.run(
["git", "-C", str(repo), *args],
capture_output=True,
text=True,
timeout=60,
)
if result.returncode != 0:
raise RuntimeError(f"git {' '.join(args)} failed: {result.stderr.strip()}")
return result.stdout.strip()
def run_task(spec: TaskSpec, adapter=None, report_to_hub: bool = True) -> RunResult:
if adapter is None:
from agent_harness.adapter import AgenticClaudeCodeAdapter
adapter = AgenticClaudeCodeAdapter(workdir=spec.target_repo)
head_before = _git(spec.target_repo, "rev-parse", "HEAD")
persona, persona_source = load_persona_bundle(spec.agent, spec.target_repo)
prompt = PROMPT_TEMPLATE.format(
persona=persona or "(no persona bundle available for this run)",
title=spec.title,
description=spec.description,
)
from llm_connect.models import RunConfig
config = RunConfig(timeout_seconds=spec.timeout_seconds, skip_if_exists=False)
try:
response = adapter.execute_prompt(prompt, config)
session_output = response.content
session_ok = True
reason = ""
except Exception as exc: # adapter failures must still be reported
session_output = ""
session_ok = False
reason = f"session failed: {exc}"
head_after = _git(spec.target_repo, "rev-parse", "HEAD")
committed = head_after != head_before
ok = session_ok and committed
if session_ok and not committed:
reason = "session completed without committing"
result = RunResult(
ok=ok,
committed=committed,
head_before=head_before,
head_after=head_after,
persona_source=persona_source,
session_output=session_output,
reason=reason,
)
if report_to_hub:
detail = {
"repo": spec.target_repo.name,
"task_title": spec.title,
"agent": spec.agent,
"labels": spec.labels,
"persona_source": persona_source,
"committed": committed,
"head_after": head_after,
"ok": ok,
"reason": reason,
}
hub.post_progress_event(
summary=f"executor run: {spec.title} ({'ok' if ok else 'failed'})",
event_type=spec.completion_event_type,
detail=detail,
task_id=spec.hub_task_id,
)
if ok and spec.hub_task_id:
hub.close_task(spec.hub_task_id)
return result