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
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2026-07-17 23:49:03 +02:00
|
|
|
import json
|
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
|
|
|
import subprocess
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
import pytest
|
2026-07-17 23:49:03 +02:00
|
|
|
import yaml
|
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
|
|
|
|
Rename package, CLI, and deploy artifacts to rein-aharness (HARNESS-WP-0002-T02)
agent_harness -> rein_aharness (package + all imports), CLI command
agent-harness -> rein-aharness, Docker image tag, k8s namespace/labels/
names, Makefile targets, deploy script env var/paths. In-repo identity
strings (hub event source, metrics harness field, default assignee,
argparse prog name, commit author identity) updated to match.
Historical documents left untouched on purpose: docs/adr/ADR-001-agent-harness-architecture.md,
docs/architecture.md (dated v0.1 snapshot), workplans/HARNESS-WP-0001
(completed under the old name), and the SSH host alias
"forgejo-agent-harness" (external ~/.ssh/config entry, not owned here).
Verified: 47/47 tests pass, CLI runs correctly from a fresh venv,
`make image` builds and the resulting container runs correctly.
deploy/README.md gained an explicit rename cutover checklist for what
this session cannot safely do unattended -- moving the host-side
secrets dir and checkout on railiance01, and not deleting the old k8s
namespace until the new one is confirmed working. The actual live
cutover (running that checklist against the real Railiance deployment)
is not attempted here -- real production surgery on binky-control's
live automation, needs the operator present.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-26 14:22:18 +02:00
|
|
|
from rein_aharness.manifest import HARNESS_MAJOR
|
|
|
|
|
from rein_aharness.runner import RunResult, run_task
|
|
|
|
|
from rein_aharness.taskspec import TaskSpec, TaskSpecError
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
def _make_repo(tmp_path: Path) -> Path:
|
|
|
|
|
repo = tmp_path / "sandbox"
|
|
|
|
|
repo.mkdir()
|
|
|
|
|
subprocess.run(["git", "init", "-q"], cwd=repo, check=True)
|
|
|
|
|
(repo / "README.md").write_text("sandbox\n")
|
|
|
|
|
subprocess.run(["git", "add", "."], cwd=repo, check=True)
|
|
|
|
|
subprocess.run(
|
|
|
|
|
["git", "-c", "user.email=t@t", "-c", "user.name=t", "commit", "-qm", "init"],
|
|
|
|
|
cwd=repo,
|
|
|
|
|
check=True,
|
|
|
|
|
)
|
|
|
|
|
return repo
|
|
|
|
|
|
|
|
|
|
|
2026-07-17 23:49:03 +02:00
|
|
|
def _write_manifest(repo: Path, agents: dict) -> None:
|
|
|
|
|
kaizen = repo / ".kaizen"
|
|
|
|
|
kaizen.mkdir(exist_ok=True)
|
|
|
|
|
data = {"version": "1", "harness": HARNESS_MAJOR, "agents": agents}
|
|
|
|
|
(kaizen / "schedule.yml").write_text(
|
|
|
|
|
yaml.safe_dump(data, sort_keys=False), encoding="utf-8"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
class CommittingAdapter:
|
|
|
|
|
"""Fake adapter that simulates a session which commits."""
|
|
|
|
|
|
|
|
|
|
def __init__(self, repo: Path):
|
|
|
|
|
self.repo = repo
|
|
|
|
|
self.prompts: list[str] = []
|
2026-07-17 23:49:03 +02:00
|
|
|
self.configs: list = []
|
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
|
|
|
|
|
|
|
|
def execute_prompt(self, prompt, config):
|
|
|
|
|
self.prompts.append(prompt)
|
2026-07-17 23:49:03 +02:00
|
|
|
self.configs.append(config)
|
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
|
|
|
(self.repo / "HELLO.md").write_text("hello\n")
|
|
|
|
|
subprocess.run(["git", "add", "."], cwd=self.repo, check=True)
|
|
|
|
|
subprocess.run(
|
|
|
|
|
["git", "-c", "user.email=t@t", "-c", "user.name=t", "commit", "-qm", "task done"],
|
|
|
|
|
cwd=self.repo,
|
|
|
|
|
check=True,
|
|
|
|
|
)
|
|
|
|
|
from llm_connect.models import LLMResponse
|
|
|
|
|
|
|
|
|
|
return LLMResponse(content="done", model="fake", usage={}, finish_reason="stop")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class IdleAdapter:
|
|
|
|
|
def execute_prompt(self, prompt, config):
|
|
|
|
|
from llm_connect.models import LLMResponse
|
|
|
|
|
|
|
|
|
|
return LLMResponse(content="nothing to do", model="fake", usage={}, finish_reason="stop")
|
|
|
|
|
|
|
|
|
|
|
2026-07-17 23:49:03 +02:00
|
|
|
class BudgetBlowingAdapter:
|
|
|
|
|
def execute_prompt(self, prompt, config):
|
|
|
|
|
from llm_connect.exceptions import LLMBudgetExceededError
|
|
|
|
|
|
|
|
|
|
if config.budget_tracker is not None:
|
|
|
|
|
# Simulate preflight/exhaustion the way adapters do.
|
|
|
|
|
config.budget_tracker.consume(config.budget_tracker.total)
|
|
|
|
|
config.budget_tracker.consume(1)
|
|
|
|
|
raise LLMBudgetExceededError(
|
|
|
|
|
"Token budget exceeded",
|
|
|
|
|
total=1,
|
|
|
|
|
spent=1,
|
|
|
|
|
requested=1,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _spec(repo: Path, agent: str = "coach") -> TaskSpec:
|
|
|
|
|
return TaskSpec(
|
|
|
|
|
title="write hello",
|
|
|
|
|
description="create HELLO.md",
|
|
|
|
|
target_repo=repo,
|
|
|
|
|
agent=agent,
|
|
|
|
|
)
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_run_task_success_when_session_commits(tmp_path) -> None:
|
|
|
|
|
repo = _make_repo(tmp_path)
|
|
|
|
|
adapter = CommittingAdapter(repo)
|
|
|
|
|
|
2026-07-17 23:49:03 +02:00
|
|
|
result = run_task(
|
|
|
|
|
_spec(repo), adapter=adapter, report_to_hub=False, write_metrics=True
|
|
|
|
|
)
|
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
|
|
|
|
|
|
|
|
assert isinstance(result, RunResult)
|
|
|
|
|
assert result.ok is True
|
|
|
|
|
assert result.committed is True
|
|
|
|
|
assert result.head_before != result.head_after
|
2026-07-17 23:49:03 +02:00
|
|
|
assert result.tool_profile == "green-commit-only"
|
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
|
|
|
assert "write hello" in adapter.prompts[0]
|
|
|
|
|
assert "Never push" in adapter.prompts[0]
|
2026-07-17 23:49:03 +02:00
|
|
|
assert "Tool profile for this run: green-commit-only" in adapter.prompts[0]
|
|
|
|
|
|
|
|
|
|
metrics_path = repo / ".kaizen" / "metrics" / "coach" / "executions.jsonl"
|
|
|
|
|
assert metrics_path.is_file()
|
|
|
|
|
rec = json.loads(metrics_path.read_text().strip().splitlines()[-1])
|
|
|
|
|
assert rec["success"] is True
|
|
|
|
|
assert rec["committed"] is True
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_run_task_fails_without_commit(tmp_path) -> None:
|
|
|
|
|
repo = _make_repo(tmp_path)
|
|
|
|
|
|
2026-07-17 23:49:03 +02:00
|
|
|
result = run_task(
|
|
|
|
|
_spec(repo), adapter=IdleAdapter(), report_to_hub=False, write_metrics=False
|
|
|
|
|
)
|
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
|
|
|
|
|
|
|
|
assert result.ok is False
|
|
|
|
|
assert result.committed is False
|
|
|
|
|
assert result.reason == "session completed without committing"
|
|
|
|
|
|
|
|
|
|
|
2026-07-17 23:49:03 +02:00
|
|
|
def test_run_task_refuses_unknown_tool_profile(tmp_path) -> None:
|
|
|
|
|
repo = _make_repo(tmp_path)
|
|
|
|
|
_write_manifest(
|
|
|
|
|
repo,
|
|
|
|
|
{
|
|
|
|
|
"coach": {
|
|
|
|
|
"cadence": "daily",
|
|
|
|
|
"tool_profile": "not-a-real-profile",
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
result = run_task(
|
|
|
|
|
_spec(repo), adapter=IdleAdapter(), report_to_hub=False, write_metrics=False
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert result.ok is False
|
|
|
|
|
assert result.reason.startswith("refused:")
|
|
|
|
|
assert "not-a-real-profile" in result.reason
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_run_task_resolves_manifest_profile_and_budget(tmp_path) -> None:
|
|
|
|
|
repo = _make_repo(tmp_path)
|
|
|
|
|
_write_manifest(
|
|
|
|
|
repo,
|
|
|
|
|
{
|
|
|
|
|
"coach": {
|
|
|
|
|
"cadence": "daily",
|
|
|
|
|
"lane": "blue",
|
|
|
|
|
"tool_profile": "blue-mail-triage",
|
|
|
|
|
"budget": 25000,
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
adapter = CommittingAdapter(repo)
|
|
|
|
|
|
|
|
|
|
result = run_task(
|
|
|
|
|
_spec(repo), adapter=adapter, report_to_hub=False, write_metrics=False
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert result.ok is True
|
|
|
|
|
assert result.tool_profile == "blue-mail-triage"
|
|
|
|
|
assert result.budget_tokens == 25000
|
|
|
|
|
assert adapter.configs[0].budget_tracker is not None
|
|
|
|
|
assert adapter.configs[0].budget_tracker.total == 25000
|
|
|
|
|
assert "blue-mail-triage" in adapter.prompts[0]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_run_task_budget_exhaustion_fails(tmp_path) -> None:
|
|
|
|
|
repo = _make_repo(tmp_path)
|
|
|
|
|
_write_manifest(
|
|
|
|
|
repo,
|
|
|
|
|
{
|
|
|
|
|
"coach": {
|
|
|
|
|
"cadence": "daily",
|
|
|
|
|
"tool_profile": "green-commit-only",
|
|
|
|
|
"budget": 10,
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
result = run_task(
|
|
|
|
|
_spec(repo),
|
|
|
|
|
adapter=BudgetBlowingAdapter(),
|
|
|
|
|
report_to_hub=False,
|
|
|
|
|
write_metrics=False,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert result.ok is False
|
|
|
|
|
assert "session failed" in result.reason
|
|
|
|
|
assert result.budget_tokens == 10
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
def test_taskspec_rejects_non_repo(tmp_path) -> None:
|
|
|
|
|
spec_file = tmp_path / "task.json"
|
|
|
|
|
spec_file.write_text(
|
|
|
|
|
'{"title": "x", "description": "y", "target_repo": "%s"}' % tmp_path
|
|
|
|
|
)
|
|
|
|
|
with pytest.raises(TaskSpecError, match="not a git repository"):
|
|
|
|
|
TaskSpec.from_file(spec_file)
|
2026-07-26 14:49:36 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_run_task_wires_on_tool_event_when_emit_tool_events(tmp_path, monkeypatch) -> None:
|
|
|
|
|
repo = _make_repo(tmp_path)
|
|
|
|
|
spec = _spec(repo)
|
|
|
|
|
captured: dict = {}
|
|
|
|
|
|
|
|
|
|
class FakeStreamingAdapter:
|
|
|
|
|
def __init__(self, workdir, tool_profile, on_tool_event=None):
|
|
|
|
|
captured["on_tool_event"] = on_tool_event
|
|
|
|
|
|
|
|
|
|
def execute_prompt(self, prompt, config):
|
|
|
|
|
captured["on_tool_event"](
|
|
|
|
|
{"type": "assistant", "message": {"content": [{"type": "tool_use", "name": "Read"}]}}
|
|
|
|
|
)
|
|
|
|
|
(repo / "HELLO.md").write_text("hi\n")
|
|
|
|
|
subprocess.run(["git", "add", "."], cwd=repo, check=True)
|
|
|
|
|
subprocess.run(
|
|
|
|
|
["git", "-c", "user.email=t@t", "-c", "user.name=t", "commit", "-qm", "task"],
|
|
|
|
|
cwd=repo,
|
|
|
|
|
check=True,
|
|
|
|
|
)
|
|
|
|
|
from llm_connect.models import LLMResponse
|
|
|
|
|
|
|
|
|
|
return LLMResponse(content="done", model="m", usage={}, finish_reason="stop", metadata={})
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr("rein_aharness.adapter.AgenticClaudeCodeAdapter", FakeStreamingAdapter)
|
|
|
|
|
|
|
|
|
|
result = run_task(spec, report_to_hub=False, write_metrics=False, emit_tool_events=True)
|
|
|
|
|
|
|
|
|
|
assert captured["on_tool_event"] is not None
|
|
|
|
|
assert len(result.tool_events) == 1
|
|
|
|
|
assert result.tool_events[0]["message"]["content"][0]["name"] == "Read"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_run_task_default_adapter_gets_no_callback_when_not_requested(tmp_path, monkeypatch) -> None:
|
|
|
|
|
repo = _make_repo(tmp_path)
|
|
|
|
|
spec = _spec(repo)
|
|
|
|
|
captured: dict = {}
|
|
|
|
|
|
|
|
|
|
class FakeAdapter:
|
|
|
|
|
def __init__(self, workdir, tool_profile, on_tool_event=None):
|
|
|
|
|
captured["on_tool_event"] = on_tool_event
|
|
|
|
|
|
|
|
|
|
def execute_prompt(self, prompt, config):
|
|
|
|
|
(repo / "HELLO.md").write_text("hi\n")
|
|
|
|
|
subprocess.run(["git", "add", "."], cwd=repo, check=True)
|
|
|
|
|
subprocess.run(
|
|
|
|
|
["git", "-c", "user.email=t@t", "-c", "user.name=t", "commit", "-qm", "task"],
|
|
|
|
|
cwd=repo,
|
|
|
|
|
check=True,
|
|
|
|
|
)
|
|
|
|
|
from llm_connect.models import LLMResponse
|
|
|
|
|
|
|
|
|
|
return LLMResponse(content="done", model="m", usage={}, finish_reason="stop", metadata={})
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr("rein_aharness.adapter.AgenticClaudeCodeAdapter", FakeAdapter)
|
|
|
|
|
|
|
|
|
|
result = run_task(spec, report_to_hub=False, write_metrics=False)
|
|
|
|
|
|
|
|
|
|
assert captured["on_tool_event"] is None
|
|
|
|
|
assert result.tool_events == []
|