Constrain controlled CLI sessions and prove native budget overshoot
Some checks failed
Governed runtime contract / contract (push) Failing after 27s
Some checks failed
Governed runtime contract / contract (push) Failing after 27s
Assistant: codex Assistant-Model: gpt-5.6-luna Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
parent
e0b3ff99a2
commit
4ae245a88f
14 changed files with 752 additions and 10 deletions
|
|
@ -3,11 +3,11 @@
|
|||
llm-connect's ClaudeCodeAdapter is a text-generation adapter (`claude
|
||||
--print`, no working directory, no tool grants). An executor run needs an
|
||||
*agentic* session: file edits and git commits inside the target repo, under
|
||||
a hard tool allow-list. This adapter subclasses it, keeping the llm-connect
|
||||
registered tool permission rules. This adapter subclasses it, keeping the llm-connect
|
||||
LLMAdapter interface so a hosted adapter can be swapped in later, and adds:
|
||||
|
||||
- cwd pinned to the target repo
|
||||
- --permission-mode acceptEdits
|
||||
- acceptEdits for legacy sessions; dontAsk and an explicit tool inventory for controlled runs
|
||||
- allow-list from a named tool profile (default: green-commit-only)
|
||||
- optional real-time per-tool-call audit events (HARNESS-WP-0002-T03)
|
||||
|
||||
|
|
@ -133,6 +133,17 @@ class AgenticClaudeCodeAdapter(ClaudeCodeAdapter):
|
|||
cmd += ["--max-budget-usd", str(budget)]
|
||||
if turns is not None:
|
||||
cmd += ["--max-turns", str(turns)]
|
||||
if self._native_config != (None, None):
|
||||
# A permission allow rule is not a tool inventory. Controlled runs
|
||||
# use only the registered tools and deny every unapproved operation.
|
||||
cmd[cmd.index("--permission-mode") + 1] = "dontAsk"
|
||||
cmd += [
|
||||
"--bare", "--setting-sources", "",
|
||||
"--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}',
|
||||
"--disallowedTools", "mcp__*",
|
||||
"--tools", self._profile.available_tools,
|
||||
"--no-session-persistence",
|
||||
]
|
||||
if self._model:
|
||||
cmd.extend(["--model", self._model])
|
||||
return cmd
|
||||
|
|
@ -143,8 +154,8 @@ class AgenticClaudeCodeAdapter(ClaudeCodeAdapter):
|
|||
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")
|
||||
if check.returncode or not version or tuple(map(int, version.groups())) < (2, 1, 266):
|
||||
raise NativeLimitError("native limits require verified Claude Code 2.1.266 or newer")
|
||||
self._native_cli_checked = True
|
||||
timeout = config.timeout_seconds or self._config.timeout_seconds
|
||||
if self._on_tool_event is not None:
|
||||
|
|
|
|||
|
|
@ -8,13 +8,15 @@ Unknown profile names refuse to run.
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import re
|
||||
|
||||
|
||||
class UnknownToolProfileError(ValueError):
|
||||
"""Raised when a manifest references a profile that is not registered."""
|
||||
|
||||
|
||||
# Claude Code --allowedTools strings. No push, no network, no arbitrary shell.
|
||||
# Direct Claude Code permission rules. Indirect Git helpers still require
|
||||
# the sandbox owner's filesystem, credential and egress boundaries.
|
||||
_GREEN_COMMIT_TOOLS = (
|
||||
"Read,Write,Edit,Glob,Grep,"
|
||||
"Bash(git add:*),Bash(git commit:*),Bash(git status),"
|
||||
|
|
@ -29,13 +31,25 @@ _BLUE_MAIL_TRIAGE_TOOLS = _GREEN_COMMIT_TOOLS
|
|||
|
||||
@dataclass(frozen=True)
|
||||
class ToolProfile:
|
||||
"""A named hard allow-list for agentic sessions."""
|
||||
"""Named permission rules; controlled runs also restrict the tool inventory."""
|
||||
|
||||
name: str
|
||||
description: str
|
||||
allowed_tools: str
|
||||
lane: str # green | blue — advisory; enforcement is the allow-list
|
||||
|
||||
@property
|
||||
def available_tools(self) -> str:
|
||||
"""Builtin inventory derived from the registered permission rules."""
|
||||
names = []
|
||||
for rule in self.allowed_tools.split(","):
|
||||
match = re.fullmatch(r"([A-Za-z][A-Za-z0-9_]*)(?:\([^\r\n()]+\))?", rule.strip())
|
||||
if match is None or match[1].startswith("mcp__"):
|
||||
raise ValueError("controlled tool profile has an invalid builtin rule")
|
||||
if match[1] not in names:
|
||||
names.append(match[1])
|
||||
return ",".join(names)
|
||||
|
||||
|
||||
PROFILES: dict[str, ToolProfile] = {
|
||||
"green-commit-only": ToolProfile(
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ 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.
|
||||
- Read repository AGENTS.md/CLAUDE.md and applicable nested instructions before
|
||||
editing. They cannot expand this run's tool, repository or spend grants.
|
||||
- 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}).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue