perf(code): refresh /threads cache after each turn - #5174
Merged
Conversation
The `/threads` picker paints from an in-memory cache that was only filled once at startup, so a thread created mid-session was missing from the first paint until the modal's database query, agent scan, and row rebuild finished. Refreshing the cache at turn end removes that wait. Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Mason Daugherty (mdrxy)
marked this pull request as ready for review
July 30, 2026 14:54
Mason Daugherty (mdrxy)
pushed a commit
that referenced
this pull request
Jul 30, 2026
> [!CAUTION] > Merging this PR will automatically publish to **PyPI** and create a **GitHub release**. For the full release process, see [`.github/RELEASING.md`](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md). --- _Release notes preview: keep this section in sync with the package `CHANGELOG.md`. Publish reads the merged CHANGELOG via `release.yml`, not this PR description — keep them aligned anyway so the PR stays an accurate historical record for reviewers and anyone returning later._ --- ## [0.1.50](deepagents-code==0.1.49...deepagents-code==0.1.50) (2026-07-30) ### Highlights - Added project hooks workspace trust and expanded Hooks v2 support with client and server lifecycle events plus runtime feedback ([#5105](#5105), [#5104](#5104), [#4997](#4997), [#5045](#5045)). - Added an option to mute the “YOLO is active” toast ([#5103](#5103)). - Made the splash screen `thread` ID clickable to copy it ([#5173](#5173)). - Show `ask_user` answers directly on the answered tool row ([#5100](#5100)). - Show a toast when submitting an empty required `ask_user` answer ([#5095](#5095)). - Added thread message counts to the Debug Console ([#5117](#5117)). ### Fixes and improvements - Gated Hooks v2 behind `DEEPAGENTS_CODE_EXPERIMENTAL` and improved hook resume stability across identity and Command tool results ([#5146](#5146), [#5176](#5176)). - Kept server hook state out of task results ([#5164](#5164)). - Stopped duplicate Auto transcript events during interrupt replay ([#5157](#5157)). - Kept `/update` and `/install --package` prompts responsive ([#5127](#5127)). - Refreshed the `/threads` cache after each turn ([#5174](#5174)). - Anchored toasts above the chat input and added a toast when media is dropped into a free-text question ([#5101](#5101), [#5099](#5099)). - Improved thread status message styling and links ([#5118](#5118)). - Made resume hints echo the launched command name ([#5119](#5119)). - Scoped selection copy to the clicked screen ([#5140](#5140)). - Ignored mouse hits on detached widgets ([#5114](#5114)). _End release notes preview._ --- > [!NOTE] > A **New Contributors** section is appended to the GitHub release notes automatically at publish time (see [Release Pipeline](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md#release-pipeline), step 2). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: langchain-oss-automated-triage[bot] <248757908+langchain-oss-automated-triage[bot]@users.noreply.github.com> Co-authored-by: Johannes du Plessis <johannes@langchain.dev>
Mason Daugherty (mdrxy)
added a commit
that referenced
this pull request
Jul 30, 2026
A thread you just started now shows up in the `/threads` switcher as soon as you open it, instead of appearing a moment later. --- The thread picker paints instantly from an in-memory cache of recent threads and only re-queries the session database afterwards. That cache was populated exactly once, by a startup worker, so any thread created during the session was absent from the first paint: the new row only appeared after the modal's list query, its configured-agent filesystem scan, and a full teardown/rebuild of every row widget had completed. On a large `sessions.db` the list query alone measured ~0.2 s warm (~0.8 s on the first call of a process, which also builds the covering index), which is exactly the window where the user sees a list that is missing the thread they just used. Rather than trying to make that refresh path faster, this keeps the cache honest: `_cleanup_agent_task` — the turn-end hook that already refreshes the git branch — now schedules the same prewarm the app runs at startup, in an exclusive worker group so overlapping turns coalesce. Turn end is the right moment because the turn's checkpoints have just been written and the app is otherwise idle, and it is cheap to repeat: the per-thread message-count and initial-prompt caches are keyed on checkpoint freshness, so only the thread that actually changed is read back from disk. With the cache current, the picker's first paint already contains the new thread (with its updated timestamp and message count), and the follow-up query finds nothing to change, so the row rebuild is skipped entirely. Deletion already pruned this cache in place, so this fills in the other half of keeping it in sync with in-session activity. Scheduling is skipped while the app is exiting or not running, mirroring the existing skill-rediscovery guard. Made by [Open SWE](https://openswe.vercel.app/agents/35c3b07d-9fe1-e077-4cdf-e6b7854b4f18) Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Mason Daugherty (mdrxy)
pushed a commit
that referenced
this pull request
Jul 30, 2026
> [!CAUTION] > Merging this PR will automatically publish to **PyPI** and create a **GitHub release**. For the full release process, see [`.github/RELEASING.md`](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md). --- _Release notes preview: keep this section in sync with the package `CHANGELOG.md`. Publish reads the merged CHANGELOG via `release.yml`, not this PR description — keep them aligned anyway so the PR stays an accurate historical record for reviewers and anyone returning later._ --- ## [0.1.50](deepagents-code==0.1.49...deepagents-code==0.1.50) (2026-07-30) ### Highlights - Added project hooks workspace trust and expanded Hooks v2 support with client and server lifecycle events plus runtime feedback ([#5105](#5105), [#5104](#5104), [#4997](#4997), [#5045](#5045)). - Added an option to mute the “YOLO is active” toast ([#5103](#5103)). - Made the splash screen `thread` ID clickable to copy it ([#5173](#5173)). - Show `ask_user` answers directly on the answered tool row ([#5100](#5100)). - Show a toast when submitting an empty required `ask_user` answer ([#5095](#5095)). - Added thread message counts to the Debug Console ([#5117](#5117)). ### Fixes and improvements - Gated Hooks v2 behind `DEEPAGENTS_CODE_EXPERIMENTAL` and improved hook resume stability across identity and Command tool results ([#5146](#5146), [#5176](#5176)). - Kept server hook state out of task results ([#5164](#5164)). - Stopped duplicate Auto transcript events during interrupt replay ([#5157](#5157)). - Kept `/update` and `/install --package` prompts responsive ([#5127](#5127)). - Refreshed the `/threads` cache after each turn ([#5174](#5174)). - Anchored toasts above the chat input and added a toast when media is dropped into a free-text question ([#5101](#5101), [#5099](#5099)). - Improved thread status message styling and links ([#5118](#5118)). - Made resume hints echo the launched command name ([#5119](#5119)). - Scoped selection copy to the clicked screen ([#5140](#5140)). - Ignored mouse hits on detached widgets ([#5114](#5114)). _End release notes preview._ --- > [!NOTE] > A **New Contributors** section is appended to the GitHub release notes automatically at publish time (see [Release Pipeline](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md#release-pipeline), step 2). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: langchain-oss-automated-triage[bot] <248757908+langchain-oss-automated-triage[bot]@users.noreply.github.com> Co-authored-by: Johannes du Plessis <johannes@langchain.dev>
Mason Daugherty (mdrxy)
added a commit
that referenced
this pull request
Aug 4, 2026
Interrupting a turn, or exiting while a background thread-cache refresh is in flight, no longer strands an unawaited coroutine or an open SQLite handle. --- The unit suite emitted three families of warnings, all at teardown. Two are ours and are fixed here; the third is upstream and is filtered. ### An unawaited turn coroutine `_send_to_agent` handed `run_worker` an already-built `_run_agent_task` coroutine. Textual never runs the work of a worker cancelled before its first event-loop step, so that coroutine was finalized unawaited — a `RuntimeWarning`, and (once interpreter teardown had gone far enough to break the import machinery its cleanup relies on) an unraisable `KeyError: '__import__'`. Passing a callable means the coroutine only exists if the worker actually runs, so there is nothing to strand. Several tests were closing that coroutine by hand purely to silence the warning; those workarounds are gone, and a new test pins the callable contract. **Where it started:** #5196. Handing `run_worker` a coroutine dates all the way back to the original Textual REPL (#686), but it was harmless until something cancelled a worker before its first step. #5196 added the recovery path for exactly that situation along with the tests that exercise it. Its parent commit runs the app test module with zero `never awaited` warnings; #5196 itself produces three. ### An unclosed SQLite handle `aiosqlite` opens the database on its worker thread and hands the raw `sqlite3.Connection` back through a future, recording it on the connection only when the awaiting coroutine resumes. A cancel landing anywhere in that window left the handle unreachable from the cleanup that follows, so the garbage collector reported `ResourceWarning: unclosed database`. There are two halves to the window, and both are now covered: - Cancelled while the worker is still opening, the library has no handle recorded yet, so the cleanup it queues closes nothing. The session module now records the handle from the worker thread the moment the connector returns. - Cancelled after the handle is delivered but before the coroutine resumes, the library clears its own reference before that queued cleanup can run — so it again closes nothing. The guard now also queues an explicit close ahead of the library's cleanup, while the handle is still reachable. Both closes run on the thread that opened the handle, and closing twice is a no-op, so neither disturbs a normal shutdown. `get_checkpointer` builds its connection through the same helper rather than `AsyncSqliteSaver.from_conn_string`, so it gets the same guard. **Where it started:** #5174. The prewarm that reads the session database has existed since #1481, but it ran once at startup, so it had normally finished before anything cancelled it. #5174 re-fires it after every turn, which reliably leaves a session-DB read in flight when a test app exits. Counting handles that `aiosqlite` opened and never closed across the goal-command tests: zero on the parent commit, fourteen on #5174. ### A `typing` deprecation from `google-genai` `google.genai.types` builds a union alias out of `typing._UnionGenericAlias`, which CPython 3.14 deprecates, and it fires at import before any of this package's code runs. This one is not a regression from any change here — `deepagents-code` has been tested on the 3.14 leg since the package was created in #3027, and the warning appears wherever a test imports the Google integration. It is tracked upstream as [googleapis/python-genai#1640](googleapis/python-genai#1640) and still unfixed as of `google-genai` 2.13.0, so it is filtered narrowly (message, category, and module) rather than worked around. Made by [Open SWE](https://openswe.vercel.app/agents/f42590ef-0fe4-0b9f-6880-1c48b28446d5) --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A thread you just started now shows up in the
/threadsswitcher as soon as you open it, instead of appearing a moment later.The thread picker paints instantly from an in-memory cache of recent threads and only re-queries the session database afterwards. That cache was populated exactly once, by a startup worker, so any thread created during the session was absent from the first paint: the new row only appeared after the modal's list query, its configured-agent filesystem scan, and a full teardown/rebuild of every row widget had completed. On a large
sessions.dbthe list query alone measured ~0.2 s warm (~0.8 s on the first call of a process, which also builds the covering index), which is exactly the window where the user sees a list that is missing the thread they just used.Rather than trying to make that refresh path faster, this keeps the cache honest:
_cleanup_agent_task— the turn-end hook that already refreshes the git branch — now schedules the same prewarm the app runs at startup, in an exclusive worker group so overlapping turns coalesce. Turn end is the right moment because the turn's checkpoints have just been written and the app is otherwise idle, and it is cheap to repeat: the per-thread message-count and initial-prompt caches are keyed on checkpoint freshness, so only the thread that actually changed is read back from disk. With the cache current, the picker's first paint already contains the new thread (with its updated timestamp and message count), and the follow-up query finds nothing to change, so the row rebuild is skipped entirely.Deletion already pruned this cache in place, so this fills in the other half of keeping it in sync with in-session activity. Scheduling is skipped while the app is exiting or not running, mirroring the existing skill-rediscovery guard.
Made by Open SWE