feat(agents): persistent Lakebase thread store - #576
Draft
MarioCadenas wants to merge 4 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>
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
Adds
LakebaseThreadStore— a persistentThreadStorefor AppKit agents backed by Databricks Lakebase (Postgres). Today agents useInMemoryThreadStore, which loses every thread on restart and grows without bound; this closes that parity gap. The injection seam already existed (agents({ threadStore })), so this is one new store implementation plus lifecycle wiring — no plumbing surgery.Implements the approved spec in
design-docs/agent-persistent-threads-plan.mdexactly.How it works
pgSQL over a pool fromcreateLakebasePool()(OAuth handled inside the pool). Not the DatabasePlugin data layer.user_id-scoped tables —agent_threads+agent_messages(FKON DELETE CASCADE) — self-bootstrapped withCREATE TABLE IF NOT EXISTSoninit()(once-guarded,select 1connectivity probe first).WHERE user_id = $— the isolation boundary; a user can never read or mutate another user's threads.tool_callsstored asjsonb, preservingToolCall.thoughtSignatureverbatim (node-pg parses jsonb on read;JSON.stringify+::jsonbcast on write).Datefields revived on read.init?()/close?()added to theThreadStoreinterface (in-memory store untouched); wired into the agents pluginsetup()(fail-fast) andshutdown().postgresresource on the agents manifest (same field/env shape as the database plugin) so a deploy can bind Lakebase; unbound apps fall back to in-memory.Commits
add persistent Lakebase thread store(schema + 5 methods) + unit testswire ThreadStore init/close lifecycle into agents pluginoptional postgres resource on agents manifest + export storepersistent threads example + docsVerification (agent-run)
user_idscoping on every query,thoughtSignatureround-trip,addMessagethrows on missing thread, pool ownership, once-guarded bootstrap.pnpm -r typecheckclean (no new errors vs baseline).pnpm check:fix(oxlint + oxfmt) clean.pnpm build && pnpm docs:buildsucceed.agents({ threadStore: new LakebaseThreadStore() })gated onLAKEBASE_ENDPOINT).NOT verified
This has not been run against a live Lakebase. The SQL correctness (casts, CTE atomicity, jsonb round-trip, cascade) is only exercised against a mocked pool in unit tests. Live dogfood — deploy against a real Lakebase, create a thread, restart the app, confirm persistence and per-user scoping — is the remaining human step. Draft until then.
Deviations from the plan (flagged, not expanded)
appkit, notagents: commitlint's scope enum is[appkit, appkit-ui, shared, playground, docs, deps, release]—agentsis rejected. PR title keeps(agents)per the task.@databricks/appkit/beta, not the GA rootindex.ts: the agents plugin and theThreadStoretype are beta exports, so the store lives beside them ("beside the existing agents exports").createLakebasePool()reads.