Add the delegation harness (A13) — step 7b
A frontier model receives a scoped sub-task, works autonomously with full tool access, and hands back; the local loop reads the handback note and the file state, never the reasoning. - Branch at delegation start, and abort discards it. That required committing each turn's work onto the branch: without commits, deleting a branch discards nothing, so "abort discards the work" was a comforting phrase rather than a fact. The caller's own in-flight changes are committed to their branch first, so an abort cannot destroy work nobody delegated - Hard caps on turns and wall clock; a breach is a defined failure return, and its result is not usable, so the caller gates rather than resuming on a half-edited tree - Every event is checkpointed as it arrives, so a crash mid-delegation leaves the branch at a known commit and a truthful transcript - Handback is validated locally: blank fields or a non-boolean success flag are refused and the delegation continues rather than accepting a useless note Two failure modes found by running it against a real model: - A success claim with nothing changed was accepted. The handback plus file state is the entire interface back, so a note describing work that does not exist is worse than no note. Success now requires a successful mutating tool call; reporting failure still does not, since "this is impossible" is a legitimate finding - The delegate invented the absolute path /workspace, wrote there, ran its tests there, and handed back truthfully — with the repo empty and git seeing nothing. Writes outside the workspace are now refused with a message naming the real root, and both prompts state where the workspace is instead of leaving the model to guess. This is not sandboxing: work outside the repo is invisible to git, so the milestone would publish an empty PR Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1c8e7e35fc
commit
53ed11f9c4
7 files changed
+824
-7
No files matched your search
@@ -20,6 +20,11 @@ from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
# Tools that change or verify something. Reading a file or listing a
|
||||
# directory is a model looking around, not doing the work, so neither counts
|
||||
# as evidence that work was carried out.
|
||||
MUTATING_TOOLS = frozenset({"write_file", "run_command"})
|
||||
|
||||
DEFAULT_TIMEOUT_S = 120
|
||||
MAX_OUTPUT_CHARS = 4000
|
||||
|
||||
@@ -110,6 +115,12 @@ class Workspace:
|
||||
candidate = Path(path)
|
||||
return candidate if candidate.is_absolute() else self.root / candidate
|
||||
|
||||
def _inside(self, target: Path) -> bool:
|
||||
try:
|
||||
return target.resolve().is_relative_to(self.root.resolve())
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
def dispatch(self, tool: str, arguments: dict[str, Any]) -> ToolOutcome:
|
||||
try:
|
||||
match tool:
|
||||
@@ -137,6 +148,18 @@ class Workspace:
|
||||
|
||||
def write_file(self, path: str, content: str) -> ToolOutcome:
|
||||
target = self._resolve(path)
|
||||
# Not a sandbox — the VM is the trust boundary (B3). This is about the
|
||||
# work existing at all: a file written outside the repo is invisible
|
||||
# to git, so the milestone would publish an empty PR while the model
|
||||
# truthfully reports success. Models do guess plausible absolute paths
|
||||
# like /workspace, so say where the workspace actually is.
|
||||
if not self._inside(target):
|
||||
return ToolOutcome(
|
||||
f"refusing to write outside the workspace: {path}. "
|
||||
f"The workspace root is {self.root}; write there, using "
|
||||
"relative paths.",
|
||||
success=False,
|
||||
)
|
||||
try:
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_text(content)
|
||||
@@ -154,6 +177,14 @@ class Workspace:
|
||||
return ToolOutcome(f"could not list {path}: {exc}", success=False)
|
||||
return ToolOutcome(_truncate("\n".join(entries) or "(empty)"), success=True)
|
||||
|
||||
def describe(self) -> str:
|
||||
"""Told to the model, so it does not have to guess where it is."""
|
||||
return (
|
||||
f"Your workspace is {self.root}. It is a git repository and the "
|
||||
"only place your work is recorded; commands run there by default. "
|
||||
"Use relative paths."
|
||||
)
|
||||
|
||||
def run_command(self, command: str) -> ToolOutcome:
|
||||
try:
|
||||
# Popen rather than run(): on timeout, run() kills only the direct
|
||||
|
||||
Reference in new issue
Block a user