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>
This commit is contained in:
parent
87ae78c56a
commit
cfc1b75157
18 changed files with 1204 additions and 1 deletions
126
agent_harness/runner.py
Normal file
126
agent_harness/runner.py
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
"""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
|
||||
Loading…
Add table
Add a link
Reference in a new issue