The `_Stub` fixture in tests/run_agent/test_concurrent_interrupt.py
bypasses `AIAgent.__init__`, so it must mirror any new instance attributes
that production methods rely on. Tool-loop guardrails (introduced in
58b89965 "fix(agent): add tool-call loop guardrails", 2026-04-27) added
three integration points to `_execute_tool_calls_concurrent`:
1. `self._tool_guardrails.before_call(...)` per tool (run_agent.py:9447)
2. `self._append_guardrail_observation(...)` per result (run_agent.py:9672)
3. `self._guardrail_block_result(...)` for blocked calls
`_Stub` defined none of these, so both
`test_concurrent_interrupt_cancels_pending` and
`test_running_concurrent_worker_sees_is_interrupted` raised
`AttributeError: '_Stub' object has no attribute '_tool_guardrails'`
on the first concurrent tool call.
Fix:
- Add a real `ToolCallGuardrailController()` instance attribute, matching
AIAgent.__init__ at run_agent.py:1160. Default config is warning-only
so the controller observes but never blocks — the tests still exercise
interrupt fanout, not guardrail behaviour.
- Bind the real `_append_guardrail_observation` and `_guardrail_block_result`
helpers from AIAgent (same pattern as the existing `_execute_tool_calls_concurrent`
/ `interrupt` / `clear_interrupt` bindings).
- Stub `_set_tool_guardrail_halt` as a no-op + add `_tool_guardrail_halt_decision = None`.
- Widen `slow_tool` and `polling_tool` side-effect signatures with `**kwargs`
to swallow new production-only `_invoke_tool` kwargs (`messages`,
`pre_tool_block_checked`).
Verification:
- pytest tests/run_agent/test_concurrent_interrupt.py -v # 4/4 pass
- pytest tests/run_agent/ # 1193 passed,
9 skipped, only pre-existing test_primary_runtime_restore failure
(issue #9 cluster, untouched here).
Diff scope: single file, 21 insertions, 2 modifications.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>