feat(agents): persistent Lakebase thread store - #576
Draft
MarioCadenas wants to merge 7 commits into
Draft
Conversation
Persistent ThreadStore backed by Lakebase (Postgres) over raw parameterized pg SQL from createLakebasePool(). Two user_id-scoped tables (agent_threads, agent_messages, FK ON DELETE CASCADE), self-bootstrapping CREATE TABLE IF NOT EXISTS via init(). Every query filters WHERE user_id = the caller's id (the isolation boundary); tool_calls stored as jsonb preserving thoughtSignature verbatim; Dates revived on read. Owns its pool by default, leaves an injected pool alone on close(). Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Add optional init?()/close?() to the ThreadStore interface (in-memory store untouched). The agents plugin awaits threadStore.init?.() first in setup() for fail-fast connectivity and threadStore.close?.() in shutdown() to release an owned pool. Field typed as ThreadStore so the optional hooks resolve. Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
…store Declare an optional postgres resource on the agents manifest (same field/env shape as the database plugin, CAN_CONNECT_AND_CREATE) so a deploy can bind Lakebase for persistent threads; unbound apps are unaffected and fall back to in-memory. Export LakebaseThreadStore (and its options type) via the agents barrel and the beta surface, beside the other agents exports. Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Add a Thread persistence section to the agents plugin docs (in-memory default vs LakebaseThreadStore, self-bootstrap, per-user isolation, deploy binding, custom-store contract). Wire an opt-in dev-playground example that uses LakebaseThreadStore when LAKEBASE_ENDPOINT is set (same signal the lakebase plugin uses) and falls back to in-memory otherwise. Regenerated API docs and template appkit.plugins.json. Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
MarioCadenas
added a commit
that referenced
this pull request
Sep 8, 2026
Left-column history panel wired to the existing thread endpoints: GET /api/agents/threads (list), GET /api/agents/threads/:id (load + resume), DELETE /api/agents/threads/:id. New conversation button resets to a fresh thread; the list refreshes after each send and highlights the active thread. Durable when the agent uses LakebaseThreadStore. Demonstrates the persistence from #576 end-to-end in the UI. Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
MarioCadenas
force-pushed
the
feat/agent-persistent-threads
branch
from
September 8, 2026 14:20
b01bf00 to
5c2e834
Compare
Contributor
📦 Bundle size reportCompared against
|
| dist | raw | gzip |
|---|---|---|
| JS (runtime) | 1.1 MB (+12 KB) | 410 KB (+3.5 KB) |
| Type declarations | 422 KB (+4.6 KB) | 152 KB (+1.8 KB) |
| Source maps | 2.3 MB (+20 KB) | 768 KB (+6.4 KB) |
| Other | 11 KB | 3.7 KB |
| Total | 3.8 MB (+36 KB) | 1.3 MB (+12 KB) |
Per-entry composition (own code — deps external (as shipped))
| Entry | Initial (gz) | Lazy (gz) | Total (gz) | node_modules (min) | Own code (min) |
|---|---|---|---|---|---|
. |
95 KB | 2.5 KB | 98 KB | external | 313 KB |
./beta |
92 KB (+2.0 KB) | 457 B | 93 KB (+2.0 KB) | external | 281 KB (+7.3 KB) |
./testing |
17 KB | 0 B | 17 KB | external | 52 KB |
./tsdown |
520 B | 0 B | 520 B | external | 813 B |
./type-generator |
23 KB | 0 B | 23 KB | external | 65 KB |
Chunks:
| Entry | Chunk | Load | Size (gz) |
|---|---|---|---|
. |
index.js |
initial | 91 KB |
. |
utils.js |
initial | 4.0 KB |
. |
remote-tunnel-manager.js |
lazy | 2.5 KB |
./beta |
beta.js |
initial | 76 KB |
./beta |
stream-manager.js |
initial | 5.8 KB |
./beta |
wide-event-emitter.js |
initial | 3.2 KB |
./beta |
databricks.js |
initial | 3.2 KB |
./beta |
configuration.js |
initial | 2.3 KB |
./beta |
service-context.js |
initial | 1.3 KB |
./beta |
client.js |
initial | 434 B |
./beta |
client-options.js |
initial | 220 B |
./beta |
supervisor-api.js |
lazy | 192 B |
./beta |
databricks.js |
lazy | 142 B |
./beta |
index.js |
lazy | 123 B |
./testing |
index.js |
initial | 17 KB |
./tsdown |
index.js |
initial | 520 B |
./type-generator |
index.js |
initial | 23 KB |
@databricks/appkit-ui
npm tarball (packed): 350 KB (-4 B) — gzipped download (dist + bin; excludes release-only docs/NOTICE).
| dist | raw | gzip |
|---|---|---|
| JS (runtime) | 395 KB | 132 KB |
| Type declarations | 229 KB | 84 KB (-1 B) |
| Source maps | 766 KB | 253 KB |
| CSS | 16 KB | 3.2 KB |
| Total | 1.4 MB | 473 KB (-1 B) |
Per-entry composition (consumer bundle — deps bundled, peerDeps external)
| Entry | Initial (gz) | Lazy (gz) | Total (gz) | node_modules (min) | Own code (min) |
|---|---|---|---|---|---|
./js |
5.3 KB | 49 KB | 55 KB | 208 KB | 14 KB |
./js/beta |
20 B | 0 B | 20 B | 0 B | 0 B |
./react |
432 KB | 49 KB | 481 KB | 1.3 MB | 177 KB |
./react/beta |
1.0 KB | 0 B | 1.0 KB | 0 B | 1.9 KB |
Chunks:
| Entry | Chunk | Load | Size (gz) |
|---|---|---|---|
./js |
index.js |
initial | 5.2 KB |
./js |
chunk |
initial | 120 B |
./js |
apache-arrow |
lazy | 49 KB |
./js/beta |
beta.js |
initial | 20 B |
./react |
index.js |
initial | 430 KB |
./react |
tslib |
initial | 2.1 KB |
./react |
apache-arrow |
lazy | 49 KB |
./react/beta |
beta.js |
initial | 1.0 KB |
Contributor
🤖 AppKit PR bot🔬 Run evalsStart an eval for this PR from the evals-monitor app: Go to Evals Monitor → 📦 Try this PR's app templateScaffolds a new app from this PR's SDK build. Run it in any folder (requires the GitHub CLI — gh run download 34368559002 -R databricks/appkit -n appkit-template-0.73.0-pr.093ac15-feat-agent-persistent-threads-576 -D appkit-pr-576 \
&& unzip -o "appkit-pr-576/appkit-template-0.73.0-pr.093ac15-feat-agent-persistent-threads-576.zip" -d "appkit-pr-576" \
&& databricks apps init --template "appkit-pr-576"The template pins |
Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Adds the backend for a history sidebar: ThreadSummary type (shared), optional ThreadStore.listSummaries + rename (both impls; optional so custom stores keep compiling), a nullable title column on agent_threads (derived default via COALESCE(title, first user message), renamable), GET /threads now returns summaries (fallback derives from list() for stores lacking listSummaries), and a new PATCH /threads/:id rename route. Rename does not bump updated_at. Extends the InMemory + Lakebase test suites. Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Exposes ThreadSummary from @databricks/appkit/beta for server-side custom ThreadStore implementers, and regenerates the appkit API reference for the listSummaries/rename/title additions. Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
MarioCadenas
force-pushed
the
feat/agent-persistent-threads
branch
from
September 9, 2026 15:11
29444fc to
27c0f8d
Compare
MarioCadenas
added this pull request to stack #579
September 9, 2026 15:14
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.
What
Persistent agent threads for AppKit — a durable
ThreadStorebacked by Databricks Lakebase (Postgres), plus the thread summary/rename API the history UI builds on.Base of a 2-PR stack. The chat-history UI (hooks + components + playground) is stacked on top of this in a follow-up PR (see the link below). This PR is backend-only:
packages/appkit+packages/shared.How it works
LakebaseThreadStore— raw parameterizedpgSQL overcreateLakebasePool()(not the DatabasePlugin layer). Twouser_id-scoped tables (agent_threads+agent_messages, FKON DELETE CASCADE), self-bootstrapped withCREATE TABLE IF NOT EXISTSoninit(). Every query filtersWHERE user_id = $— the isolation boundary.tool_callsstored asjsonb, preservingthoughtSignature. Owns its pool by default; leaves an injected pool alone.init?()/close?()on theThreadStoreinterface, wired into the agents plugin's setup (fail-fast connectivity) and shutdown.GET /threadsreturns cheap summaries ({id, title, updatedAt, messageCount}, no message bodies; aLATERALjoin derives the title from the first user message viaCOALESCE);PATCH /threads/:idrenames (nullabletitlecolumn, derived default, rename wins, doesn't bumpupdated_at).ThreadStoregains optionallistSummaries/rename(custom stores keep compiling; the plugin falls back tolist()).postgresresource on the agents manifest;LakebaseThreadStoreandThreadSummaryexported from@databricks/appkit/beta.Verification
check:fix/build/docs:buildpass.ALTER TABLE ADD COLUMN titleon a pre-existing table, persistence across a full process restart, per-user isolation (cross-user read/rename → 404),thoughtSignaturejsonb round-trip, summary projection, rename (+ empty-title 400), delete/cascade.NOT verified
Draft — not for merge until the deployed-SP dogfood is done.