diff --git a/tests/run_agent/test_concurrent_interrupt.py b/tests/run_agent/test_concurrent_interrupt.py index 9a6ba73e..61d7752f 100644 --- a/tests/run_agent/test_concurrent_interrupt.py +++ b/tests/run_agent/test_concurrent_interrupt.py @@ -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