fix subagent max iteration limit

This commit is contained in:
hanyuanling
2026-04-30 13:45:40 +08:00
committed by Xubin Ren
parent f8fd9f0011
commit 3c20d16117
5 changed files with 165 additions and 5 deletions
+119 -3
View File
@@ -54,11 +54,129 @@ async def test_subagent_exec_tool_receives_allowed_env_keys(tmp_path):
mgr.runner.run.assert_awaited_once()
@pytest.mark.asyncio
async def test_subagent_uses_configured_max_iterations(tmp_path):
"""Subagents should honor the configured tool-iteration limit."""
from nanobot.agent.subagent import SubagentManager, SubagentStatus
from nanobot.bus.queue import MessageBus
bus = MessageBus()
provider = MagicMock()
provider.get_default_model.return_value = "test-model"
mgr = SubagentManager(
provider=provider,
workspace=tmp_path,
bus=bus,
max_tool_result_chars=_MAX_TOOL_RESULT_CHARS,
max_iterations=37,
)
mgr._announce_result = AsyncMock()
async def fake_run(spec):
assert spec.max_iterations == 37
return SimpleNamespace(
stop_reason="done",
final_content="done",
error=None,
tool_events=[],
)
mgr.runner.run = AsyncMock(side_effect=fake_run)
status = SubagentStatus(
task_id="sub-1", label="label", task_description="do task", started_at=time.monotonic()
)
await mgr._run_subagent(
"sub-1", "do task", "label", {"channel": "test", "chat_id": "c1"}, status
)
mgr.runner.run.assert_awaited_once()
def test_subagent_default_max_iterations_matches_agent_defaults(tmp_path):
"""Direct SubagentManager construction should use the agent default limit."""
from nanobot.agent.subagent import SubagentManager
from nanobot.bus.queue import MessageBus
bus = MessageBus()
provider = MagicMock()
provider.get_default_model.return_value = "test-model"
mgr = SubagentManager(
provider=provider,
workspace=tmp_path,
bus=bus,
max_tool_result_chars=_MAX_TOOL_RESULT_CHARS,
)
assert mgr.max_iterations == AgentDefaults().max_tool_iterations
def test_agent_loop_passes_max_iterations_to_subagents(tmp_path):
"""AgentLoop's configured limit should be shared with spawned subagents."""
from nanobot.agent.loop import AgentLoop
from nanobot.bus.queue import MessageBus
bus = MessageBus()
provider = MagicMock()
provider.get_default_model.return_value = "test-model"
loop = AgentLoop(
bus=bus,
provider=provider,
workspace=tmp_path,
model="test-model",
max_iterations=42,
)
assert loop.subagents.max_iterations == 42
@pytest.mark.asyncio
async def test_agent_loop_syncs_updated_max_iterations_before_run(tmp_path):
"""Runtime max_iterations changes should be reflected before tool execution."""
from nanobot.agent.loop import AgentLoop
from nanobot.bus.queue import MessageBus
bus = MessageBus()
provider = MagicMock()
provider.get_default_model.return_value = "test-model"
loop = AgentLoop(
bus=bus,
provider=provider,
workspace=tmp_path,
model="test-model",
max_iterations=42,
)
loop.tools.get_definitions = MagicMock(return_value=[])
async def fake_run(spec):
assert spec.max_iterations == 55
assert loop.subagents.max_iterations == 55
return SimpleNamespace(
stop_reason="done",
final_content="done",
error=None,
tool_events=[],
messages=[],
usage={},
had_injections=False,
tools_used=[],
)
loop.runner.run = AsyncMock(side_effect=fake_run)
loop.max_iterations = 55
await loop._run_agent_loop([])
loop.runner.run.assert_awaited_once()
@pytest.mark.asyncio
async def test_drain_pending_blocks_while_subagents_running(tmp_path):
"""_drain_pending should block when no messages are available but sub-agents are still running."""
from nanobot.agent.loop import AgentLoop
from nanobot.agent.subagent import SubagentManager
from nanobot.bus.events import InboundMessage
from nanobot.bus.queue import MessageBus
from nanobot.session.manager import Session
@@ -74,8 +192,6 @@ async def test_drain_pending_blocks_while_subagents_running(tmp_path):
injection_callback = None
# Capture the injection_callback that _run_agent_loop creates
original_run = loop.runner.run
async def fake_runner_run(spec):
nonlocal injection_callback
injection_callback = spec.injection_callback