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:
Xubin Ren
2026-05-16 16:33:49 +08:00
committed by Xubin Ren
co-authored by Cursor
parent cf09a8d691
commit e804f2fddb
5 changed files with 109 additions and 8 deletions
+6 -5
View File
@@ -35,7 +35,7 @@ from nanobot.providers.factory import ProviderSnapshot
from nanobot.session.goal_state import (
goal_state_runtime_lines,
goal_state_ws_blob,
sustained_goal_active,
runner_wall_llm_timeout_s,
)
from nanobot.session.manager import Session, SessionManager
from nanobot.utils.artifacts import generated_image_paths_from_messages
@@ -253,6 +253,7 @@ class AgentLoop:
restrict_to_workspace=restrict_to_workspace,
disabled_skills=disabled_skills,
max_iterations=self.max_iterations,
llm_wall_timeout_for_session=lambda sk: runner_wall_llm_timeout_s(self.sessions, sk),
)
self._unified_session = unified_session
self._max_messages = max_messages if max_messages > 0 else 120
@@ -795,10 +796,10 @@ class AgentLoop:
injection_callback=_drain_pending,
# Sustained goals may legitimately exceed NANOBOT_LLM_TIMEOUT_S; idle stall
# is still capped by NANOBOT_STREAM_IDLE_TIMEOUT_S in streaming providers.
llm_timeout_s=(
0.0
if session is not None and sustained_goal_active(session.metadata)
else None
llm_timeout_s=runner_wall_llm_timeout_s(
self.sessions,
session.key if session is not None else session_key,
metadata=(session.metadata if session is not None else None),
),
))
finally:
+11 -1
View File
@@ -6,7 +6,7 @@ import time
import uuid
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
from typing import Any, Callable
from loguru import logger
@@ -79,6 +79,7 @@ class SubagentManager:
restrict_to_workspace: bool = False,
disabled_skills: list[str] | None = None,
max_iterations: int | None = None,
llm_wall_timeout_for_session: Callable[[str | None], float | None] | None = None,
):
defaults = AgentDefaults()
self.provider = provider
@@ -96,6 +97,7 @@ class SubagentManager:
)
self.max_concurrent_subagents = defaults.max_concurrent_subagents
self.runner = AgentRunner(provider)
self._llm_wall_timeout_for_session = llm_wall_timeout_for_session
self._running_tasks: dict[str, asyncio.Task[None]] = {}
self._task_statuses: dict[str, SubagentStatus] = {}
self._session_tasks: dict[str, set[str]] = {} # session_key -> {task_id, ...}
@@ -196,6 +198,12 @@ class SubagentManager:
{"role": "user", "content": task},
]
sess_key = origin.get("session_key")
llm_timeout = (
self._llm_wall_timeout_for_session(sess_key)
if self._llm_wall_timeout_for_session
else None
)
result = await self.runner.run(AgentRunSpec(
initial_messages=messages,
tools=tools,
@@ -207,6 +215,8 @@ class SubagentManager:
error_message=None,
fail_on_tool_error=True,
checkpoint_callback=_on_checkpoint,
session_key=sess_key,
llm_timeout_s=llm_timeout,
))
status.phase = "done"
status.stop_reason = result.stop_reason