Add ReinOpenWeights adapter, closing GLAS-WP-0001-T05
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

glas_harness/reins/rein_openweights.py implements the Rein ABC by
shelling out to `rein-openweights run --task-file ... --no-hub
[--model ...]`, mirroring reins/rein_aharness.py exactly. Factored the
duplicated git_head/write_task_file logic into reins/_shared.py, used
by both adapters now. registry/reins/rein-openweights.yaml flipped to
status: implemented. 8 new tests (mocked subprocess), 18/18 passing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-07-26 13:42:29 +02:00
parent 4bdf8de950
commit 266f99a2fe
8 changed files with 238 additions and 43 deletions

View file

@ -0,0 +1,28 @@
"""Shared helpers for reins that implement the contract by shelling out to a CLI."""
from __future__ import annotations
import json
import subprocess
import tempfile
from pathlib import Path
from typing import Any
def git_head(repo: str) -> str:
proc = subprocess.run(
["git", "-C", str(Path(repo).expanduser()), "rev-parse", "HEAD"],
capture_output=True,
text=True,
)
return proc.stdout.strip() if proc.returncode == 0 else ""
def write_task_file(title: str, description: str, target_repo: str, **extra: Any) -> str:
task_spec = {"title": title, "description": description, "target_repo": target_repo, **extra}
fd = tempfile.NamedTemporaryFile(
mode="w", suffix=".json", prefix="glas-harness-task-", delete=False
)
json.dump(task_spec, fd)
fd.close()
return fd.name

View file

@ -12,14 +12,12 @@ end + sandbox handoff), not fine-grained tool interception yet.
from __future__ import annotations
import json
import shutil
import subprocess
import tempfile
from pathlib import Path
from typing import Any
from glas_harness.contract import Rein, SandboxHandle, ToolCall, ToolResult
from glas_harness.reins._shared import git_head, write_task_file
class ReinAharnessNotInstalled(RuntimeError):
@ -49,26 +47,15 @@ class ReinAharness(Rein):
if not target_repo:
raise ValueError("no target_repo resolvable from inputs or sandbox reachability")
task_file = inputs.get("task_file")
if not task_file:
task_spec = {
"title": inputs["title"],
"description": inputs["description"],
"target_repo": target_repo,
"agent": inputs.get("agent", "coach"),
}
fd = tempfile.NamedTemporaryFile(
mode="w", suffix=".json", prefix="glas-harness-task-", delete=False
)
json.dump(task_spec, fd)
fd.close()
task_file = fd.name
task_file = inputs.get("task_file") or write_task_file(
inputs["title"], inputs["description"], target_repo, agent=inputs.get("agent", "coach")
)
return {
"sandbox_id": sandbox.sandbox_id,
"task_file": task_file,
"target_repo": target_repo,
"head_before": self._git_head(target_repo),
"head_before": git_head(target_repo),
}
def dispatch_tool(self, session: dict[str, str], tool_call: ToolCall) -> ToolResult:
@ -85,18 +72,9 @@ class ReinAharness(Rein):
return ToolResult(ok=ok, output=proc.stdout, error=None if ok else proc.stderr)
def end_session(self, session: dict[str, str]) -> dict[str, str]:
head_after = self._git_head(session["target_repo"])
head_after = git_head(session["target_repo"])
committed = bool(head_after) and head_after != session.get("head_before")
return {
"commit_sha": head_after or "",
"committed": str(committed),
}
@staticmethod
def _git_head(repo: str) -> str:
proc = subprocess.run(
["git", "-C", str(Path(repo).expanduser()), "rev-parse", "HEAD"],
capture_output=True,
text=True,
)
return proc.stdout.strip() if proc.returncode == 0 else ""

View file

@ -0,0 +1,73 @@
"""ReinOpenWeights — glas-harness adapter around the rein-openweights CLI.
Same shape as reins/rein_aharness.py: rein-openweights's `run` command
performs an entire bounded agentic session (credential acquisition,
OpenRouter tool-calling loop, commit verification) as one opaque unit
no per-tool-call hooks exposed yet. dispatch_tool collapses the whole
run into a single call.
"""
from __future__ import annotations
import shutil
import subprocess
from typing import Any
from glas_harness.contract import Rein, SandboxHandle, ToolCall, ToolResult
from glas_harness.reins._shared import git_head, write_task_file
class ReinOpenWeightsNotInstalled(RuntimeError):
pass
class ReinOpenWeights(Rein):
def __init__(self, cli_bin: str = "rein-openweights", model: str | None = None) -> None:
self.cli_bin = cli_bin
self.model = model
def _bin(self) -> str:
resolved = shutil.which(self.cli_bin)
if not resolved:
raise ReinOpenWeightsNotInstalled(
f"'{self.cli_bin}' not found on PATH — install rein-openweights first"
)
return resolved
def start_session(
self, profile: dict[str, Any], inputs: dict[str, str], sandbox: SandboxHandle
) -> dict[str, str]:
target_repo = (
inputs.get("target_repo")
or sandbox.reachability.get("workspace_dir")
or sandbox.reachability.get("remote_dir")
)
if not target_repo:
raise ValueError("no target_repo resolvable from inputs or sandbox reachability")
task_file = inputs.get("task_file") or write_task_file(
inputs["title"], inputs["description"], target_repo
)
return {
"sandbox_id": sandbox.sandbox_id,
"task_file": task_file,
"target_repo": target_repo,
"head_before": git_head(target_repo),
}
def dispatch_tool(self, session: dict[str, str], tool_call: ToolCall) -> ToolResult:
argv = [self._bin(), "run", "--task-file", session["task_file"], "--no-hub"]
if self.model:
argv += ["--model", self.model]
proc = subprocess.run(argv, capture_output=True, text=True)
ok = proc.returncode == 0
return ToolResult(ok=ok, output=proc.stdout, error=None if ok else proc.stderr)
def end_session(self, session: dict[str, str]) -> dict[str, str]:
head_after = git_head(session["target_repo"])
committed = bool(head_after) and head_after != session.get("head_before")
return {
"commit_sha": head_after or "",
"committed": str(committed),
}