Skip to content

feat(agents): persistent Lakebase thread store - #576

Draft
MarioCadenas wants to merge 7 commits into
mainfrom
feat/agent-persistent-threads
Draft

feat(agents): persistent Lakebase thread store#576
MarioCadenas wants to merge 7 commits into
mainfrom
feat/agent-persistent-threads

Conversation

@MarioCadenas

@MarioCadenas MarioCadenas commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

What

Persistent agent threads for AppKit — a durable ThreadStore backed 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 parameterized pg SQL over createLakebasePool() (not the DatabasePlugin layer). Two user_id-scoped tables (agent_threads + agent_messages, FK ON DELETE CASCADE), self-bootstrapped with CREATE TABLE IF NOT EXISTS on init(). Every query filters WHERE user_id = $ — the isolation boundary. tool_calls stored as jsonb, preserving thoughtSignature. Owns its pool by default; leaves an injected pool alone.
  • Lifecycle — optional init?()/close?() on the ThreadStore interface, wired into the agents plugin's setup (fail-fast connectivity) and shutdown.
  • Summaries + renameGET /threads returns cheap summaries ({id, title, updatedAt, messageCount}, no message bodies; a LATERAL join derives the title from the first user message via COALESCE); PATCH /threads/:id renames (nullable title column, derived default, rename wins, doesn't bump updated_at). ThreadStore gains optional listSummaries/rename (custom stores keep compiling; the plugin falls back to list()).
  • Manifest + exports — optional postgres resource on the agents manifest; LakebaseThreadStore and ThreadSummary exported from @databricks/appkit/beta.

Verification

  • 35 unit tests (store + summaries + rename), typecheck clean vs baseline, check:fix / build / docs:build pass.
  • Live-tested against a real Lakebase (dogfood staging, throwaway branch): schema bootstrap incl. ALTER TABLE ADD COLUMN title on a pre-existing table, persistence across a full process restart, per-user isolation (cross-user read/rename → 404), thoughtSignature jsonb round-trip, summary projection, rename (+ empty-title 400), delete/cascade.

NOT verified

  • Deployed service-principal path — live tests ran as my own identity on a throwaway branch, not as the app SP owning the tables in a deployed app. That deploy dogfood is the remaining human step.

Draft — not for merge until the deployed-SP dogfood is done.

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
MarioCadenas force-pushed the feat/agent-persistent-threads branch from b01bf00 to 5c2e834 Compare September 8, 2026 14:20
@MarioCadenas MarioCadenas changed the title feat(agents): persistent Lakebase thread store feat(agents): persistent Lakebase threads + chat-history UI Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle size report

Compared against bundle-size-baseline.json (main).

@databricks/appkit

npm tarball (packed): 1.1 MB (+10 KB) — gzipped download (dist + bin; excludes release-only docs/NOTICE).

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

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

🤖 AppKit PR bot

🔬 Run evals

Start an eval for this PR from the evals-monitor app: Go to Evals Monitor →

📦 Try this PR's app template

Scaffolds a new app from this PR's SDK build. Run it in any folder (requires the GitHub CLI — gh auth login — and the Databricks 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 @databricks/appkit and @databricks/appkit-ui to tarballs built from this branch, so the scaffolded app runs against this PR's code.

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
MarioCadenas force-pushed the feat/agent-persistent-threads branch from 29444fc to 27c0f8d Compare September 9, 2026 15:11
@MarioCadenas MarioCadenas changed the title feat(agents): persistent Lakebase threads + chat-history UI feat(agents): persistent Lakebase thread store Sep 9, 2026
@MarioCadenas
MarioCadenas added this pull request to stack #579 September 9, 2026 15:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant