feat: import governed sandbox commits and enforce native CLI limits
Some checks are pending
Governed runtime contract / contract (push) Waiting to run

Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
tegwick 2026-09-09 14:08:10 +02:00
parent 1429db5ad4
commit c63caf5568
16 changed files with 1236 additions and 7 deletions

View file

@ -24,6 +24,7 @@ one aggregate `LLMResponse` at the end for interface compatibility.
from __future__ import annotations
import json
import re
import subprocess
import threading
from pathlib import Path
@ -32,6 +33,7 @@ from typing import Any, Callable
from llm_connect.claude_code import ClaudeCodeAdapter
from llm_connect.exceptions import LLMSubprocessError, LLMTimeoutError
from llm_connect.models import LLMResponse, RunConfig
from rein_aharness.native_limits import NativeLimitError, terminal_accounting, validate_limits
from rein_aharness.execution_cancel import (
ExecutionCancel,
@ -99,6 +101,8 @@ class AgenticClaudeCodeAdapter(ClaudeCodeAdapter):
self._workdir = workdir
self._on_tool_event = on_tool_event
self._cancel = cancel
self._native_config = (None, None)
self._native_cli_checked = False
if isinstance(tool_profile, ToolProfile):
self._profile = tool_profile
else:
@ -109,6 +113,10 @@ class AgenticClaudeCodeAdapter(ClaudeCodeAdapter):
return self._profile
def _build_command(self, config: RunConfig) -> list[str]:
budget = config.model_params.get("max_budget_usd")
turns = config.model_params.get("max_turns")
validate_limits(budget, turns)
self._native_config = (budget, turns)
cmd = [
self._cli_path,
"--print",
@ -119,6 +127,12 @@ class AgenticClaudeCodeAdapter(ClaudeCodeAdapter):
]
if self._on_tool_event is not None:
cmd += ["--output-format", "stream-json", "--include-hook-events", "--verbose"]
elif budget is not None or turns is not None:
cmd += ["--output-format", "json"]
if budget is not None:
cmd += ["--max-budget-usd", str(budget)]
if turns is not None:
cmd += ["--max-turns", str(turns)]
if self._model:
cmd.extend(["--model", self._model])
return cmd
@ -126,12 +140,23 @@ class AgenticClaudeCodeAdapter(ClaudeCodeAdapter):
def execute_prompt(self, prompt: str, config: RunConfig) -> LLMResponse:
self._preflight_budget(config)
cmd = self._build_command(config)
if self._native_config != (None, None) and not self._native_cli_checked:
check = subprocess.run([self._cli_path, "--version"], capture_output=True, text=True, timeout=10, cwd=self._workdir)
version = re.match(r"(\d+)\.(\d+)\.(\d+)(?:\s|$)", check.stdout.strip())
if check.returncode or not version or tuple(map(int, version.groups())) < (2, 1, 217):
raise NativeLimitError("native limits require verified Claude Code 2.1.217 or newer")
self._native_cli_checked = True
timeout = config.timeout_seconds or self._config.timeout_seconds
if self._on_tool_event is not None:
response = self._execute_streaming(cmd, prompt, timeout)
else:
response = self._execute_blocking(cmd, prompt, timeout)
self._consume_budget(config, response)
try:
self._consume_budget(config, response)
except Exception as exc:
if self._native_config != (None, None):
raise NativeLimitError("controlled Claude run failed token accounting", cost_usd=response.metadata.get("cost_usd")) from exc
raise
return response
def _execute_blocking(self, cmd: list[str], prompt: str, timeout: int) -> LLMResponse:
@ -144,7 +169,19 @@ class AgenticClaudeCodeAdapter(ClaudeCodeAdapter):
cwd=self._workdir,
)
stdout, stderr = self._wait_for_process(proc, prompt, timeout)
usage, cost = {}, None
if self._native_config != (None, None):
try:
envelope = json.loads(stdout)
except (TypeError, ValueError) as exc:
raise NativeLimitError("controlled Claude run returned invalid terminal JSON") from exc
usage, cost = terminal_accounting(envelope, max_budget_usd=self._native_config[0], max_turns=self._native_config[1])
stdout = envelope.get("result", "")
if not isinstance(stdout, str):
raise NativeLimitError("controlled Claude run returned invalid result text", cost_usd=cost)
if proc.returncode != 0:
if self._native_config != (None, None):
raise NativeLimitError("controlled Claude CLI exited unsuccessfully", cost_usd=cost)
raise LLMSubprocessError(
f"claude CLI exited with code {proc.returncode}",
return_code=proc.returncode,
@ -153,9 +190,10 @@ class AgenticClaudeCodeAdapter(ClaudeCodeAdapter):
return LLMResponse(
content=stdout,
model=self._model or "claude-code-cli",
usage={},
usage=usage,
finish_reason="stop",
metadata={
"cost_usd": cost,
"provider": "claude-code-agentic",
"cli_path": self._cli_path,
"workdir": str(self._workdir),
@ -174,6 +212,7 @@ class AgenticClaudeCodeAdapter(ClaudeCodeAdapter):
)
text_parts: list[str] = []
tool_event_count = 0
terminal_results = []
def reader() -> None:
nonlocal tool_event_count
@ -186,6 +225,8 @@ class AgenticClaudeCodeAdapter(ClaudeCodeAdapter):
event = json.loads(line)
except json.JSONDecodeError:
continue
if isinstance(event, dict) and event.get("type") == "result":
terminal_results.append(event)
self._handle_stream_event(event, text_parts)
if _is_tool_event(event):
tool_event_count += 1
@ -200,7 +241,14 @@ class AgenticClaudeCodeAdapter(ClaudeCodeAdapter):
reader_thread.join(timeout=5)
stderr = proc.stderr.read() if proc.stderr else ""
usage, cost = {}, None
if self._native_config != (None, None):
if reader_thread.is_alive() or len(terminal_results) != 1:
raise NativeLimitError("controlled Claude stream requires one complete terminal result")
usage, cost = terminal_accounting(terminal_results[0], max_budget_usd=self._native_config[0], max_turns=self._native_config[1])
if returncode != 0:
if self._native_config != (None, None):
raise NativeLimitError("controlled Claude CLI exited unsuccessfully", cost_usd=cost)
raise LLMSubprocessError(
f"claude CLI exited with code {returncode}",
return_code=returncode,
@ -210,7 +258,7 @@ class AgenticClaudeCodeAdapter(ClaudeCodeAdapter):
return LLMResponse(
content="".join(text_parts),
model=self._model or "claude-code-cli",
usage={},
usage=usage,
finish_reason="stop",
metadata={
"provider": "claude-code-agentic",
@ -218,6 +266,7 @@ class AgenticClaudeCodeAdapter(ClaudeCodeAdapter):
"workdir": str(self._workdir),
"tool_profile": self._profile.name,
"tool_event_count": tool_event_count,
"cost_usd": cost,
},
)