Nanobot
Nanobot blocks stale background tasks from overwriting new sessions
Nanobot patches a race where title generation held an old Session across an await, then overwrote the replacement created by /new. Related changes make streamed delivery log exactly once and retain bounded subagent transcripts across success, failure and cancellation.
HKUDS/nanobot PR #5271 is the inspected primary source: “fix(session): prevent stale background task saves from overwriting session data.” The related records below were inspected as supporting context rather than independent confirmation.
The facts
- HKUDS/nanobot PR #5271 was created 2026-08-06T12:05:17Z and was open at inspection; its title is “fix(session): prevent stale background task saves from overwriting session data.” - HKUDS/nanobot PR #5206 was created 2026-08-01T13:52:17Z and was open at inspection; its title is “fix(delivery): log streamed responses exactly once.” - HKUDS/nanobot PR #5291 was created 2026-08-07T17:16:04Z and was open at inspection; its title is “fix(agent): persist subagent conversation transcripts.” - The repository reported 46775 stars, 792 open issues, default branch main, and last push 2026-08-08T17:40:22Z. - The latest tagged-release baseline checked was v0.3.0 published 2026-07-25T08:08:47Z.
What changed
HKUDS/nanobot PR #5271 reports: Summary Background tasks like maybe_generate_webui_title hold a Session reference across an await provider.chat_with_retry(...) call. If the user runs /new during that await window, cmd_new calls session.clear() → save() → invalidate(), and the next turn creates a brand-new Session object. When the background task resumes, it writes its stale reference via sessions.save(session), **overwriting the new session's messages on disk** with the old (now-empty) object. Messages are silently lost — no exception is raised. Root cause _cancel_active_tasks (loop.py:788) only cancels per-key tasks in _active_tasks, while background tasks are scheduled into the global _background_tasks set and are never cancelled by /new. Fix Two layers of defense: 1. **save() identity check** (session/manager.py): Before persisting, compare the incoming session object against the cached session via is. If they differ, the caller holds a stale reference — discard the write. 2. **maybe_generate_webui_title re-fetch** (session/webui_turns.py): After the LLM await, re-check get_cached(session_key). If the cached object has changed, skip the title update entirely. The save() check covers all background paths (autocompact, consolidation, title generation) in one place.
HKUDS/nanobot PR #5206 reports: Summary Streamed responses were logged twice: once at stream finalization in TurnDelivery._publish_stream_end and again in AgentLoop._assemble_outbound. This produced duplicate 'Response to' log lines for every streamed message. Change Accumulate streamed content in TurnDelivery (_stream_content) and log it once at stream finalization. Skip the Response to log in AgentLoop._assemble_outbound when the response was streamed. Every delivered message is now recorded exactly once, regardless of delivery path. Testing Verified the streamed path logs a single 'Delivered streamed response' line and the non-streamed path still logs 'Response to'.
HKUDS/nanobot PR #5291 reports: Summary Subagent runs previously left nothing behind: once a background subagent finished, only the rendered announcement of its final result survived. Its full conversation — the tool calls, results, and reasoning steps — vanished with the process, so there was no way to review what a subagent actually did or debug why a task failed. Every subagent run now leaves a durable transcript. The full message exchange is persisted per task as normalized JSONL under /memory/subagents/.jsonl, captured on every exit path: success, tool error, max iterations, provider error, and cancellation (a cancelled run flushes whatever partial exchange the hook captured). Transcripts stay isolated from memory/history.jsonl and session stores, so they never leak into main-agent prompt injection or Dream consolidation. Key design decisions: **Capture point:** the transcript is read from the runner's existing hook lifecycle (on_finally), which fires with the full-or-partial message list on every exit path — no AgentRunner changes. **Read-back:** transcripts live inside the agent workspace, so the main agent can read them with the existing read_file tool using the task id returned
Why it matters
State ownership, delivery receipts and transcripts are one trust surface: each event needs one current owner, one durable representation and an explicit retention boundary. The operator test is whether visible output, retained state and authority still describe the same event after retries, background work or restart.
Current
The primary record was open when captured on 2026-08-09. 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/pull/5271. Supporting records are https://github.com/HKUDS/nanobot/pull/5206 and https://github.com/HKUDS/nanobot/pull/5291. 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
Use identity or generation checks before every delayed save, record delivery once at its owner, and keep diagnostic history bounded and isolated from prompts. 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.