Imported from gabrielmoreira/agent-skills-mirror (
mirrors/repos/elizaOS@eliza/plugins/plugin-sql/AGENTS.md). Install upstream withnpx skills add gabrielmoreira/agent-skills-mirror --skill plugin-sql. Copyright stays with the author.
@elizaos/plugin-sql
SQL database adapter plugin for elizaOS — provides persistent storage via PostgreSQL or embedded PGlite (WASM), with Drizzle ORM, automatic schema migrations, and optional Row Level Security.
Purpose / role
This plugin registers a DatabaseAdapter with the elizaOS agent runtime so that all core runtime persistence (memories, entities, rooms, tasks, cache, logs, relationships, etc.) works against a real SQL backend. It is the default database plugin; elizaOS agents load it automatically if no other adapter is already registered. On Node/Bun it selects PostgreSQL when POSTGRES_URL is set, otherwise falls back to embedded PGlite. The plugin runs in Node; browser clients use their host transport.
Plugin surface
The exported plugin object (src/index.ts) registers:
| Kind | Name | Description |
|---|---|---|
| Service | AdvancedMemoryStorageService (serviceType = "memoryStorage") |
Implements MemoryStorageProvider; persists and completely retrieves long-term memories through the runtime memory API |
| Service | SqlPrincipalService (serviceType = "principal") |
Canonical generation-fenced identity authority for claims, person-link attestations, reversible redirects, merge/split journals, and owner-binding reads |
| Service | SqlMembershipService (serviceType = "membership") |
Canonical publisher-generation and cursor-fenced connector-room authority with atomic complete snapshots, bounded freshness, exact idempotency, and fail-closed authorization |
| Schema | schema (all tables) |
Passed as plugin.schema so DatabaseMigrationService can auto-migrate at startup |
No actions, providers, evaluators, or event handlers are registered by this plugin. Identity mutation is never model-callable.
Layout
plugins/plugin-sql/
package.json Single npm manifest; scripts, deps
build.ts Node ESM and bundled declarations in dist/
README.md human-facing docs
src/
index.ts Node entry: PostgreSQL + PGlite; createDatabaseAdapter()
base.ts BaseDrizzleAdapter — shared IDatabaseAdapter implementation
types.ts DrizzleDatabase union type; getDb() helper
agent-mapping.ts Utilities for normalizing agent message examples from DB rows
utils.ts Node storage helpers (resolvePgliteDir)
utils/
string-to-uuid.ts String-to-UUID conversion utility
connector-credential-store.ts ConnectorCredentialStore/Vault interfaces + factory
routes/
identity-person-link.ts Authenticated operator attestation + verification routes
migration-service.ts DatabaseMigrationService — discovers plugin schemas, runs migrations, re-applies RLS
migrations.ts One-off migrations (e.g., entity RLS backfill)
rls.ts Row Level Security helpers (install/apply/uninstall)
pg/
adapter.ts PgDatabaseAdapter (wraps BaseDrizzleAdapter for Postgres)
manager.ts PostgresConnectionManager — pg Pool singleton, withEntityContext
sslmode.ts SSL mode resolver
pglite/
adapter.ts PgliteDatabaseAdapter (wraps BaseDrizzleAdapter for PGlite)
manager.ts PGliteClientManager — PGlite singleton, lifecycle
errors.ts PGlite-specific error types
schema/
index.ts Re-exports all table definitions
agent.ts / room.ts / memory.ts / entity.ts / ... One file per table
services/
advanced-memory-storage.ts AdvancedMemoryStorageService implementation
sql-principal.ts Canonical identity authority implementation
sql-membership.ts Canonical connector-room membership authority implementation
stores/
agent.store.ts / memory.store.ts / room.store.ts / ... Query logic split by domain
runtime-migrator/
index.ts RuntimeMigrator entry
runtime-migrator.ts Diff-based migration engine
schema-transformer.ts Drizzle schema → SQL diff
extension-manager.ts PGlite extension loading
drizzle/ Drizzle ORM re-exports
Identity HTTP ingress belongs to packages/agent/src/api/identity-person-link-routes.ts; this package registers storage and authority services only.
Commands
All scripts run from the plugin root via bun run --cwd plugins/plugin-sql <script>.
bun run --cwd plugins/plugin-sql build # Node ESM + bundled declarations
bun run --cwd plugins/plugin-sql dev # Watch build
bun run --cwd plugins/plugin-sql test # vitest run
bun run --cwd plugins/plugin-sql typecheck # tsc --noEmit
bun run --cwd plugins/plugin-sql lint # biome lint
bun run --cwd plugins/plugin-sql lint:check # biome lint (no write)
bun run --cwd plugins/plugin-sql format # biome format (write)
bun run --cwd plugins/plugin-sql format:check # biome format (check only)
bun run --cwd plugins/plugin-sql clean # Remove generated dist and cache
bun run --cwd plugins/plugin-sql test:e2e # live smoke test (needs running stack)
Config / env vars
| Variable | Required | Default | Effect |
|---|---|---|---|
POSTGRES_URL |
No | — | PostgreSQL connection string. When absent, PGlite is used. |
PGLITE_DATA_DIR |
No | .eliza/.elizadb |
Directory (or idb:// URL) for PGlite data storage. |
ENABLE_DATA_ISOLATION |
No | false |
When true, enables PostgreSQL Row Level Security per-server isolation. |
ELIZA_SERVER_ID |
Conditional | — | Required when ENABLE_DATA_ISOLATION=true; becomes the RLS server UUID. |
ELIZA_ALLOW_DESTRUCTIVE_MIGRATIONS |
No | false |
Allow column drops and other destructive schema changes at startup. |
ELIZA_APPLY_MESSAGE_SEARCH_OBJECTS |
No | auto | Controls automatic install of the message_search_document generated column and message-search GIN indexes. Production Postgres adapters skip this DDL by default; set true after scheduling the generated-column/index migration. |
ELIZA_PGLITE_DISABLE_EXTENSIONS |
No | false |
Disables PGlite extension loading when set. |
ELIZA_IOS_LOCAL_BACKEND |
No | — | Overrides the local backend URL for iOS platform targets. |
ELIZA_ANDROID_LOCAL_BACKEND |
No | — | Overrides the local backend URL for Android platform targets. |
ELIZA_BENCH_DISABLE_DOTENV |
No | false |
Prevents benchmark processes from loading ancestor .env files while resolving PGlite storage. Subscription chat-only benchmark mode enforces the same behavior. |
NODE_ENV |
No | development |
production disables verbose migration logging and tightens safety checks. |
Settings are read via runtime.getSetting(key) inside plugin.init.
How to extend
Add a new schema table
- Create
src/schema/<tableName>.tsexporting a DrizzlepgTable(...). - Add the export to
src/schema/index.ts. - The plugin's
schemaexport is picked up byDatabaseMigrationServiceat startup — no manualdrizzle-kit generatestep needed in normal development.
Add a new store (domain queries)
- Create
src/stores/<domain>.store.tsimplementing your query functions againstDrizzleDatabase. - Export from
src/stores/index.ts. - Call from
BaseDrizzleAdapterinsrc/base.tsor from the relevantPgDatabaseAdapter/PgliteDatabaseAdapter.
Add a new service
- Implement
Servicefrom@elizaos/coreinsrc/services/<name>.ts. - Add it to the
servicesarray in thepluginobject insrc/index.ts.
Conventions / gotchas
- Global singleton managers. Both
PostgresConnectionManagerandPGliteClientManagerare stored underSymbol.for("elizaos.plugin-sql.global-singletons")onglobalThis. This prevents multiple pools when the module is imported from multiple paths in the same process. Do not create manager instances directly — always go throughcreateDatabaseAdapter(). - Skips init if adapter already registered. If another plugin already called
registerDatabaseAdapterbefore this plugin'sinitruns, the plugin does nothing. This is intentional; use it to swap in a custom adapter by loading it first. - One Node entry. The root export uses
dist/index.jsanddist/index.d.ts. Schema and Drizzle have explicit entries; generated code stays in package-rootdist/. Shared bundle chunks preserve schema object identity across the root and schema entries. - Schema subpath export. Consumers that only need schema types (e.g., for drizzle queries outside the plugin) can import from
@elizaos/plugin-sql/schemawithout pulling in adapters. - Drizzle subpath export. Common Drizzle query helpers (
eq,sql,and, etc.) are re-exported from@elizaos/plugin-sqland@elizaos/plugin-sql/drizzleto avoid direct drizzle-orm version coupling in consumer code. - Vector identity includes the representation.
ensureEmbeddingSpace(spaceId)activates a named encoder/pooling/normalization representation for one agent and returns source memories needing re-embedding. Reads exclude unversioned and other-model vectors even when dimensions match. Memory rows survive. Named-vector writes replace a nonce; legacy writers cannot overwrite named vectors or downgrade them to unversioned rows.updateMemoryEmbeddingretains its atomic source comparison. Dimension changes still useensureEmbeddingDimensionand scoped stale-width reconciliation. - RLS is PostgreSQL-only. PGlite does not support Row Level Security. The
ENABLE_DATA_ISOLATIONpath is silently skipped on PGlite. - Document entitlements are query-time authority. Document list, lookup, and fragment queries authorize the parent before constructing results. Current room IDs satisfy the parent's single room entitlement; validated
directGrantEntityIdsprovide read-only access outside the room, except foragent-privatedocuments. Never materialize per-member document grants or move these predicates after pagination/ranking. - Tests live under
src/__tests__/and run via vitest configured insrc/vitest.config.ts.
Verification
Follow the repository-wide verification and evidence standard in the root CLAUDE.md. Run the package's relevant build, typecheck, lint, and test commands, then exercise the real integration boundary changed by the work. Inspect the produced domain artifacts and failure behavior; do not substitute mocked success for the system under test.
The base plugin has no Electric sync, cloud write forwarding, or Neon serverless driver. Use ordinary PostgreSQL connections for hosted Postgres. PGlite local live queries are available only through explicitly supplied extensions.