resolve_run_policy returns blueprint for persona prepare; add tenant onboard helper script. Marks HARNESS-WP-0001-T07 done after binky onboarding handoff.
222 lines
7 KiB
Python
222 lines
7 KiB
Python
"""One-task run orchestration.
|
|
|
|
Flow: lock target repo → resolve tool profile / budget from instance
|
|
manifest → snapshot HEAD → persona bundle → prompt → agentic session →
|
|
verify a new commit exists → kaizen metrics + 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.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
import time
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
from agent_harness import hub, metrics
|
|
from agent_harness.manifest import resolve_run_policy
|
|
from agent_harness.persona import load_persona_bundle
|
|
from agent_harness.profiles import UnknownToolProfileError, get_profile
|
|
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.
|
|
- Tool profile for this run: {tool_profile} (lane={lane}).
|
|
- 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 = ""
|
|
tool_profile: str = ""
|
|
budget_tokens: int | None = None
|
|
tokens_spent: int | None = None
|
|
execution_time_s: float = 0.0
|
|
|
|
|
|
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,
|
|
write_metrics: bool = True,
|
|
) -> RunResult:
|
|
try:
|
|
profile_name, budget_tokens, lane, blueprint = resolve_run_policy(
|
|
spec.target_repo, spec.agent
|
|
)
|
|
profile = get_profile(profile_name)
|
|
except UnknownToolProfileError as exc:
|
|
return RunResult(
|
|
ok=False,
|
|
committed=False,
|
|
head_before="",
|
|
head_after="",
|
|
persona_source="none",
|
|
session_output="",
|
|
reason=f"refused: {exc}",
|
|
tool_profile="",
|
|
budget_tokens=None,
|
|
)
|
|
except Exception as exc:
|
|
return RunResult(
|
|
ok=False,
|
|
committed=False,
|
|
head_before="",
|
|
head_after="",
|
|
persona_source="none",
|
|
session_output="",
|
|
reason=f"manifest resolution failed: {exc}",
|
|
tool_profile="",
|
|
budget_tokens=None,
|
|
)
|
|
|
|
if adapter is None:
|
|
from agent_harness.adapter import AgenticClaudeCodeAdapter
|
|
|
|
adapter = AgenticClaudeCodeAdapter(
|
|
workdir=spec.target_repo,
|
|
tool_profile=profile,
|
|
)
|
|
|
|
head_before = _git(spec.target_repo, "rev-parse", "HEAD")
|
|
persona, persona_source = load_persona_bundle(blueprint, spec.target_repo)
|
|
prompt = PROMPT_TEMPLATE.format(
|
|
persona=persona or "(no persona bundle available for this run)",
|
|
title=spec.title,
|
|
description=spec.description,
|
|
tool_profile=profile.name,
|
|
lane=lane or profile.lane,
|
|
)
|
|
|
|
from llm_connect.models import BudgetTracker, RunConfig
|
|
|
|
budget_tracker = BudgetTracker(total=budget_tokens) if budget_tokens else None
|
|
config = RunConfig(
|
|
timeout_seconds=spec.timeout_seconds,
|
|
skip_if_exists=False,
|
|
budget_tracker=budget_tracker,
|
|
)
|
|
|
|
started = time.monotonic()
|
|
try:
|
|
response = adapter.execute_prompt(prompt, config)
|
|
session_output = response.content
|
|
session_ok = True
|
|
reason = ""
|
|
except Exception as exc: # adapter / budget failures must still be reported
|
|
session_output = ""
|
|
session_ok = False
|
|
reason = f"session failed: {exc}"
|
|
execution_time_s = time.monotonic() - started
|
|
|
|
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"
|
|
|
|
tokens_spent = budget_tracker.spent if budget_tracker is not None else None
|
|
|
|
result = RunResult(
|
|
ok=ok,
|
|
committed=committed,
|
|
head_before=head_before,
|
|
head_after=head_after,
|
|
persona_source=persona_source,
|
|
session_output=session_output,
|
|
reason=reason,
|
|
tool_profile=profile.name,
|
|
budget_tokens=budget_tokens,
|
|
tokens_spent=tokens_spent,
|
|
execution_time_s=execution_time_s,
|
|
)
|
|
|
|
if write_metrics:
|
|
try:
|
|
metrics.record_execution(
|
|
spec.target_repo,
|
|
spec.agent,
|
|
success=ok,
|
|
execution_time_s=execution_time_s,
|
|
tokens=tokens_spent,
|
|
committed=committed,
|
|
head_after=head_after,
|
|
reason=reason or None,
|
|
metadata={
|
|
"task_title": spec.title,
|
|
"tool_profile": profile.name,
|
|
"labels": list(spec.labels),
|
|
"completion_event_type": spec.completion_event_type,
|
|
},
|
|
)
|
|
except OSError:
|
|
pass # metrics must not block run completion reporting
|
|
|
|
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,
|
|
"tool_profile": profile.name,
|
|
"budget_tokens": budget_tokens,
|
|
"tokens_spent": tokens_spent,
|
|
"execution_time_s": round(execution_time_s, 3),
|
|
}
|
|
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 tokens_spent is not None or budget_tokens is not None:
|
|
hub.post_token_event(
|
|
repo=spec.target_repo.name,
|
|
tokens=tokens_spent or 0,
|
|
budget=budget_tokens,
|
|
agent=spec.agent,
|
|
ok=ok,
|
|
detail={"task_title": spec.title, "tool_profile": profile.name},
|
|
)
|
|
if ok and spec.hub_task_id:
|
|
hub.close_task(spec.hub_task_id)
|
|
|
|
return result
|