Nanobot

Nanobot stops an idle timer from killing active Anthropic streams

A Nanobot issue shows its no-callback Anthropic path treating an idle timeout as a total wall-clock limit, terminating long but active generations. The paired patch resets the timer on chunks while separate work budgets model-visible MCP schemas.

← Back to homeOriginal source ↗

HKUDS/nanobot issue #5391 is the inspected primary source: “NANOBOT_STREAM_IDLE_TIMEOUT_S acts as a total timeout on the Anthropic no-callback stream path, killing long but active generations.” The related records below were inspected as supporting context rather than independent confirmation.

The facts

- HKUDS/nanobot issue #5391 was created 2026-08-14T07:08:16Z and was closed at inspection; its title is “NANOBOT_STREAM_IDLE_TIMEOUT_S acts as a total timeout on the Anthropic no-callback stream path, killing long but active generations.” - HKUDS/nanobot PR #5392 was created 2026-08-14T07:30:11Z and was closed at inspection; its title is “fix(anthropic): treat stream idle timeout as inactivity only, not total time.” - HKUDS/nanobot PR #5388 was created 2026-08-13T18:31:44Z and was open at inspection; its title is “feat(agent): budget model-visible MCP schemas.” - The repository reported 47003 stars, 710 open issues, default branch main, and last push 2026-08-14T15:18:14Z. - The latest tagged-release baseline checked was v0.3.0 published 2026-07-25T08:08:47Z.

What changed

HKUDS/nanobot issue #5391 reports: Bug Description With the Anthropic provider, the streaming idle timeout (default 90s) is also applied as a total timeout on the no-callback path of AnthropicProvider.chat_stream (nanobot/providers/anthropic_provider.py:842-845): response = await asyncio.wait_for( stream.get_final_message(), timeout=idle_timeout_s, ) get_final_message() internally consumes the entire stream until completion (SDK until_done()), so the whole generation must finish within idle_timeout_s. A stream that keeps producing chunks but runs longer than 90 seconds in total (long document writes, large tool-call arguments, extended thinking) is killed even though it never stalled — the user sees Error calling LLM: stream stalled for more than 90 seconds despite continuous activity. Introduced by PR #3579 (4c54a2b, "fix(anthropic): auto-fallback to stream on long-request error"), which wrapped get_final_message() in wait_for using the idle value as a wall-clock cap. The callback path already drains the stream per-chunk (timer resets every chunk), so only the no-callback path is affected: memory consolidation/archiving (agent/memory.py:1016 → streaming fallback) and the #2709 "Streaming is required" fallback. Steps to Reproduce 1. Use the Anthropic provider with default settings. 2. Trigger a generation that streams steadily for longer than 90s total (e.g. large memory consolidation, long file write). 3. The turn fails with Error calling LLM: stream stalled for more than 90 seconds even though chunks arrived continuously until the cutoff. Minimal deterministic repro

HKUDS/nanobot PR #5392 reports: Fixes #5391** Summary NANOBOT_STREAM_IDLE_TIMEOUT_S (default 90s) is also applied as a **total** timeout on the no-callback path of AnthropicProvider.chat_stream: get_final_message() internally consumes the entire stream until completion (SDK until_done()), so the whole generation must finish within idle_timeout_s. A stream that keeps producing chunks but runs longer than 90 seconds in total is killed even though it never stalled - the user sees Error calling LLM: stream stalled for more than 90 seconds despite continuous activity. Introduced by #3579 (streaming fallback for long-request errors). Changes AnthropicProvider.chat_stream: the per-chunk idle-timeout drain loop now runs unconditionally (timer resets on every chunk), no longer gated on delta callbacks being present After the stream is drained, get_final_message() is awaited bare (the SDK snapshot is available immediately); the total-timeout wait_for wrapper is removed except asyncio.TimeoutError -> error_kind="timeout" is preserved, so genuinely stalled streams are still cut off by the idle timeout Why Fixes the no-callback paths: memory consolidation/archiving (agent/memory.py -> chat_with_retry -> streaming fallback) and the "Streaming is required" fallback (#2709). Wall-clock protection for streaming is already handled at the runner layer (max(300, timeout_s * 2), #4902), so the provider layer only needs idle semantics. Tests New regression test: 5 chunks at 0.06s intervals (0.30s total) with an idle timeout of 0.15s, no callbacks - asserts

HKUDS/nanobot PR #5388 reports: Summary add an opt-in byte budget for model-visible MCP tool schemas, disabled by default preserve every built-in tool and leave the registered/executable MCP tool set unchanged choose a deterministic MCP subset from the latest visible user request and keep it stable for the run fail open to all MCP schemas when intent is ambiguous, schemas are malformed, or the strongest match cannot fit Draft scope This draft explores the conservative budgeted-view option from the issue without adding another model, embedding, or discovery call. The ASCII lexical matcher intentionally fails open for non-English or ambiguous turns; disabling the setting restores existing behavior. Relates to #5298. Validation focused runner, configuration, and selector tests: 129 passed Ruff: passed BasedPyright: 0 errors, 0 warnings, 0 notes full suite before the final exact-byte adjustment: 6372 passed, 48 skipped, with 5 unrelated Windows/date-boundary baseline failures; the focused suite, Ruff, and BasedPyright were rerun after that adjustment git diff --check: passed

Why it matters

Timeouts must measure the condition named in the setting. A stream that is producing tokens is not idle, while oversized tool schemas can consume the context before generation begins. The operator test is whether the system remains bounded and its receipts still describe the action after failure, retry or restart.

Current

The primary record was closed when captured on 2026-08-15. The tagged-release baseline was v0.3.0 published 2026-07-25T08:08:47Z. Repository metadata, full source bodies, current pull requests and issues, releases, Google News, Hacker News, Lobsters, Metamesh and the rendered ClawCharts row were inspected. ClawCharts selected the subject; it did not prove the claim.

Evidence

The primary URL is https://github.com/HKUDS/nanobot/issues/5391. Supporting records are https://github.com/HKUDS/nanobot/pull/5392 and https://github.com/HKUDS/nanobot/pull/5388. Source bodies, timestamps and states are preserved in the daily evidence bundle. Test counts and reproductions remain attributed to their authors unless identified as independently rerun.

Source boundary

Open work is described as open, closed work as closed, and operator reports as reports. A pull request is evidence of proposed or reviewed direction, not proof of a shipped release. Search residue, package mirrors and historically published source spines were excluded.

Operator take

Test active streams beyond the configured idle window, separately cap total duration if desired, and account for tool-schema tokens before selecting a generation budget. Preserve a before-state receipt, make the smallest reversible change, and verify the original failure independently.

Caveat

Public project records are mutable. Status, scope and evidence can change after publication. This brief records the inspected state and does not authorize changes to a reader’s deployment.

Source inspected; source state, environment and release boundary remain explicit.