Merge pull request 'fix(test_concurrent_interrupt): add _tool_guardrails to _Stub fixture (partial close hermes-agent#9)' (#13) from fix/concurrent-interrupt-stub-guardrails into main
Some checks failed
Tests / e2e (push) Successful in 54s
Tests / test (push) Has been cancelled
Nix / nix (macos-latest) (push) Waiting to run
Docker Build and Publish / build-and-push (push) Has been skipped
Nix / nix (ubuntu-latest) (push) Has been cancelled

This commit is contained in:
claude-ceo-assistant 2026-05-08 21:03:43 +00:00
commit 386c75ecca

View File

@ -20,6 +20,7 @@ def _make_agent(monkeypatch):
monkeypatch.setenv("HERMES_INFERENCE_PROVIDER", "")
# Avoid full AIAgent init — just import the class and build a stub
import run_agent as _ra
from agent.tool_guardrails import ToolCallGuardrailController
class _Stub:
_interrupt_requested = False
@ -53,6 +54,12 @@ def _make_agent(monkeypatch):
self._tool_worker_threads: set = set()
self._tool_worker_threads_lock = threading.Lock()
self._active_children_lock = threading.Lock()
# Mirror AIAgent.__init__ (run_agent.py:1160 — added in 58b89965
# "fix(agent): add tool-call loop guardrails", 2026-04-27).
# _execute_tool_calls_concurrent calls self._tool_guardrails
# .before_call(...) on every tool, so the stub needs a real
# controller instance with default (warning-only) config.
self._tool_guardrails = ToolCallGuardrailController()
def _touch_activity(self, desc):
self._last_activity = time.time()
@ -77,6 +84,14 @@ def _make_agent(monkeypatch):
stub._execute_tool_calls_concurrent = _ra.AIAgent._execute_tool_calls_concurrent.__get__(stub)
stub.interrupt = _ra.AIAgent.interrupt.__get__(stub)
stub.clear_interrupt = _ra.AIAgent.clear_interrupt.__get__(stub)
# Tool-loop guardrails (added in 58b89965, 2026-04-27) are invoked
# before/after every concurrent tool. Bind the real helpers — the
# default ToolCallGuardrailController() above is warning-only so
# they never block a tool, just observe.
stub._append_guardrail_observation = _ra.AIAgent._append_guardrail_observation.__get__(stub)
stub._guardrail_block_result = _ra.AIAgent._guardrail_block_result.__get__(stub)
stub._set_tool_guardrail_halt = lambda *a, **kw: None
stub._tool_guardrail_halt_decision = None
# /steer injection (added in PR #12116) fires after every concurrent
# tool batch. Stub it as a no-op — this test exercises interrupt
# fanout, not steer injection.
@ -107,7 +122,9 @@ def test_concurrent_interrupt_cancels_pending(monkeypatch):
original_invoke = agent._invoke_tool
def slow_tool(name, args, task_id, call_id=None):
def slow_tool(name, args, task_id, call_id=None, **kwargs):
# **kwargs swallows production-only kwargs (messages,
# pre_tool_block_checked) added to _invoke_tool over time.
if name == "slow_one":
# Block until the test sets the interrupt
barrier.wait(timeout=10)
@ -184,7 +201,9 @@ def test_running_concurrent_worker_sees_is_interrupted(monkeypatch):
observed = {"saw_true": False, "poll_count": 0, "worker_tid": None}
worker_started = threading.Event()
def polling_tool(name, args, task_id, call_id=None, messages=None):
def polling_tool(name, args, task_id, call_id=None, messages=None, **kwargs):
# **kwargs swallows production-only kwargs (pre_tool_block_checked)
# added to _invoke_tool over time.
observed["worker_tid"] = threading.current_thread().ident
worker_started.set()
deadline = time.monotonic() + 5.0