Skip to content

@anvia/memory-postgres ​

@anvia/memory-postgres stores Anvia agent memory in PostgreSQL. Use it when several application instances need the same durable conversation history or when memory belongs in an existing operational Postgres environment.

Install ​

sh
pnpm add @anvia/memory-postgres @anvia/core

The package includes pg and should use a version compatible with its declared @anvia/core dependency range.

Connect a store ​

ts
import { Agent } from '@anvia/core/agent'
import { PostgresMemoryClient } from '@anvia/memory-postgres'

const memoryClient = new PostgresMemoryClient({
  connectionString: process.env.DATABASE_URL!,
})
const memory = memoryClient.memoryStore()
await memory.ensure()

const agent = new Agent({
  id: 'support',
  model: model,
  memory: { store: memory, savePolicy: 'turn' },
})

You may pass a compatible client or pool instead of connectionString. Pool-like clients let the adapter acquire and release a connection for each transaction.

What it provides ​

  • Ordered, transactional message persistence.
  • Advisory transaction locking per memory scope by default.
  • Failed-run storage and message validation by default.
  • Read-only inspection and memory compaction support.
  • Custom table prefixes or explicit table names.
  • Exportable schema SQL for application-owned migrations.

Own the schema in production ​

Calling store.ensure() creates the pgcrypto extension, sessions, messages, errors, and the unique message-position index.

For controlled deployments, generate the same SQL and apply it through your migration system:

ts
import { createPostgresMemorySchemaSql } from '@anvia/memory-postgres'

const sql = createPostgresMemorySchemaSql({
  tablePrefix: 'app_',
})

Then connect without DDL at application startup:

ts
const memoryClient = new PostgresMemoryClient({
  connectionString: process.env.DATABASE_URL!,
})
const memory = memoryClient.memoryStore({
  tablePrefix: 'app_',
})
await memory.validate()

Keep the schema options identical in the migration and runtime configuration. Explicit tableNames override the prefix for individual tables.

Scope and concurrency ​

The default scope combines sessionId and userId. Add tenant metadata when needed:

ts
const memoryClient = new PostgresMemoryClient({
  connectionString: process.env.DATABASE_URL!,
})
const memory = memoryClient.memoryStore({
  scopeKey: {
    metadataKeys: ['tenantId'],
  },
  lock: 'advisory',
})

Advisory locking serializes position assignment for concurrent appends to the same scope. Set lock: 'none' only when another layer guarantees that those writes cannot race. Scope isolation does not replace database authorization or tenant access checks.

Production patterns ​

  • Reuse an application-managed pool when connection ownership and shutdown are already centralized.
  • Apply DDL through migrations and call store.validate() in runtime processes.
  • Monitor table growth and decide how long conversation and error histories should remain.
  • Keep validateMessages: true unless validated messages are guaranteed upstream.
  • Review custom names as identifiers, not arbitrary SQL fragments; the adapter validates and quotes them.

See Memory sessions for context design and Custom stores for the core contract.

Reference ​

Built for Anvia.