qmd/src/pg/index.ts
Haitao Pan 47bd3ded44 feat(pg): add switchable PostgreSQL backend + OpenClaw/Hermes memory bridge
Add an optional PostgreSQL backend (QMD_BACKEND=pg) alongside the
unchanged default SQLite path. PG store uses pgvector (HNSW) for vectors
and pg_jieba + pg_trgm for full-text/Chinese tokenization, with a
namespace column isolating multi-agent memory (openclaw/hermes).

- src/pg/: config, db-pg, schema bootstrap, memory store
- MCP memory_add/memory_search/memory_get tools; qmd pg status + memory CLI
- connection via QMD_PG_URL/DATABASE_URL/qmd config, stunnel TLS 5443
- tests: pg-config (unit) + pg-memory integration (gated on QMD_PG_URL) + pg-compose
- docs/plan: plan, usage, test report, changelog; track docs/**/*.md

SQLite path: zero regression (typecheck clean, 249 passed / 6 skipped).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 19:13:04 +08:00

71 lines
1.9 KiB
TypeScript

/**
* pg/index.ts - Entry point for the PostgreSQL memory bridge.
*
* Wires qmd's LlamaCpp embedder (external OpenAI-compatible API by default,
* optional local models) to the PG-backed memory store and returns a handle
* that disposes both the LLM and the connection pool on close.
*/
import { LlamaCpp } from "../llm.js";
import { resolvePgConfig, isPgBackend, type PgConnectionConfig } from "./config.js";
import { PgMemoryStore } from "./memory-store.js";
export { PgMemoryStore } from "./memory-store.js";
export type {
AddMemoryInput,
AddMemoryResult,
MemorySearchResult,
MemoryRecord,
SearchOptions,
Embedder,
} from "./memory-store.js";
export {
resolveBackend,
isPgBackend,
resolveNamespace,
resolvePgConfig,
redactConnectionString,
DEFAULT_NAMESPACE,
} from "./config.js";
export type { Backend, PgConnectionConfig } from "./config.js";
export interface MemoryBridge {
store: PgMemoryStore;
config: PgConnectionConfig;
dispose(): Promise<void>;
}
/**
* Open the memory bridge from the environment. Throws a clear error if the PG
* backend is not configured.
*/
export async function openMemoryBridge(
env: NodeJS.ProcessEnv = process.env,
): Promise<MemoryBridge> {
if (!isPgBackend(env)) {
throw new Error(
"PostgreSQL backend is not selected. Set QMD_BACKEND=pg and QMD_PG_URL to use memory commands.",
);
}
const config = resolvePgConfig(env);
// A dedicated embedder for the bridge — lazy-loads models on first use and
// auto-unloads after inactivity. Uses the same external embed API as the
// SQLite engine so vectors are comparable across hosts.
const llm = new LlamaCpp({
inactivityTimeoutMs: 5 * 60 * 1000,
disposeModelsOnInactivity: true,
});
const store = await PgMemoryStore.open(config, llm);
return {
store,
config,
dispose: async () => {
await store.close();
await llm.dispose();
},
};
}