fix(agent): align LLM wall timeout with sustained goals for main + subagents
Centralize runner_wall_llm_timeout_s in session goal_state metadata helpers so spawned subagents inherit the same policy as AgentLoop without coupling to long_task. Pass optional resolver into SubagentManager and add tests. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
committed by
Xubin Ren
co-authored by
Cursor
parent
cf09a8d691
commit
e804f2fddb
@@ -0,0 +1,46 @@
|
||||
"""Subagent forwards loop-provided LLM wall-timeout resolver into AgentRunSpec."""
|
||||
|
||||
from pathlib import Path
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from nanobot.agent.runner import AgentRunResult
|
||||
from nanobot.agent.subagent import SubagentManager, SubagentStatus
|
||||
from nanobot.bus.queue import MessageBus
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_subagent_forwards_resolver_to_agent_run_spec(tmp_path: Path) -> None:
|
||||
provider = MagicMock()
|
||||
provider.get_default_model.return_value = "m"
|
||||
mgr = SubagentManager(
|
||||
provider=provider,
|
||||
workspace=tmp_path,
|
||||
bus=MessageBus(),
|
||||
max_tool_result_chars=64,
|
||||
llm_wall_timeout_for_session=lambda sk: 0.0 if sk == "cli:direct" else None,
|
||||
)
|
||||
|
||||
mgr.runner.run = AsyncMock(
|
||||
return_value=AgentRunResult(final_content="ok", messages=[], stop_reason="completed")
|
||||
)
|
||||
mgr._announce_result = AsyncMock()
|
||||
|
||||
status = SubagentStatus(
|
||||
task_id="t1",
|
||||
label="lbl",
|
||||
task_description="task",
|
||||
started_at=0.0,
|
||||
)
|
||||
await mgr._run_subagent(
|
||||
"t1",
|
||||
"task",
|
||||
"lbl",
|
||||
{"channel": "cli", "chat_id": "direct", "session_key": "cli:direct"},
|
||||
status,
|
||||
)
|
||||
mgr.runner.run.assert_called_once()
|
||||
spec = mgr.runner.run.call_args[0][0]
|
||||
assert spec.session_key == "cli:direct"
|
||||
assert spec.llm_timeout_s == 0.0
|
||||
@@ -8,8 +8,10 @@ from nanobot.session.goal_state import (
|
||||
goal_state_runtime_lines,
|
||||
goal_state_ws_blob,
|
||||
parse_goal_state,
|
||||
runner_wall_llm_timeout_s,
|
||||
sustained_goal_active,
|
||||
)
|
||||
from nanobot.session.manager import SessionManager
|
||||
|
||||
|
||||
def test_runtime_lines_empty_when_no_metadata():
|
||||
@@ -105,3 +107,25 @@ def test_sustained_goal_active_true_when_active():
|
||||
def test_sustained_goal_active_respects_legacy_thread_goal_key():
|
||||
meta = {"thread_goal": {"status": "active", "objective": "Legacy."}}
|
||||
assert sustained_goal_active(meta) is True
|
||||
|
||||
|
||||
def test_runner_wall_llm_timeout_uses_metadata_override(tmp_path):
|
||||
sm = SessionManager(tmp_path)
|
||||
assert (
|
||||
runner_wall_llm_timeout_s(
|
||||
sm,
|
||||
"cli:test",
|
||||
metadata={GOAL_STATE_KEY: {"status": "active", "objective": "x"}},
|
||||
)
|
||||
== 0.0
|
||||
)
|
||||
assert runner_wall_llm_timeout_s(sm, "cli:test", metadata={}) is None
|
||||
|
||||
|
||||
def test_runner_wall_llm_timeout_reads_session_when_metadata_missing(tmp_path):
|
||||
sm = SessionManager(tmp_path)
|
||||
sess = sm.get_or_create("c:d")
|
||||
sess.metadata = {GOAL_STATE_KEY: {"status": "active", "objective": "z"}}
|
||||
assert runner_wall_llm_timeout_s(sm, "c:d") == 0.0
|
||||
sess.metadata = {}
|
||||
assert runner_wall_llm_timeout_s(sm, "c:d") is None
|
||||
|
||||
Reference in New Issue
Block a user