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
+22 -2
View File
@@ -1,8 +1,8 @@
"""Session metadata helpers for sustained goals (e.g. ``long_task`` / ``complete_goal``).
Tools set ``metadata[GOAL_STATE_KEY]``. Reads accept the legacy session key ``thread_goal``
for older sessions. The agent uses ``goal_state_runtime_lines`` and
``goal_state_ws_blob`` without importing tool implementations.
for older sessions. Callers use ``goal_state_runtime_lines``, ``goal_state_ws_blob``, and
``runner_wall_llm_timeout_s`` without importing tool implementations.
"""
from __future__ import annotations
@@ -10,6 +10,8 @@ from __future__ import annotations
import json
from typing import Any, Mapping, MutableMapping
from nanobot.session.manager import SessionManager
GOAL_STATE_KEY = "goal_state"
# Older builds stored the same JSON blob under this key.
_LEGACY_GOAL_STATE_SESSION_KEY = "thread_goal"
@@ -89,3 +91,21 @@ def goal_state_ws_blob(metadata: Mapping[str, Any] | None) -> dict[str, Any]:
blob["objective"] = objective
return blob
return {"active": False}
def runner_wall_llm_timeout_s(
sessions: SessionManager,
session_key: str | None,
*,
metadata: Mapping[str, Any] | None = None,
) -> float | None:
"""Wall-clock cap for :class:`~nanobot.agent.runner.AgentRunner` when streaming an LLM.
Returns ``0.0`` to disable ``asyncio.wait_for`` around the request when a sustained goal is
active; ``None`` means use ``NANOBOT_LLM_TIMEOUT_S``. Pass in-memory ``metadata`` when the
caller already holds :attr:`~nanobot.session.manager.Session.metadata` for this turn.
"""
meta: Mapping[str, Any] | None = metadata
if meta is None and session_key:
meta = sessions.get_or_create(session_key).metadata
return 0.0 if sustained_goal_active(meta) else None