From 3865ad7af83287e842bfd3dcdcc0f5b152df2122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Emre=20Kabakc=C4=B1?= Date: Thu, 28 May 2026 15:43:12 +0100 Subject: [PATCH] chore: drop stale PGlite references and vestigial workaround comments Post-#965 (embedded-postgres replacing PGlite), several comment blocks still referenced PGlite-era constraints that no longer apply. Cleans up landing docs, code comments, test docstrings, and removes the historical justifications for the auth/index.tsx ctx.internalAdapter deadlock-avoidance pattern and the turn-liveness.ts literal-queue-name workaround. The code itself is unchanged; only stale narrative is gone. --- db/migrations/00000000000000_baseline.sql | 5 ++-- packages/cli/scripts/build.cjs | 2 +- packages/cli/src/__tests__/dev.test.ts | 2 +- .../docs/getting-started/comparison.md | 4 +-- .../content/docs/getting-started/index.mdx | 4 +-- .../src/content/docs/guides/architecture.mdx | 2 +- .../content/docs/guides/troubleshooting.md | 10 +++---- .../landing/src/content/docs/reference/cli.md | 6 ++--- packages/owletto | 2 +- .../server/scripts/build-server-bundle.mjs | 2 +- .../setup/embedded-postgres-backend.ts | 2 +- .../server/src/__tests__/setup/test-db.ts | 4 +-- packages/server/src/auth/index.tsx | 27 +++---------------- .../gateway/__tests__/turn-liveness.test.ts | 4 +-- .../gateway/orchestration/turn-liveness.ts | 7 +---- packages/server/src/lobu/agent-routes.ts | 15 +++++------ packages/server/vitest.config.ts | 2 +- 17 files changed, 37 insertions(+), 63 deletions(-) diff --git a/db/migrations/00000000000000_baseline.sql b/db/migrations/00000000000000_baseline.sql index b8d6da6fa..0090ca46e 100644 --- a/db/migrations/00000000000000_baseline.sql +++ b/db/migrations/00000000000000_baseline.sql @@ -223,9 +223,8 @@ -- DROP TABLE public.organization_lobu_links_d20260519; -- -- ... etc. -- --- Fresh DBs (local dev, PGlite, CI): no surgery, no backups; dbmate --- applies this file from scratch. Wipe local PGlite dirs --- (`rm -rf /data`) to take advantage of the squash. +-- Fresh DBs (local dev, CI): no surgery, no backups; dbmate +-- applies this file from scratch. -- ============================================================================= -- diff --git a/packages/cli/scripts/build.cjs b/packages/cli/scripts/build.cjs index adf8df087..3ad318c13 100644 --- a/packages/cli/scripts/build.cjs +++ b/packages/cli/scripts/build.cjs @@ -45,7 +45,7 @@ if (fs.existsSync(catalogManifestSrc) && fs.existsSync("dist/connectors")) { fs.cpSync(catalogManifestSrc, "dist/connectors/.catalog-manifest.json"); } -// Copy database migrations for the bundled PGlite local server. +// Copy database migrations for the bundled embedded-Postgres local server. copyDirIfExists("../../db/migrations", "dist/db/migrations"); // Copy the built owletto web UI (admin/console SPA) next to the server bundle diff --git a/packages/cli/src/__tests__/dev.test.ts b/packages/cli/src/__tests__/dev.test.ts index 5fad87063..24e969619 100644 --- a/packages/cli/src/__tests__/dev.test.ts +++ b/packages/cli/src/__tests__/dev.test.ts @@ -242,7 +242,7 @@ describe("lobu run backend bundle resolution", () => { ).toBe(false); }); - test("no effective URL means no refusal (PGlite path)", () => { + test("no effective URL means no refusal (embedded-Postgres path)", () => { expect( shouldRefuseSharedDatabaseUrl({ effectiveDatabaseUrl: undefined, diff --git a/packages/landing/src/content/docs/getting-started/comparison.md b/packages/landing/src/content/docs/getting-started/comparison.md index f28bc05bc..7f9c12d4b 100644 --- a/packages/landing/src/content/docs/getting-started/comparison.md +++ b/packages/landing/src/content/docs/getting-started/comparison.md @@ -15,7 +15,7 @@ This page compares Lobu against other ways to run agents for multiple users. | **Multi-tenant** | Per-user/channel isolation | Single user | Per-thread sandbox | Per-conversation | | **Platforms** | Slack, Telegram, WhatsApp, Discord, Teams, Google Chat, REST API | CLI and API | API endpoints (MCP, A2A, Agent Protocol) | API | | **Embeddable** | Mount inside Next.js, Express, Hono, Fastify | No | No | No | -| **Self-hosted** | Single Node process (bundled PGlite, or BYO Postgres) | Single process | LangSmith hosted (self-host option) | Cloud only | +| **Self-hosted** | Single Node process (embedded Postgres, or BYO Postgres) | Single process | LangSmith hosted (self-host option) | Cloud only | | **Model support** | Any provider via config | Any provider | Any LangChain-compatible provider | Anthropic only | | **Runtime** | OpenClaw | OpenClaw | LangGraph | Claude | | **Network isolation** | Gateway-mediated egress, domain filtering | Host network | Sandbox-level | Platform-managed | @@ -129,7 +129,7 @@ OpenClaw (~800k LOC) was designed as a **single-tenant, single-user system**. Pr | Secret handling | Gateway proxy injects credentials | Direct env vars | | Egress control | Domain allowlists via HTTP proxy | Host network | | Worker lifecycle | Persistent subprocess per channel | Always running | -| Deployment | Single Node process (bundled PGlite or BYO Postgres) | Single process | +| Deployment | Single Node process (embedded Postgres or BYO Postgres) | Single process | Inside each Lobu worker, the full OpenClaw runtime runs untouched. Lobu rewrites only the gateway layer (~40k LOC) to be multi-tenant. diff --git a/packages/landing/src/content/docs/getting-started/index.mdx b/packages/landing/src/content/docs/getting-started/index.mdx index ebd9c615f..7c9355532 100644 --- a/packages/landing/src/content/docs/getting-started/index.mdx +++ b/packages/landing/src/content/docs/getting-started/index.mdx @@ -7,14 +7,14 @@ Lobu is the open-source backend for multi-user AI agents. It gives every user or ## Quick start -Lobu boots as a single Node process. By default it uses local PGlite with pgvector enabled, so you can start without Docker or a separate Postgres. Set `DATABASE_URL` only when you want to use an external Postgres. +Lobu boots as a single Node process. By default it uses an embedded Postgres (PG18 + pgvector), so you can start without Docker or a separate Postgres. Set `DATABASE_URL` only when you want to use an external Postgres. ```bash # 1. Scaffold npx @lobu/cli@latest init my-agent cd my-agent -# 2. Boot locally (uses PGlite by default) +# 2. Boot locally (uses embedded Postgres by default) npx @lobu/cli@latest run # 3. Chat with your agent (in another terminal) diff --git a/packages/landing/src/content/docs/guides/architecture.mdx b/packages/landing/src/content/docs/guides/architecture.mdx index 1dc43069e..da90c47c4 100644 --- a/packages/landing/src/content/docs/guides/architecture.mdx +++ b/packages/landing/src/content/docs/guides/architecture.mdx @@ -21,7 +21,7 @@ Lobu is embedded-only: the gateway, agent workers, the embeddings model, and the - **Gateway**: orchestration, OAuth, secrets, domain policy, routing — all in the host Node process. - **Worker**: model execution, tools, workspace state — a sandboxed subprocess that never sees real credentials. -- **Postgres (with pgvector)**: the only external dependency Lobu ever needs. Holds the run queue, agent settings, grants, secrets, chat history, and MCP proxy sessions. Scaffolded projects (`lobu init` / `lobu run`) default to an **in-process PGlite** database, so `DATABASE_URL` is optional there; only the monorepo `make dev` requires an external Postgres. There is no Redis anywhere. +- **Postgres (with pgvector)**: the only external dependency Lobu ever needs. Holds the run queue, agent settings, grants, secrets, chat history, and MCP proxy sessions. Scaffolded projects (`lobu init` / `lobu run`) default to an **embedded Postgres** (PG18 + pgvector, runs in-process), so `DATABASE_URL` is optional there; only the monorepo `make dev` requires an external Postgres. There is no Redis anywhere. ## Persistent Memory diff --git a/packages/landing/src/content/docs/guides/troubleshooting.md b/packages/landing/src/content/docs/guides/troubleshooting.md index 29e935a26..7467a2217 100644 --- a/packages/landing/src/content/docs/guides/troubleshooting.md +++ b/packages/landing/src/content/docs/guides/troubleshooting.md @@ -3,7 +3,7 @@ title: Troubleshooting description: Common issues and how to fix them. --- -Lobu boots as a single Node process: `lobu run`. A scaffolded project defaults to an **in-process PGlite** database (entry `start-local.bundle.mjs`), so `DATABASE_URL` is optional — set it only when you want an external Postgres (with pgvector), in which case the entry is `server.bundle.mjs`. The monorepo `make dev` always requires `DATABASE_URL`. Worker subprocesses are spawned by the gateway's `EmbeddedDeploymentManager`. There is no Redis. +Lobu boots as a single Node process: `lobu run`. A scaffolded project defaults to an **embedded Postgres** (PG18 + pgvector, entry `start-local.bundle.mjs`), so `DATABASE_URL` is optional — set it only when you want an external Postgres, in which case the entry is `server.bundle.mjs`. The monorepo `make dev` always requires `DATABASE_URL`. Worker subprocesses are spawned by the gateway's `EmbeddedDeploymentManager`. There is no Redis. ## Worker won't start @@ -22,7 +22,7 @@ make clean-workers # in the monorepo # Common causes: # - Port 8787 already in use → Change GATEWAY_PORT or PORT in .env # - DATABASE_URL set but not reachable → see "Agent not responding" below -# (with the default PGlite backend there's no DATABASE_URL to misconfigure) +# (with the default embedded-Postgres backend there's no DATABASE_URL to misconfigure) # - Invalid lobu.config.ts → npx @lobu/cli@latest validate ``` @@ -33,7 +33,7 @@ make clean-workers # in the monorepo curl http://localhost:8787/health # If you've configured an external Postgres, check the connection -# (skip this with the default in-process PGlite backend) +# (skip this with the default embedded-Postgres backend) psql "$DATABASE_URL" -c 'select 1' # Clear stale chat history (for stuck conversations). @@ -104,7 +104,7 @@ curl -v http://localhost:8118 ```bash # Check Node process memory (the entry is server.bundle.mjs with an external -# Postgres, or start-local.bundle.mjs with the default PGlite backend) +# Postgres, or start-local.bundle.mjs with the default embedded-Postgres backend) ps -o pid,rss,command -p "$(pgrep -f '(server|start-local)\.bundle\.mjs')" # Workspaces accumulate per agent under ./workspaces/ @@ -140,7 +140,7 @@ npx @lobu/cli@latest memory health ## Postgres not reachable -Only relevant if you've configured an external Postgres via `DATABASE_URL` — the default scaffolded backend is in-process PGlite and has nothing to connect to. +Only relevant if you've configured an external Postgres via `DATABASE_URL` — the default scaffolded backend is an embedded Postgres and has nothing to connect to. ```bash # Verify DATABASE_URL in .env diff --git a/packages/landing/src/content/docs/reference/cli.md b/packages/landing/src/content/docs/reference/cli.md index 1b374892f..487b31bce 100644 --- a/packages/landing/src/content/docs/reference/cli.md +++ b/packages/landing/src/content/docs/reference/cli.md @@ -38,13 +38,13 @@ Generates: - `*.reaction.ts` — watcher reaction scripts, referenced via `defineWatcher({ reaction })` - `AGENTS.md`, `TESTING.md`, `README.md`, `.gitignore` -Interactive prompts guide you through provider, platform, network access policy, gateway port, public URL, and memory configuration. Local runs use bundled PGlite by default; set `DATABASE_URL` when you want to use external Postgres with pgvector. +Interactive prompts guide you through provider, platform, network access policy, gateway port, public URL, and memory configuration. Local runs use an embedded Postgres (PG18 + pgvector) by default; set `DATABASE_URL` when you want to use an external Postgres. --- ### `run` (aliases: `dev`, `start`) -Run the embedded Lobu stack. `lobu.config.ts` is not required. With no `DATABASE_URL`, the command starts bundled local PGlite and stores data under `~/.lobu/data` (override with `LOBU_DATA_DIR`). If `DATABASE_URL` is set in the environment or `.env`, Lobu uses that external Postgres instead. +Run the embedded Lobu stack. `lobu.config.ts` is not required. With no `DATABASE_URL`, the command starts an embedded Postgres (PG18 + pgvector) and stores data under `~/.lobu/data` (override with `LOBU_DATA_DIR`). If `DATABASE_URL` is set in the environment or `.env`, Lobu uses that external Postgres instead. ```bash npx @lobu/cli@latest run @@ -324,6 +324,6 @@ cd my-agent npx @lobu/cli@latest validate npx @lobu/cli@latest apply --org my-org -# 4. Run locally (PGlite by default; external Postgres if DATABASE_URL is set) +# 4. Run locally (embedded Postgres by default; external Postgres if DATABASE_URL is set) npx @lobu/cli@latest run ``` diff --git a/packages/owletto b/packages/owletto index 98c09b929..ab5064524 160000 --- a/packages/owletto +++ b/packages/owletto @@ -1 +1 @@ -Subproject commit 98c09b9297680869061c5b7a691f3cecf5da45bc +Subproject commit ab506452478a0220a9f45a833ff5b8ed62a25648 diff --git a/packages/server/scripts/build-server-bundle.mjs b/packages/server/scripts/build-server-bundle.mjs index c51695cb1..56c617ffd 100644 --- a/packages/server/scripts/build-server-bundle.mjs +++ b/packages/server/scripts/build-server-bundle.mjs @@ -16,7 +16,7 @@ * External: bare specifiers stay external (loaded from node_modules at * runtime). Published `@lobu/cli` declares those runtime dependencies * directly so Node's resolver finds them from the CLI install. Native addons - * (isolated-vm), PGlite native/WASM assets, and packages with + * (isolated-vm), embedded-postgres + pgvector binaries, and packages with * require-in-the-middle hooks (Sentry, OpenTelemetry, pino) MUST stay external * to keep their runtime hooks working. */ diff --git a/packages/server/src/__tests__/setup/embedded-postgres-backend.ts b/packages/server/src/__tests__/setup/embedded-postgres-backend.ts index 5d61f4ec9..32afa761d 100644 --- a/packages/server/src/__tests__/setup/embedded-postgres-backend.ts +++ b/packages/server/src/__tests__/setup/embedded-postgres-backend.ts @@ -6,7 +6,7 @@ * needs no external Postgres, exactly like `lobu run`. Same binary + pgvector * injection as the production embedded path (src/embedded-runtime.ts), so tests * exercise the real engine (prepared statements, multi-conn pool, LISTEN/NOTIFY, - * cube/earthdistance, pgvector) with no PGlite-specific quirks. + * cube/earthdistance, pgvector). */ import { mkdtempSync, rmSync } from 'node:fs'; diff --git a/packages/server/src/__tests__/setup/test-db.ts b/packages/server/src/__tests__/setup/test-db.ts index 917c37a68..8bdac0110 100644 --- a/packages/server/src/__tests__/setup/test-db.ts +++ b/packages/server/src/__tests__/setup/test-db.ts @@ -134,7 +134,7 @@ export async function setupTestDatabase(): Promise { // // Postgres 15+ removed the implicit CREATE privilege on schema `public` from // the `public` role: only the schema OWNER can run DDL there (including - // DROP/CREATE SCHEMA). PGlite and superuser/owner connections can recreate + // DROP/CREATE SCHEMA). Embedded Postgres and superuser/owner connections can recreate // the whole schema; a plain (non-owner) connection user against a real PG15+ // server cannot, and `DROP SCHEMA IF EXISTS public CASCADE` fails with // `must be owner of schema public`. Reset gracefully across all three. @@ -196,7 +196,7 @@ export async function setupTestDatabase(): Promise { /** * Reset schema `public` to a clean slate, working whether the connection user * owns the schema or not. Returns whether the connection user ends up owning - * `public` (true on PGlite / superuser / schema-owner connections). + * `public` (true on embedded Postgres / superuser / schema-owner connections). * * Preferred path (owner / superuser): take ownership of `public` if we can, * then DROP/CREATE the whole schema — the historical behaviour, which also diff --git a/packages/server/src/auth/index.tsx b/packages/server/src/auth/index.tsx index a576d6f77..40fb1c73f 100644 --- a/packages/server/src/auth/index.tsx +++ b/packages/server/src/auth/index.tsx @@ -635,22 +635,10 @@ export async function createAuth(env: Env, request?: Request) { user: { create: { before: async (user, ctx) => { - // Single-user-mode chokepoint. The /api/auth/* URL filter - // in index.ts blocks /api/auth/sign-up/*, but Better Auth - // also creates users on magic-link verify and OAuth - // callbacks — paths the URL guard never sees. This hook - // fires before every user INSERT, so it's the one place - // that closes the fork-via-magic-link / fork-via-OAuth - // backdoor. - // - // The count goes through ctx.internalAdapter so it joins - // the in-flight transaction connection. Calling getDb() - // here would request a second pool connection while - // sign-up's runWithTransaction holds the only one — - // deadlock in PGlite mode (pool max=1). See #947. - // Missing ctx (called outside the BA endpoint pipeline) - // throws via `ctx!` → BA returns FAILED_TO_CREATE_USER, - // which is the fail-closed posture we want. + // Single-user-mode chokepoint. The URL filter in index.ts + // blocks /api/auth/sign-up/*, but Better Auth also creates + // users on magic-link verify and OAuth callbacks; this hook + // fires before every user INSERT. if (env.LOBU_SINGLE_USER === "1") { // Exclude the synthetic install_operator row // (auto-provisioned by ensureInstallOperator) so the @@ -757,13 +745,6 @@ export async function createAuth(env: Env, request?: Request) { // password-hash row at boot. See // docs/install-operator-bootstrap.md. if (account.providerId !== "credential") { - // Route through ctx.internalAdapter so the lookup - // shares the in-flight transaction connection on the - // one path that wraps in runWithTransaction — - // createOAuthUser, called from OAuth callback for new - // users. Avoids the PGlite pool-max=1 deadlock; see - // #947. `/link-social` and existing-user callback - // links aren't transactional today but stay safe. const linkedUser = await ctx!.context.internalAdapter.findUserById(account.userId); const principalKind = ( diff --git a/packages/server/src/gateway/__tests__/turn-liveness.test.ts b/packages/server/src/gateway/__tests__/turn-liveness.test.ts index 095da5b53..6337b8d46 100644 --- a/packages/server/src/gateway/__tests__/turn-liveness.test.ts +++ b/packages/server/src/gateway/__tests__/turn-liveness.test.ts @@ -1,6 +1,6 @@ /** - * Integration tests for turn-liveness (#946) against a real Postgres (PGlite in - * CI). Exercises the durable election marker end to end: arm, discharge, + * Integration tests for turn-liveness (#946) against a real Postgres (embedded + * PG18 in CI). Exercises the durable election marker end to end: arm, discharge, * fast-path failure, the first-writer-wins election (failTurnIfPending), * atomic terminal-reply commit, the deadline sweep + exactly-once, and the * globally-unique (deploymentName:messageId) marker key. diff --git a/packages/server/src/gateway/orchestration/turn-liveness.ts b/packages/server/src/gateway/orchestration/turn-liveness.ts index a0f66ad98..e8f792a39 100644 --- a/packages/server/src/gateway/orchestration/turn-liveness.ts +++ b/packages/server/src/gateway/orchestration/turn-liveness.ts @@ -229,16 +229,11 @@ export async function sweepExpiredTurns( try { const sql = getDb(); const failed = await sql.begin(async (tx: DbClient) => { - // Literal queue_name (no bound params) on the claim to avoid a PGlite - // quirk where parameterized RETURNING statements intermittently report a - // parameter-count mismatch (see runs-queue stale sweep). The emit insert - // below DOES use params but has no RETURNING, so it's unaffected. const rows = await tx.unsafe<{ action_input: unknown }>( // status + run_type match the partial predicate and leading column of // `runs_lobu_claim_idx`, so the inner SELECT is an index range scan // (run_type, queue_name, …, run_at) — not a full scan of `runs` (which - // retains 30 days of completed rows). Literals only (no bound params) - // to dodge the PGlite parameterized-RETURNING quirk. + // retains 30 days of completed rows). `DELETE FROM public.runs WHERE id IN ( SELECT id FROM public.runs diff --git a/packages/server/src/lobu/agent-routes.ts b/packages/server/src/lobu/agent-routes.ts index c5693051f..d36d79f73 100644 --- a/packages/server/src/lobu/agent-routes.ts +++ b/packages/server/src/lobu/agent-routes.ts @@ -1070,10 +1070,10 @@ const STABLE_PLATFORM_LOCK_NAMESPACE = 0x73746263; // "stbc" /** * FNV-1a 32-bit hash of the stable ID. Computed in JS (rather than calling * Postgres's `hashtext()`) so the parameter passed to pg_advisory_xact_lock - * is a plain int — postgres-js's parameter type inference and PGlite's - * extended-query plan cache get tangled when nesting `hashtext(text)::int` - * inside the lock's `(int, int)` signature. The deterministic JS hash gives - * us the same contract: same stable ID → same lock key. + * is a plain int — postgres-js's parameter type inference gets tangled + * when nesting `hashtext(text)::int` inside the lock's `(int, int)` + * signature. The deterministic JS hash gives us the same contract: same + * stable ID → same lock key. */ function hashStableId(stableId: string): number { let hash = 0x811c9dc5; @@ -1112,9 +1112,8 @@ const stablePlatformLockChains: Map> = new Map(); * * Wrapping the whole flow in a single `sql.begin(...)` is not viable: the * tx connection plus parent-pool writes via `connectionStore` / - * `chatManager.addConnection` would self-deadlock both on the pglite-mode - * serialized-client queue and on row-level locks against an uncommitted - * placeholder row from a different connection. + * `chatManager.addConnection` would self-deadlock on row-level locks + * against an uncommitted placeholder row from a different connection. */ async function withStablePlatformLock(stableId: string, fn: () => Promise): Promise { const lockKey = hashStableId(stableId); @@ -1122,7 +1121,7 @@ async function withStablePlatformLock(stableId: string, fn: () => Promise) const work = previous.then(async () => { // Touch the DB-side advisory lock so multi-host writers serialize too. // Inlined via unsafe() because the `(int, int)` overload confuses - // PGlite's extended-query plan cache when other queries on the same + // postgres-js's parameter type inference when other queries on the same // backend cycle through different parameter type oids; both inputs are // validated int32s, no SQL injection surface. Failure here is non-fatal — // the in-process chain still serializes for the embedded case. diff --git a/packages/server/vitest.config.ts b/packages/server/vitest.config.ts index ff8e82b80..e9eed2955 100644 --- a/packages/server/vitest.config.ts +++ b/packages/server/vitest.config.ts @@ -29,7 +29,7 @@ export default defineConfig({ ], testTimeout: 30_000, hookTimeout: 60_000, - // Integration tests share one Postgres/PGlite. Running multiple files in + // Integration tests share one Postgres. Running multiple files in // parallel means one file's `cleanupTestDatabase()` can wipe another file's // fixtures mid-run. Serialize files so fixtures stay stable. pool: "forks",