fix(subagents): resolve the subagent registry off the event loop (#8189)

task_tool and batch_task_tool read the subagent registry synchronously
inside their async tool bodies. Every registry read sweeps the
managed-definition store for its cache signature on a one-second TTL
miss - the file store globs and stats the definitions directory, the
SQL store runs a per-row query - and the sandbox-policy check may read
config files. On the streaming event loop that stalls every concurrent
run for the duration of the IO, the same class of loop stall removed
for tool assembly in issue #5172. Dispatch these hops through
run_assembly like the other blocking resolves in the same tools.
This commit is contained in:
hataa 2026-11-13 14:16:23 +08:00 committed by GitHub
parent 94447d12b6
commit ea4aee716e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 343 additions and 70 deletions

View File

@ -35,6 +35,7 @@ from deerflow.subagents.batch_runtime import (
from deerflow.subagents.registry import get_available_subagent_names, get_subagent_config
from deerflow.tools.sync import make_sync_tool_wrapper
from deerflow.tools.types import Runtime
from deerflow.utils.assembly_io import run_assembly
class BatchTaskItem(BaseModel):
@ -215,8 +216,11 @@ async def batch_task(
metadata = runtime.config.get("metadata", {}) if runtime is not None else {}
app_config = _batch_app_config(runtime)
allowed_subagents = metadata.get("allowed_subagents")
available = get_available_subagent_names(app_config=app_config, allowed_subagents=allowed_subagents)
config = get_subagent_config(subagent_type, app_config=app_config)
# Registry reads sweep the managed-definition store for its signature
# (file stats, or a SQL query) on every TTL miss; resolve them on the
# assembly pool so they cannot stall the streaming event loop (issue #5172).
available = await run_assembly(get_available_subagent_names, app_config=app_config, allowed_subagents=allowed_subagents)
config = await run_assembly(get_subagent_config, subagent_type, app_config=app_config)
if config is None or subagent_type not in available:
names = ", ".join(available) if available else "none"
return _result(

View File

@ -954,7 +954,12 @@ async def task_tool(
# caller can retry safely. The context_mode guard is the first branch
# executed inside the helper, and catalog discovery was moved after the
# guard so an invalid mode never triggers a managed-subagent store access.
preflight_config, preflight_error = _preflight_delegation_checks(
# The helper sweeps the managed-definition store for its signature (file
# stats, or a SQL query) on every TTL miss, and the sandbox-policy check
# may read config files, so it resolves on the assembly pool like every
# other blocking hop below (issue #5172).
preflight_config, preflight_error = await run_assembly(
_preflight_delegation_checks,
subagent_type=subagent_type,
context_mode=context_mode,
runtime_app_config=runtime_app_config,

View File

@ -0,0 +1,264 @@
"""Regression: subagent registry reads resolve off the event loop.
``_managed_definitions`` sweeps the managed-definition store for its cache
signature on every TTL miss (one second): the file store globs and stats the
definitions directory, the SQL store runs a per-row query, and
``get_available_subagent_names`` may additionally read config files through
``is_host_bash_allowed``. The ``task`` and ``batch_task`` tools run on the
streaming event loop, so those reads must be dispatched to the assembly pool
like every other blocking hop (issue #5172).
Under the strict Blockbuster context (this directory's conftest), the real
``FileManagedSubagentStore`` signature sweep raises ``BlockingError`` when it
runs on the loop, so the anchors below fail against a plain synchronous
registry call and pass only when the dispatch survives. The meta-check at the
end proves the probe has teeth by calling the store directly on the loop.
"""
from __future__ import annotations
import importlib
import threading
from enum import Enum
from pathlib import Path
from types import SimpleNamespace
import pytest
from langchain_core.messages import ToolMessage
from deerflow.config.subagents_config import SubagentsAppConfig
from deerflow.persistence.managed_subagents import ManagedSubagentDefinition
from deerflow.persistence.managed_subagents.file import FileManagedSubagentStore
from deerflow.subagents import registry
from deerflow.tools.builtins.batch_task_tool import BatchTaskItem
# importlib.import_module binds the real module: the package attribute
# ``deerflow.tools.builtins.task_tool`` is shadowed by the StructuredTool.
task_tool_module = importlib.import_module("deerflow.tools.builtins.task_tool")
batch_task_tool_module = importlib.import_module("deerflow.tools.builtins.batch_task_tool")
pytestmark = pytest.mark.asyncio
class _FakeSubagentStatus(Enum):
COMPLETED = "completed"
FAILED = "failed"
RUNNING = "running"
@property
def is_terminal(self) -> bool:
return self is not _FakeSubagentStatus.RUNNING
def _managed_definition_payload() -> str:
return ManagedSubagentDefinition(
name="researcher",
description="Managed researcher",
system_prompt="You are researcher.",
enabled=True,
).model_dump_json()
def _observe_file_store(observed_threads: list) -> FileManagedSubagentStore:
class _ObservingFileStore(FileManagedSubagentStore):
def signature(self):
observed_threads.append(threading.current_thread())
# Real production blocking read (glob + stat inside a deerflow
# frame): trips the strict gate when it runs on the loop.
return super().signature()
def list(self):
observed_threads.append(threading.current_thread())
return super().list()
return _ObservingFileStore()
def _install_real_file_store(monkeypatch, tmp_path: Path, observed_threads: list) -> FileManagedSubagentStore:
# Patch the file store's own get_paths binding instead of constructing a
# Paths(base_dir=...) here: resolving the base dir does blocking IO and
# this helper runs inside the strict gate, in async test bodies.
root = tmp_path / "managed-subagents"
root.mkdir(parents=True, exist_ok=True)
(root / "researcher.json").write_text(_managed_definition_payload(), encoding="utf-8")
store = _observe_file_store(observed_threads)
monkeypatch.setattr(
"deerflow.persistence.managed_subagents.file.get_paths",
lambda: SimpleNamespace(managed_subagents_dir=root),
)
monkeypatch.setattr(registry, "get_managed_subagent_store", lambda _config=None: store)
# Keep the per-agent override resolution off the real config files: the
# sweep under test is the managed-definition store, not AppConfig loading.
monkeypatch.setattr(registry, "_resolve_subagents_app_config", lambda _app_config=None: SubagentsAppConfig())
registry._clear_managed_definitions_cache()
return store
def _completed_result() -> SimpleNamespace:
return SimpleNamespace(
status=_FakeSubagentStatus.COMPLETED,
ai_messages=[],
result="done",
error=None,
stop_reason=None,
token_usage_records=[],
usage_reported=False,
tool_receipts=None,
bash_executions=None,
)
class _DummyExecutor:
def __init__(self, **_kwargs):
pass
def execute_async(self, _prompt, task_id=None):
return task_id or "generated-task-id"
async def test_task_tool_registry_reads_resolve_off_loop(monkeypatch, tmp_path):
"""task_tool resolves the subagent registry on a worker thread."""
observed_threads: list = []
_install_real_file_store(monkeypatch, tmp_path, observed_threads)
monkeypatch.setattr(task_tool_module, "SubagentStatus", _FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", _DummyExecutor)
# Host-bash policy and the inherit-path app-config fallback read
# AppConfig files; neither is the probe target here.
monkeypatch.setattr(task_tool_module, "is_host_bash_allowed", lambda *_a, **_k: True)
monkeypatch.setattr(task_tool_module, "get_app_config", lambda: SimpleNamespace(models=[SimpleNamespace(name="default-model")]))
# Tool assembly is already anchored in test_tool_assembly_offloop.py; stub
# it here so the probe target stays the registry sweep.
monkeypatch.setattr("deerflow.tools.get_available_tools", lambda **_kwargs: [])
monkeypatch.setattr(task_tool_module, "get_background_task_result", lambda _task_id: _completed_result())
monkeypatch.setattr(task_tool_module, "get_stream_writer", lambda: lambda _event: None)
async def _no_sleep(_: float) -> None:
return None
monkeypatch.setattr(task_tool_module.asyncio, "sleep", _no_sleep)
tool = task_tool_module.task_tool
invoke = getattr(tool, "coroutine", None) or getattr(tool, "func", None)
assert invoke is not None
command = await invoke(
runtime=None,
description="test",
prompt="p",
subagent_type="researcher",
tool_call_id="tc-registry-offloop",
)
messages = command.update["messages"]
assert len(messages) == 1
assert isinstance(messages[0], ToolMessage)
assert "Task Succeeded" in messages[0].content
assert observed_threads, "the managed-definition signature sweep must be invoked"
assert all(thread is not threading.main_thread() for thread in observed_threads)
async def test_batch_task_registry_reads_resolve_off_loop(monkeypatch, tmp_path):
"""batch_task resolves the subagent registry on a worker thread.
The submitter is a failing stub: batch_task validates the registry (and
the caller policy) BEFORE attempting submission, so the sweep must have
run — off the loop — by the time the submission error is reported.
"""
observed_threads: list = []
_install_real_file_store(monkeypatch, tmp_path, observed_threads)
class _FailingSubmitter:
accepts_continuation_authority = False
async def submit(self, _request):
raise RuntimeError("no durable batch backend in this test")
async def get_batch(self, **_kwargs):
return None
monkeypatch.setattr(batch_task_tool_module, "_batch_submitter", lambda: _FailingSubmitter())
tool = batch_task_tool_module.batch_task
invoke = getattr(tool, "coroutine", None) or getattr(tool, "func", None)
assert invoke is not None
result = await invoke(
runtime=SimpleNamespace(
state={},
context={},
config={"configurable": {"thread_id": "thread-1"}},
),
title="batch",
items=[BatchTaskItem(key="k1", prompt="p")],
subagent_type="researcher",
tool_call_id="tc-batch-registry-offloop",
)
messages = result.update["messages"]
assert len(messages) == 1
assert "Batch submission failed" in messages[0].content
assert observed_threads, "the managed-definition signature sweep must be invoked"
assert all(thread is not threading.main_thread() for thread in observed_threads)
async def test_registry_signature_sweep_trips_the_gate(monkeypatch, tmp_path):
"""Meta-check: the real file-store signature sweep called on the loop must
raise BlockingError, so the anchors above cannot go vacuously green."""
from blockbuster import BlockingError
observed_threads: list = []
store = _install_real_file_store(monkeypatch, tmp_path, observed_threads)
with pytest.raises(BlockingError):
store.signature()
assert observed_threads == [threading.main_thread()]
async def test_task_tool_sandbox_policy_resolves_off_loop(monkeypatch, tmp_path):
"""The subagent_type="bash" sandbox-policy hop dispatches to a worker thread.
``is_host_bash_allowed(config=None)`` falls through to ``get_app_config()``,
which stats and hashes the config files on every call — so flattening this
one dispatch back to a synchronous call would silently return that read to
the loop even though the registry anchors stay green (they never enter the
bash branch). A real blocking file-read probe as ``is_host_bash_allowed``
keeps it teeth-verified like every other offload in this file.
"""
probe_file = tmp_path / "sandbox-policy-probe.txt"
probe_file.write_text("probe body", encoding="utf-8")
observed_threads: list = []
def _blocking_probe_is_host_bash_allowed(_config=None):
# Real filesystem IO: trips the strict gate when it runs on the loop.
probe_file.read_text(encoding="utf-8")
observed_threads.append(threading.current_thread())
return True
_install_real_file_store(monkeypatch, tmp_path, [])
monkeypatch.setattr(task_tool_module, "SubagentStatus", _FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", _DummyExecutor)
monkeypatch.setattr(task_tool_module, "is_host_bash_allowed", _blocking_probe_is_host_bash_allowed)
monkeypatch.setattr(task_tool_module, "get_app_config", lambda: SimpleNamespace(models=[SimpleNamespace(name="default-model")]))
monkeypatch.setattr("deerflow.tools.get_available_tools", lambda **_kwargs: [])
monkeypatch.setattr(task_tool_module, "get_background_task_result", lambda _task_id: _completed_result())
monkeypatch.setattr(task_tool_module, "get_stream_writer", lambda: lambda _event: None)
async def _no_sleep(_: float) -> None:
return None
monkeypatch.setattr(task_tool_module.asyncio, "sleep", _no_sleep)
tool = task_tool_module.task_tool
invoke = getattr(tool, "coroutine", None) or getattr(tool, "func", None)
assert invoke is not None
command = await invoke(
runtime=None,
description="test",
prompt="p",
subagent_type="bash",
tool_call_id="tc-sandbox-policy-offloop",
)
messages = command.update["messages"]
assert len(messages) == 1
assert isinstance(messages[0], ToolMessage)
assert "Task Succeeded" in messages[0].content
assert observed_threads, "the sandbox-policy read must be invoked"
assert all(thread is not threading.main_thread() for thread in observed_threads)

View File

@ -110,7 +110,7 @@ async def test_task_tool_assembles_off_loop(monkeypatch, tmp_path):
monkeypatch.setattr(
task_tool_module,
"get_subagent_config",
lambda _name: SubagentConfig(
lambda _name, **_kwargs: SubagentConfig(
name="general-purpose",
description="General helper",
system_prompt="Base system prompt",
@ -188,7 +188,7 @@ async def test_task_tool_inherit_app_config_resolves_off_loop(monkeypatch, tmp_p
monkeypatch.setattr(
task_tool_module,
"get_subagent_config",
lambda _name: SubagentConfig(
lambda _name, **_kwargs: SubagentConfig(
name="general-purpose",
description="General helper",
system_prompt="Base system prompt",

View File

@ -346,8 +346,8 @@ class _DummyScheduledTask:
def test_task_tool_returns_error_for_unknown_subagent(monkeypatch):
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: None)
monkeypatch.setattr(task_tool_module, "get_available_subagent_names", lambda: ["general-purpose"])
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: None)
monkeypatch.setattr(task_tool_module, "get_available_subagent_names", lambda **_kwargs: ["general-purpose"])
result = _run_task_tool(
runtime=None,
@ -368,12 +368,12 @@ def test_task_tool_enforces_caller_subagent_snapshot(monkeypatch):
runtime.config["metadata"]["allowed_subagents"] = ["planner"]
captured = {}
def available(*, allowed_subagents):
captured["allowed"] = allowed_subagents
def available(**kwargs):
captured["allowed"] = kwargs.get("allowed_subagents")
return ["planner"]
monkeypatch.setattr(task_tool_module, "get_available_subagent_names", available)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: _make_subagent_config())
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: _make_subagent_config())
result = _run_task_tool(
runtime=runtime,
@ -392,8 +392,8 @@ def test_task_tool_enforces_caller_subagent_snapshot(monkeypatch):
def test_task_tool_explains_when_caller_policy_permits_no_subagents(monkeypatch):
runtime = _make_runtime()
runtime.config["metadata"]["allowed_subagents"] = []
monkeypatch.setattr(task_tool_module, "get_available_subagent_names", lambda *, allowed_subagents: [])
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: _make_subagent_config())
monkeypatch.setattr(task_tool_module, "get_available_subagent_names", lambda **_kwargs: [])
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: _make_subagent_config())
result = _run_task_tool(
runtime=runtime,
@ -429,7 +429,7 @@ def test_task_tool_forwards_the_run_extension_snapshot_to_executor(monkeypatch):
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: _make_subagent_config())
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: _make_subagent_config())
monkeypatch.setattr(
task_tool_module,
"get_background_task_result",
@ -462,7 +462,7 @@ def test_task_tool_installs_and_closes_narrow_middleware_recorder(monkeypatch):
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: _make_subagent_config())
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: _make_subagent_config())
monkeypatch.setattr(
task_tool_module,
"get_background_task_result",
@ -499,7 +499,7 @@ def test_task_tool_omits_extensions_without_a_run_snapshot(monkeypatch):
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: _make_subagent_config())
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: _make_subagent_config())
monkeypatch.setattr(
task_tool_module,
"get_background_task_result",
@ -631,7 +631,7 @@ def test_task_tool_forwards_channel_user_id_to_executor(monkeypatch):
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: _make_subagent_config())
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: _make_subagent_config())
monkeypatch.setattr(
task_tool_module,
"get_background_task_result",
@ -669,7 +669,7 @@ def test_task_tool_forwards_is_internal_true_to_executor(monkeypatch):
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: _make_subagent_config())
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: _make_subagent_config())
monkeypatch.setattr(
task_tool_module,
"get_background_task_result",
@ -700,7 +700,7 @@ def test_task_tool_preserves_overwrite_sandbox_state_for_executor(monkeypatch):
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: _make_subagent_config())
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: _make_subagent_config())
monkeypatch.setattr(
task_tool_module,
"get_background_task_result",
@ -731,7 +731,7 @@ def test_task_tool_forwards_is_internal_false_to_executor(monkeypatch):
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: _make_subagent_config())
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: _make_subagent_config())
monkeypatch.setattr(
task_tool_module,
"get_background_task_result",
@ -760,7 +760,7 @@ def test_task_tool_copies_attributes_to_executor(monkeypatch):
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: _make_subagent_config())
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: _make_subagent_config())
monkeypatch.setattr(
task_tool_module,
"get_background_task_result",
@ -790,7 +790,7 @@ def test_task_tool_rejects_non_mapping_attributes(monkeypatch):
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: _make_subagent_config())
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: _make_subagent_config())
monkeypatch.setattr("deerflow.tools.get_available_tools", lambda **kwargs: [])
runtime = _make_runtime()
@ -800,9 +800,9 @@ def test_task_tool_rejects_non_mapping_attributes(monkeypatch):
def test_task_tool_rejects_bash_subagent_when_host_bash_disabled(monkeypatch):
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: _make_subagent_config())
monkeypatch.setattr(task_tool_module, "get_available_subagent_names", lambda: ["general-purpose"])
monkeypatch.setattr(task_tool_module, "is_host_bash_allowed", lambda: False)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: _make_subagent_config())
monkeypatch.setattr(task_tool_module, "get_available_subagent_names", lambda **_kwargs: ["general-purpose"])
monkeypatch.setattr(task_tool_module, "is_host_bash_allowed", lambda _config=None: False)
result = _run_task_tool(
runtime=_make_runtime(),
@ -834,7 +834,7 @@ def test_task_tool_threads_runtime_app_config_to_subagent_dependencies(monkeypat
captured["prompt"] = prompt
return task_id or "generated-task-id"
def fake_get_available_subagent_names(*, app_config):
def fake_get_available_subagent_names(*, app_config, allowed_subagents=None):
captured["names_app_config"] = app_config
return ["bash"]
@ -928,7 +928,7 @@ def test_task_tool_emits_running_and_completed_events(monkeypatch):
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
def get_result(execution_id):
polled_execution_ids.append(execution_id)
@ -1009,7 +1009,7 @@ def test_task_tool_emits_cumulative_usage_on_running_event(monkeypatch):
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: task_id}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(task_tool_module, "get_background_task_result", lambda _: next(responses))
monkeypatch.setattr(task_tool_module, "get_stream_writer", lambda: events.append)
monkeypatch.setattr(task_tool_module.asyncio, "sleep", _no_sleep)
@ -1043,8 +1043,8 @@ def test_rejected_task_does_not_capture_parent_history(monkeypatch, context_mode
if rejection == "caller-policy":
runtime.config["metadata"]["allowed_subagents"] = []
monkeypatch.setattr(task_tool_module, "get_available_subagent_names", lambda **kwargs: [] if rejection == "caller-policy" else ["general-purpose"])
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: None if rejection == "unknown" else _make_subagent_config())
monkeypatch.setattr(task_tool_module, "is_host_bash_allowed", lambda: False)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: None if rejection == "unknown" else _make_subagent_config())
monkeypatch.setattr(task_tool_module, "is_host_bash_allowed", lambda _config=None: False)
capture = MagicMock(wraps=task_tool_module.ParentContextSnapshot.from_state)
monkeypatch.setattr(task_tool_module.ParentContextSnapshot, "from_state", capture)
executor = MagicMock()
@ -1082,7 +1082,7 @@ def test_task_tool_context_mode_captures_dispatch_time_history(monkeypatch, cont
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: _make_subagent_config())
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: _make_subagent_config())
monkeypatch.setattr(task_tool_module, "get_background_task_result", lambda _: _make_result(FakeSubagentStatus.COMPLETED, result="done"))
monkeypatch.setattr(task_tool_module, "get_stream_writer", lambda: lambda event: None)
monkeypatch.setattr(task_tool_module.asyncio, "sleep", _no_sleep)
@ -1126,7 +1126,7 @@ def test_task_dispatch_snapshot_does_not_include_unfinished_ambiguous_calls(monk
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: _make_subagent_config())
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: _make_subagent_config())
monkeypatch.setattr(task_tool_module, "get_background_task_result", lambda _: _make_result(FakeSubagentStatus.COMPLETED, result="done"))
monkeypatch.setattr(task_tool_module, "get_stream_writer", lambda: lambda event: None)
monkeypatch.setattr(task_tool_module.asyncio, "sleep", _no_sleep)
@ -1181,7 +1181,7 @@ def test_task_tool_propagates_tool_groups_to_subagent(monkeypatch, mcp_plugins):
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(
task_tool_module,
"get_background_task_result",
@ -1232,7 +1232,7 @@ def test_task_tool_uses_subagent_model_override_for_tool_loading(monkeypatch):
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(
task_tool_module,
"get_background_task_result",
@ -1275,7 +1275,7 @@ def test_task_tool_inherits_parent_skill_allowlist_for_default_subagent(monkeypa
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(
task_tool_module,
"get_background_task_result",
@ -1321,7 +1321,7 @@ def test_task_tool_intersects_parent_and_subagent_skill_allowlists(monkeypatch):
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(
task_tool_module,
"get_background_task_result",
@ -1360,7 +1360,7 @@ def test_task_tool_no_tool_groups_passes_none(monkeypatch):
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(
task_tool_module,
"get_background_task_result",
@ -1398,7 +1398,7 @@ def test_task_tool_runtime_none_passes_groups_none(monkeypatch):
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(
task_tool_module,
"get_background_task_result",
@ -1437,7 +1437,7 @@ def test_task_tool_runtime_none_passes_groups_none(monkeypatch):
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: task_id}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(
task_tool_module,
@ -1485,7 +1485,7 @@ def test_task_tool_inherit_model_resolves_app_config_off_loop(monkeypatch):
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(
task_tool_module,
"get_background_task_result",
@ -1521,7 +1521,7 @@ def test_task_tool_returns_timed_out_message(monkeypatch):
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: task_id}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(
task_tool_module,
@ -1557,7 +1557,7 @@ def test_task_tool_surfaces_stop_reason_for_capped_run(monkeypatch):
events = []
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(
task_tool_module,
"get_background_task_result",
@ -1599,7 +1599,7 @@ def test_task_tool_polling_safety_timeout(monkeypatch):
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: task_id}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
receipts = [
{
@ -1652,7 +1652,7 @@ def test_cleanup_called_on_completed(monkeypatch):
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: task_id}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(
task_tool_module,
@ -1692,7 +1692,7 @@ def test_cleanup_called_on_failed(monkeypatch):
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: task_id}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(
task_tool_module,
@ -1735,7 +1735,7 @@ def test_cleanup_called_on_timed_out(monkeypatch):
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: task_id}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(
task_tool_module,
@ -1812,7 +1812,7 @@ def test_cleanup_not_called_on_polling_safety_timeout(monkeypatch):
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: task_id}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(
task_tool_module,
@ -1882,7 +1882,7 @@ def test_cleanup_scheduled_on_cancellation(monkeypatch):
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: task_id}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(task_tool_module, "get_background_task_result", get_result)
monkeypatch.setattr(task_tool_module, "get_stream_writer", lambda: events.append)
@ -1928,7 +1928,7 @@ def test_task_started_emit_failure_stops_subagent_reports_usage_and_cleans_up(mo
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: task_id}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(task_tool_module, "get_background_task_result", lambda _: terminal_result)
monkeypatch.setattr(task_tool_module, "get_stream_writer", lambda: lambda _event: None)
monkeypatch.setattr(task_tool_module, "aemit_custom_event", failing_emit)
@ -2014,7 +2014,7 @@ def test_unexpected_poller_error_deferred_cleanup_survives_sync_invocation(monke
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: execution_id}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(task_tool_module, "get_background_task_result", flip_terminal_off_caller_thread)
monkeypatch.setattr(task_tool_module, "get_stream_writer", lambda: lambda _event: None)
monkeypatch.setattr(task_tool_module, "aemit_custom_event", fail_on_status_emit)
@ -2108,7 +2108,7 @@ def test_unexpected_error_with_failing_status_accessor_preserves_exception_and_a
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: execution_id}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(task_tool_module, "get_background_task_result", broken_accessor)
monkeypatch.setattr(task_tool_module, "get_stream_writer", lambda: lambda _event: None)
monkeypatch.setattr(task_tool_module, "aemit_custom_event", ok_emit)
@ -2190,7 +2190,7 @@ def test_unexpected_error_re_raised_promptly_via_short_grace(monkeypatch):
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: "exec-grace"}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(task_tool_module, "get_background_task_result", lambda _: _make_result(FakeSubagentStatus.RUNNING, ai_messages=["partial"]))
monkeypatch.setattr(task_tool_module, "get_stream_writer", lambda: lambda _event: None)
monkeypatch.setattr(task_tool_module, "aemit_custom_event", fail_on_second_emit)
@ -2298,7 +2298,7 @@ def test_deferred_cleanup_drops_final_usage_when_parent_loop_closed(monkeypatch)
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: execution_id}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(task_tool_module, "get_background_task_result", flip_terminal_and_grow_usage_after_caller_teardown)
monkeypatch.setattr(task_tool_module, "get_stream_writer", lambda: lambda _event: None)
monkeypatch.setattr(task_tool_module, "aemit_custom_event", fail_on_status_emit)
@ -2437,7 +2437,7 @@ def test_deferred_final_usage_reported_on_parent_loop_with_real_recorder(monkeyp
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: execution_id}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(task_tool_module, "get_background_task_result", flip_terminal_and_grow_usage_from_deferred_thread)
monkeypatch.setattr(task_tool_module, "get_stream_writer", lambda: lambda _event: None)
monkeypatch.setattr(task_tool_module, "aemit_custom_event", fail_on_status_emit)
@ -2646,7 +2646,7 @@ async def test_unexpected_error_grace_wait_cancellation_is_honored(monkeypatch):
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: execution_id}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(task_tool_module, "get_background_task_result", lambda _: _make_result(FakeSubagentStatus.RUNNING, ai_messages=[]))
monkeypatch.setattr(task_tool_module, "get_stream_writer", lambda: lambda _event: None)
monkeypatch.setattr(task_tool_module, "aemit_custom_event", fail_on_status_emit)
@ -2735,7 +2735,7 @@ def test_execute_async_failure_leaves_no_background_residue(monkeypatch):
},
),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(task_tool_module, "cleanup_background_task", lambda execution_id_arg: cleanup_calls.append(execution_id_arg))
monkeypatch.setattr(
task_tool_module,
@ -2803,7 +2803,7 @@ def test_cancelled_cleanup_stops_after_timeout(monkeypatch):
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: task_id}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(task_tool_module, "get_stream_writer", lambda: events.append)
monkeypatch.setattr(task_tool_module.asyncio, "sleep", cancel_on_first_sleep)
monkeypatch.setattr(task_tool_module, "run_on_isolated_subagent_loop", fake_run_on_isolated_subagent_loop)
@ -2867,7 +2867,7 @@ def test_cancellation_wait_uses_subagent_polling_budget(monkeypatch):
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: task_id}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(task_tool_module, "get_background_task_result", get_result)
monkeypatch.setattr(task_tool_module, "get_stream_writer", lambda: events.append)
monkeypatch.setattr(task_tool_module.asyncio, "sleep", cancel_then_continue)
@ -2968,7 +2968,7 @@ def test_cancellation_calls_request_cancel(monkeypatch):
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: "execution-cancel"}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(
task_tool_module,
@ -3021,7 +3021,7 @@ def test_task_tool_returns_cancelled_message(monkeypatch):
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: task_id}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(task_tool_module, "get_background_task_result", lambda _: next(responses))
monkeypatch.setattr(task_tool_module, "get_stream_writer", lambda: events.append)
@ -3058,7 +3058,7 @@ def test_task_tool_emits_completed_metadata(monkeypatch):
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: task_id}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(task_tool_module, "get_background_task_result", lambda _: _make_result(FakeSubagentStatus.COMPLETED, result="done"))
monkeypatch.setattr(task_tool_module, "get_stream_writer", lambda: lambda _: None)
monkeypatch.setattr(task_tool_module.asyncio, "sleep", _no_sleep)
@ -3092,7 +3092,7 @@ def test_task_tool_emits_disappeared_task_metadata(monkeypatch):
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: task_id}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(task_tool_module, "get_background_task_result", lambda _: None)
monkeypatch.setattr(task_tool_module, "get_stream_writer", lambda: events.append)
monkeypatch.setattr(task_tool_module, "cleanup_background_task", lambda _: None)
@ -3124,7 +3124,7 @@ def test_task_tool_bounds_large_result_metadata(monkeypatch):
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: task_id}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(task_tool_module, "get_background_task_result", lambda _: _make_result(FakeSubagentStatus.COMPLETED, result=huge))
monkeypatch.setattr(task_tool_module, "get_stream_writer", lambda: lambda _: None)
monkeypatch.setattr(task_tool_module.asyncio, "sleep", _no_sleep)
@ -3195,7 +3195,7 @@ def test_cancellation_reports_subagent_usage(monkeypatch):
"SubagentExecutor",
type("DummyExecutor", (), {"__init__": lambda self, **kwargs: None, "execute_async": lambda self, prompt, task_id=None: task_id}),
)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(task_tool_module, "get_background_task_result", get_result)
monkeypatch.setattr(task_tool_module, "get_stream_writer", lambda: events.append)
monkeypatch.setattr(task_tool_module.asyncio, "sleep", cancel_on_third_sleep)
@ -3251,7 +3251,7 @@ def test_terminal_events_include_usage(monkeypatch, status, expected_type):
result = _make_result(status, result="ok" if status == FakeSubagentStatus.COMPLETED else None, error="err" if status != FakeSubagentStatus.COMPLETED else None, token_usage_records=records)
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(task_tool_module, "get_background_task_result", lambda _: result)
monkeypatch.setattr(task_tool_module, "get_stream_writer", lambda: events.append)
monkeypatch.setattr(task_tool_module, "aemit_custom_event", fake_emit_custom_event)
@ -3287,7 +3287,7 @@ def test_terminal_event_usage_none_when_no_records(monkeypatch):
result = _make_result(FakeSubagentStatus.COMPLETED, result="done", token_usage_records=[])
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: config)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: config)
monkeypatch.setattr(task_tool_module, "get_background_task_result", lambda _: result)
monkeypatch.setattr(task_tool_module, "get_stream_writer", lambda: events.append)
monkeypatch.setattr(task_tool_module.asyncio, "sleep", _no_sleep)
@ -3381,7 +3381,7 @@ def _run_completed_task_tool(monkeypatch, *, result_text: str, tool_receipts: li
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: _make_subagent_config())
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: _make_subagent_config())
monkeypatch.setattr(
task_tool_module,
"get_background_task_result",
@ -3430,7 +3430,7 @@ def _run_completed_task_tool_with_criteria(monkeypatch, *, criteria: list[str],
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: _make_subagent_config())
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: _make_subagent_config())
monkeypatch.setattr(
task_tool_module,
"get_background_task_result",
@ -3490,7 +3490,7 @@ def test_task_tool_acceptance_check_failure_is_isolated(monkeypatch):
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: _make_subagent_config())
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: _make_subagent_config())
monkeypatch.setattr(
task_tool_module,
"get_background_task_result",
@ -3564,7 +3564,7 @@ def test_task_tool_failed_carries_receipts_without_verdict(monkeypatch):
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: _make_subagent_config())
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: _make_subagent_config())
monkeypatch.setattr(
task_tool_module,
"get_background_task_result",
@ -3600,7 +3600,7 @@ def _capture_executor_call(monkeypatch, **call_kwargs):
monkeypatch.setattr(task_tool_module, "SubagentStatus", FakeSubagentStatus)
monkeypatch.setattr(task_tool_module, "SubagentExecutor", DummyExecutor)
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _: _make_subagent_config())
monkeypatch.setattr(task_tool_module, "get_subagent_config", lambda _name, **_kwargs: _make_subagent_config())
monkeypatch.setattr(
task_tool_module,
"get_background_task_result",