Implement the harness contract and prove it against rein-aharness (GLAS-WP-0001-T02/T03/T04)
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 3s

- src/glas_harness/contract.py: Rein ABC (start_session/dispatch_tool/
  end_session), SandboxHandle/ToolCall/ToolResult, per docs/harness-contract.md.
- reins/rein_aharness.py: adapter around the rein-aharness CLI. Collapses
  a whole `agent-harness run` into one dispatch_tool call for now (no
  per-tool hooks yet — tracked in rein-aharness HARNESS-WP-0002-T03);
  verifies success via new-commit detection, mirroring rein-aharness's
  own success signal.
- gateway.py + cli.py: resolves a sand-boxer sandbox, runs one task
  through a rein, tears the sandbox down.
- registry/reins/*.yaml (rein-aharness implemented, rein-openweights
  planned) and profiles/harness.agent-dev{,-local}.yaml, pairing with
  sand-boxer's profile.agent-dev and the new profile.bwrap-local.

Tested against a real local git repo + mocked SandboxManager/CLI
subprocess (10 tests, all passing). A real live run against the actual
Claude Code CLI is deliberately left for a human-triggered follow-up —
not executed autonomously since it spends real API credits/credentials.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-07-26 13:02:59 +02:00
parent 97a50fc780
commit 5681fce787
16 changed files with 556 additions and 4 deletions

88
tests/test_gateway.py Normal file
View file

@ -0,0 +1,88 @@
from datetime import UTC, datetime
from unittest.mock import MagicMock
from sandboxer.models import Reachability, SandboxState, SandboxStatus
from glas_harness.contract import Rein, SandboxHandle, ToolCall, ToolResult
from glas_harness.gateway import run_task_through_rein
class _FakeRein(Rein):
def __init__(self) -> None:
self.calls: list[str] = []
def start_session(self, profile, inputs, sandbox: SandboxHandle):
self.calls.append("start_session")
assert sandbox.sandbox_id == "sbx1"
assert sandbox.reachability.get("workspace_dir") == "/tmp/ws"
return {"session": "s1"}
def dispatch_tool(self, session, tool_call: ToolCall) -> ToolResult:
self.calls.append("dispatch_tool")
assert tool_call.name == "run_task"
return ToolResult(ok=True, output="done")
def end_session(self, session):
self.calls.append("end_session")
return {"commit_sha": "deadbeef", "committed": "True"}
def _fake_status(sandbox_id: str = "sbx1") -> SandboxStatus:
now = datetime.now(UTC)
return SandboxStatus(
sandbox_id=sandbox_id,
profile_id="profile.bwrap-local",
extension_id="ext.bwrap",
state=SandboxState.READY,
consumer={"actor": "agt", "project": "glas-harness"},
host="localhost",
reachability=Reachability(host="localhost", pid="123", workspace_dir="/tmp/ws"),
created_at=now,
updated_at=now,
)
def test_run_task_through_rein_creates_and_destroys_sandbox() -> None:
manager = MagicMock()
manager.create.return_value = _fake_status()
rein = _FakeRein()
result = run_task_through_rein(
sandbox_profile="profile.bwrap-local",
repo="/tmp/repo",
title="t",
description="d",
rein=rein,
manager=manager,
)
assert rein.calls == ["start_session", "dispatch_tool", "end_session"]
manager.create.assert_called_once()
manager.destroy.assert_called_once_with("sbx1")
assert result["tool_ok"] is True
assert result["summary"]["committed"] == "True"
def test_run_task_through_rein_destroys_sandbox_even_on_failure() -> None:
manager = MagicMock()
manager.create.return_value = _fake_status()
class _FailingRein(_FakeRein):
def dispatch_tool(self, session, tool_call):
raise RuntimeError("boom")
rein = _FailingRein()
try:
run_task_through_rein(
sandbox_profile="profile.bwrap-local",
repo="/tmp/repo",
title="t",
description="d",
rein=rein,
manager=manager,
)
except RuntimeError:
pass
manager.destroy.assert_called_once_with("sbx1")

103
tests/test_rein_aharness.py Normal file
View file

@ -0,0 +1,103 @@
import json
import subprocess
from unittest.mock import MagicMock, patch
import pytest
from glas_harness.contract import Rein, SandboxHandle, ToolCall
from glas_harness.reins.rein_aharness import ReinAharness, ReinAharnessNotInstalled
def test_is_rein_subclass() -> None:
assert issubclass(ReinAharness, Rein)
def test_bin_raises_if_not_installed() -> None:
rein = ReinAharness(cli_bin="does-not-exist-binary")
with pytest.raises(ReinAharnessNotInstalled):
rein._bin()
def test_start_session_writes_task_file_and_captures_head(tmp_path) -> None:
repo = tmp_path / "repo"
repo.mkdir()
subprocess.run(["git", "init", "-q"], cwd=repo, check=True)
subprocess.run(["git", "commit", "-q", "--allow-empty", "-m", "init"], cwd=repo, check=True)
rein = ReinAharness()
sandbox = SandboxHandle(sandbox_id="abc", host="localhost", reachability={"workspace_dir": str(repo)})
session = rein.start_session(
profile={"id": "harness.agent-dev-local"},
inputs={"title": "t", "description": "d"},
sandbox=sandbox,
)
assert session["target_repo"] == str(repo)
assert session["head_before"]
task_spec = json.loads(open(session["task_file"]).read())
assert task_spec["title"] == "t"
assert task_spec["target_repo"] == str(repo)
def test_start_session_requires_resolvable_target_repo() -> None:
rein = ReinAharness()
sandbox = SandboxHandle(sandbox_id="abc", host="localhost", reachability={})
with pytest.raises(ValueError, match="target_repo"):
rein.start_session(profile={}, inputs={"title": "t", "description": "d"}, sandbox=sandbox)
def test_dispatch_tool_invokes_agent_harness_cli() -> None:
rein = ReinAharness()
session = {"task_file": "/tmp/task.json"}
fake_result = MagicMock(returncode=0, stdout="ok", stderr="")
with patch.object(rein, "_bin", return_value="/usr/bin/agent-harness"):
with patch("glas_harness.reins.rein_aharness.subprocess.run", return_value=fake_result) as run:
result = rein.dispatch_tool(session, ToolCall(name="run_task"))
argv = run.call_args.args[0]
assert argv[:3] == ["/usr/bin/agent-harness", "run", "--task-file"]
assert result.ok is True
assert result.output == "ok"
def test_dispatch_tool_reports_failure() -> None:
rein = ReinAharness()
session = {"task_file": "/tmp/task.json"}
fake_result = MagicMock(returncode=1, stdout="", stderr="boom")
with patch.object(rein, "_bin", return_value="/usr/bin/agent-harness"):
with patch("glas_harness.reins.rein_aharness.subprocess.run", return_value=fake_result):
result = rein.dispatch_tool(session, ToolCall(name="run_task"))
assert result.ok is False
assert result.error == "boom"
def test_end_session_detects_new_commit(tmp_path) -> None:
repo = tmp_path / "repo"
repo.mkdir()
subprocess.run(["git", "init", "-q"], cwd=repo, check=True)
subprocess.run(["git", "commit", "-q", "--allow-empty", "-m", "init"], cwd=repo, check=True)
rein = ReinAharness()
head_before = rein._git_head(str(repo))
subprocess.run(["git", "commit", "-q", "--allow-empty", "-m", "task"], cwd=repo, check=True)
summary = rein.end_session({"target_repo": str(repo), "head_before": head_before})
assert summary["committed"] == "True"
assert summary["commit_sha"] != head_before
def test_end_session_no_new_commit(tmp_path) -> None:
repo = tmp_path / "repo"
repo.mkdir()
subprocess.run(["git", "init", "-q"], cwd=repo, check=True)
subprocess.run(["git", "commit", "-q", "--allow-empty", "-m", "init"], cwd=repo, check=True)
rein = ReinAharness()
head = rein._git_head(str(repo))
summary = rein.end_session({"target_repo": str(repo), "head_before": head})
assert summary["committed"] == "False"