fix(execute_code): report in-script tool errors the script ignored

A script that calls hermes_tools.write_file/patch and never prints the
returned {"error": ...} got status=success with empty output, so the
model believed the write happened. In a cross-agent bench this cost 3 of
18 Opus runs 4-6 extra turns (write_file refused by the read-before-write
guard, then re-probing and re-emitting whole files). The result now
carries tool_errors for that cell, and the in-script write_file doc says
existing files must be read first.
This commit is contained in:
teknium1
2026-09-28 01:51:01 -07:00
committed by Teknium
parent 1b91b8eaa5
commit 3e1bfb2935
5 changed files with 60 additions and 8 deletions

View File

@@ -314,6 +314,7 @@ class SessionKernel:
self.stop_event = threading.Event()
self.death_pipe_w: Optional[int] = None
self.tool_call_log: List = []
self.cell_log_start = 0
self.tool_call_counter: List[int] = [0]
# Cells currently attached (bumped under the registry lock on selection, dropped when the
# cell settles). Reaping/cap-eviction skip attached kernels: tearing one down mid-spawn
@@ -802,6 +803,10 @@ def _cell_result(kernel: SessionKernel, key: Tuple, status: str, payload: Dict[s
"execution_count": kernel.execution_count, "state_reset": state_reset},
}
result.update(stdout_metadata)
from tools.code_execution_rpc import tool_errors_since
tool_errors = tool_errors_since(kernel.tool_call_log, kernel.cell_log_start)
if tool_errors:
result["tool_errors"] = tool_errors
# Cell-side spill (runner clipped before replying): same read_file recipe as the host-side spill.
cell_spill = str(payload.get("stdout_spill_path", "") or "")
if cell_spill and payload.get("stdout_clipped"):
@@ -882,6 +887,7 @@ def _run_cell(kernel: SessionKernel, key: Tuple, code: str, *, task_id: str, chi
assert kernel.proc is not None and kernel.proc.stdin is not None
# Per-cell tool budget: the RPC loop enforces counter < max; reset without restarting.
kernel.tool_call_counter[0] = 0
kernel.cell_log_start = len(kernel.tool_call_log)
kernel.raw.drain(), kernel.stderr.drain() # raw output leaked between cells belongs to no cell
kernel.cell_authority = authority
kernel.proc.stdin.write((json.dumps({"id": uuid.uuid4().hex, "code": code}) + "\n").encode("utf-8"))