# Memind Memory V2 Memory V2 is a facade and policy abstraction layer over existing Memind memory systems. It is not a new memory system. It does not introduce new storage, new execution paths, or a replacement runtime. ## Scope Memory V2 wraps the existing: - `conversation-memory.mjs` - MySQL memory tables such as `h5_conversation_messages` and `h5_user_memory_items` - capability-driven memory hooks such as `memory_store`, `context_memory`, and `chat_recall` It provides: - unified memory API: `resolve`, `write`, `compact` - feature-flag control plane - backend adapter layer, with the legacy conversation memory backend first - future optional plugin points for vector memory, extraction, lifecycle, behavior modeling, and policy routing It guarantees: - zero change to goosed runtime - zero change to PG session behavior - zero change to SSE protocol - zero change to core Portal execution flow - zero blocking on memory backend failure - full backward compatibility with the existing `conversation-memory` system ## Current Implementation The first implementation is intentionally narrow: - `memory-v2.mjs` defines the facade. - `memory-v2-runtime.mjs` wires server runtime backends from explicit environment variables. - `memory-v2-plugin-backends.mjs` defines disabled-by-default plugin slots for future backends. - `memory-v2-pgvector.mjs` defines a disabled-by-default semantic memory adapter skeleton. - `memory-v2-pgvector-schema.mjs` defines explicit, manual pgvector schema setup helpers. - `createLegacyMemoryBackend(...)` adapts the existing conversation memory service. - `resolveMemoryV2Policy(...)` maps feature flags into a stable policy object. - `server.mjs` creates Memory V2 from the existing conversation memory service. - `tkmind-proxy.mjs` reads user memories through `memoryV2.resolve(...)` before passing them into the existing session reconcile path. - `/user-memory/v1/remember-recent` writes through `memoryV2.write(...)`. - `/user-memory/v1/sync` compacts through `memoryV2.compact(...)`. - Runtime startup does not create schema. - No goosed or SSE behavior is changed. The pgvector adapter does not create tables or generate embeddings. It only defines the adapter contract for a future semantic memory backend and requires explicit `enabled: true`, an injected PostgreSQL pool, and either an input embedding or an injected `embedQuery(...)` function. The server runtime uses `createMemoryV2Runtime(...)`. It keeps pgvector dormant unless all of these are true: - `MEMORY_VECTOR_ENABLED=1` - `MEMORY_PGVECTOR_DATABASE_URL` is set - `MEMORY_PGVECTOR_EMBEDDING_MODULE` points to a module exporting `embedQuery(query, input)` or a default function If any requirement is missing, pgvector reports `available=false` and Memory V2 falls back to the legacy conversation-memory backend. The runtime never reads the app's MySQL `DATABASE_URL` for pgvector. When pgvector is available and `MEMORY_BACKEND=pgvector`, only `resolve(...)` uses pgvector. `write(...)` and `compact(...)` continue to use the legacy backend unless a future backend explicitly supports those operations. Local development may call `ensurePgvectorMemorySchema(...)` manually to create an empty table. Production must treat the same schema as a controlled migration with backup, approval, and a separate backfill plan. ## API Contract ### `resolve(input)` Reads memory for the current request. Expected input: ```json { "userId": "h5 user id", "sessionId": "goosed session id", "query": "current user prompt", "limit": 40 } ``` Returns a normalized payload: ```json { "ok": true, "enabled": true, "skipped": false, "source": "legacy-conversation-memory", "profile": null, "semanticMemories": [], "behaviorSummary": null, "activeGoals": [], "memories": [] } ``` ### `write(input)` Writes or queues memory-related evidence. In the legacy adapter this maps to `saveAndAnalyze(...)`. Expected input: ```json { "userId": "h5 user id", "sessionId": "goosed session id", "messages": [] } ``` ### `compact(input)` Compacts or analyzes pending user memory. In the legacy adapter this maps to `analyzeUser(...)`. Expected input: ```json { "userId": "h5 user id", "sessionId": "goosed session id" } ``` ### `getStatus()` Returns the facade policy and backend contract status for read-only observability. Example: ```json { "enabled": true, "backend": "legacy", "selectedBackend": "legacy-conversation-memory", "profileEnabled": true, "eventLogEnabled": true, "vectorEnabled": false, "failOpen": true, "backends": [ { "name": "legacy-conversation-memory", "available": true, "supports": { "resolve": true, "write": true, "compact": true } } ] } ``` ## Backend Adapter Contract Every backend adapter must be optional and fail-open. A backend may implement any subset of the API, but it must not create a new execution path. Required field: - `name`: stable backend name used by `MEMORY_BACKEND` Optional fields: - `isAvailable()`: returns `false` when the backend is configured but not usable - `resolve(input)`: returns profile, semantic memories, behavior summary, active goals, or legacy memories - `write(input)`: records or queues memory evidence - `compact(input)`: performs pending extraction, summarization, or lifecycle maintenance Backend implementations must not: - call goosed directly - mutate PG session state - change SSE payloads - block chat when unavailable - assume they are the only memory backend ## Plugin Registry Memory V2 exposes future plugin slots through read-only backend status. This is a contract registry, not a service integration. The default facade includes unavailable placeholders for: | Backend | Category | Role | | --- | --- | --- | | `pgvector` | semantic | primary vector store | | `qdrant` | semantic | scale-out vector store | | `weaviate` | semantic | knowledge graph fusion | | `mem0` | extraction | automatic memory generation | | `letta` | lifecycle | long/short-term memory OS | | `neo4j` | behavior | behavior graph | | `redis-streams` | behavior | event tracking | | `langgraph` | policy | routing/reasoning policy | These placeholders: - report `available=false` - expose declared capability support in `/api/runtime/status` - never open network connections - never create tables or queues - never become selected while unavailable - allow `MEMORY_BACKEND=` to safely fall back to legacy A real backend adapter replaces its placeholder only when the required client, credentials, schema, and tests are explicitly provided. Backend selection is operation-aware: - `resolve` may use a semantic backend such as pgvector. - `write` must fall back to the first available backend that supports writes, usually legacy conversation memory. - `compact` must fall back to the first available backend that supports compaction, usually legacy conversation memory. This allows semantic retrieval to be canaried without replacing existing memory capture or compaction. ## Backend Contract Gate `memory-v2-backend-contract.mjs` defines the adapter contract gate for current and future backends. Every backend must: - have a stable lowercase kebab-case `name` - implement at least one of `resolve`, `write`, or `compact` - expose `isAvailable()` or default to available - expose an unavailable reason when `isAvailable() === false` - use a known plugin category when `category` is present - keep feature flags in `SCREAMING_SNAKE_CASE` when `flag` is present The default gate checks legacy plus all disabled plugin slots: ```bash npm run check:memory-v2-contracts ``` End-to-end local validation can now be run from one entrypoint: ```bash npm run check:memory-v2-stack -- \ --base-url http://127.0.0.1:18081 \ --expect-backend pgvector \ --expect-selected-backend pgvector ``` This aggregate check runs: - backend contract validation - synthetic Memory V2 health validation - app-level `/api/runtime/status` canary - real cookie-backed `register/login -> agent/start -> agent/runs -> SSE -> remember-recent -> sync` session flow - backend smoke probes for every configured non-legacy backend Unconfigured external backends are reported explicitly as `not_configured` instead of failing the whole check. To see exactly which env vars are still missing for any backend, run: ```bash npm run check:memory-v2-config -- --backend qdrant npm run check:memory-v2-config -- --backend letta --format shell ``` `json` mode reports missing keys and readiness. `shell` mode prints a copy-paste export template for the selected backend. When replacing the pgvector placeholder with the real disabled adapter: ```bash npm run check:memory-v2-contracts -- --include-pgvector-adapter ``` Future adapters for Qdrant, Weaviate, Mem0, Letta, Neo4j, Redis Streams, or LangGraph must pass this contract before being wired into `createMemoryV2Runtime(...)`. ## Current Validation Baseline As of the current Memory V2 rollout branch, local validation has already proven: - `memoryV2.resolve(...)` can select `pgvector` - `memoryV2.write(...)` and `memoryV2.compact(...)` remain legacy-first - `/user-memory/v1/remember-recent` and `/user-memory/v1/sync` still reconcile back into the active session - the live Portal path `agent/start -> agent/runs -> SSE Finish -> session detail` continues to work under Memory V2 That baseline is now captured by `npm run check:memory-v2-session` and included in `npm run check:memory-v2-stack`. ## Backend Scaffold `memory-v2-adapter-scaffold.mjs` renders disabled-by-default backend adapter templates that satisfy the contract gate. Dry-run a Qdrant adapter: ```bash npm run scaffold:memory-v2-backend -- \ --name qdrant \ --category semantic \ --role scale-out-vector-store \ --capability resolve ``` Write a Mem0 adapter scaffold: ```bash npm run scaffold:memory-v2-backend -- \ --name mem0 \ --category extraction \ --role automatic-memory-generation \ --capability write \ --capability compact \ --write ``` The scaffold command refuses to overwrite existing files. After generating a real adapter, run: ```bash npm run check:memory-v2-contracts ``` Then add focused adapter tests before wiring the adapter into `createMemoryV2Runtime(...)`. ## Feature Flags `MEMORY_ENABLED` Global Memory V2 switch. If unset, it follows the existing `USER_CONVERSATION_MEMORY_ENABLED` behavior for backward compatibility. `MEMORY_PROFILE_ENABLED` Controls whether resolved profile payloads may be returned. `MEMORY_EVENT_LOG_ENABLED` Controls memory write/event capture. When disabled, `write(...)` returns a skipped result without touching the backend. `MEMORY_VECTOR_ENABLED` Reserved for future vector backends. It is disabled by default. `MEMORY_BACKEND` Preferred backend name. The current default is `legacy`. `MEMORY_FAIL_OPEN` Defaults to enabled. Backend failures return degraded empty payloads rather than blocking chat. `MEMORY_PGVECTOR_DATABASE_URL` Dedicated PostgreSQL connection string for Memory V2 semantic memory. It must not point at the MySQL business database and is ignored unless `MEMORY_VECTOR_ENABLED=1`. `MEMORY_PGVECTOR_EMBEDDING_MODULE` Optional module path for pgvector runtime retrieval. The module must export `embedQuery(query, input)` or a default async function. Without this module, runtime pgvector retrieval stays unavailable even when a PostgreSQL URL is present. `MEMORY_PGVECTOR_TABLE` Optional table name for pgvector retrieval. Defaults to `memory_embeddings`. `MEMORY_PGVECTOR_POOL_MAX` Optional PostgreSQL pool size for Memory V2 pgvector retrieval. Defaults to `5`. ## Current Integration Points Memory V2 is only allowed to sit above existing memory code. Read path: ```text tkmind-proxy -> memoryV2.resolve -> legacy conversation-memory backend -> existing reconcileAgentSession -> existing buildSessionMemoryEntries -> existing harness remember/bootstrap ``` Write path: ```text /user-memory/v1/remember-recent -> memoryV2.write -> legacy conversation-memory saveAndAnalyze ``` Compact path: ```text /user-memory/v1/sync -> memoryV2.compact -> legacy conversation-memory analyzeUser ``` If `MEMORY_ENABLED=0`, Memory V2 returns skipped/empty results and does not touch the legacy backend. The session reconcile path still runs for non-memory context such as sandbox guidance and time anchors. Runtime observability: ```text /api/runtime/status -> memory: memoryV2.getStatus() ``` This is read-only and exists only to make rollout/debugging visible. Local health gate: ```bash npm run check:memory-v2-contracts npm run check:memory-v2 -- --require-enabled --expect-backend legacy ``` Local app canary against a running Portal service: ```bash npm run canary:memory-v2-app -- \ --base-url http://127.0.0.1:8081 \ --require-enabled \ --expect-backend legacy ``` For pgvector canary, run the app with explicit Memory V2 env and assert both configured and selected backends: ```bash npm run canary:memory-v2-app -- \ --base-url http://127.0.0.1:18081 \ --require-enabled \ --require-target-healthy \ --expect-backend pgvector \ --expect-selected-backend pgvector ``` Production rollout details live in [production-rollout-runbook.md](production-rollout-runbook.md). ## Backend Plugin Slots These are optional future backends. None is required for Memory V2 phase one. Semantic memory: - pgvector as the primary vector option - Qdrant as a scale-out option - Weaviate for knowledge graph fusion Memory extraction: - Mem0 for automatic memory generation Memory lifecycle management: - Letta for long-term and short-term memory operating semantics Behavior and user model: - Neo4j for behavior graph modeling - Redis Streams for event tracking Memory policy engine: - LangGraph for routing and reasoning policy ## Integration Rule Future integration must use existing Memind injection points. The preferred path is: ```text tkmind-proxy / agent run -> Memory V2 resolve -> existing session reconcile -> existing buildSessionMemoryEntries -> existing harness remember/bootstrap ``` Do not add direct goosed changes, PG session changes, SSE protocol changes, or new blocking calls in the reply path. ## Rollout Checklist Phase 1 rollout: 1. Keep `MEMORY_BACKEND=legacy`. 2. Enable `MEMORY_ENABLED=1`. 3. Keep `MEMORY_VECTOR_ENABLED=0`. 4. Verify `/api/runtime/status` reports `memory.enabled=true` and `selectedBackend=legacy-conversation-memory`. 5. Run targeted tests before release. Rollback: ```bash MEMORY_ENABLED=0 ``` Rollback must stop Memory V2 reads/writes while preserving non-memory session reconcile behavior. Future backend rollout: 1. Add the backend adapter behind `MEMORY_BACKEND=`. 2. Replace the matching unavailable placeholder with the real adapter only inside Memory V2 wiring. 3. Verify `getStatus().backends[]` reports capability support. 4. Keep backend disabled or unavailable by default. 5. Run local retrieval/write tests without changing goosed/SSE. 6. Enable for a narrow canary only after legacy fallback remains green. ## pgvector Adapter Skeleton `memory-v2-pgvector.mjs` is the first semantic-memory backend skeleton. Current constraints: - Disabled by default. - Requires an injected PostgreSQL pool with a `query(sql, params)` method. - Requires an explicit embedding via `resolve({ embedding })` or an injected `embedQuery(query, input)` function. - Server runtime only creates the PostgreSQL pool when vector retrieval, a PostgreSQL URL, and an embedding module are all configured. - Performs read-only vector lookup. - Does not create schema automatically. - Does not migrate or copy MySQL memory rows. - Does not call goosed, SSE, or Portal execution APIs. - Falls back through Memory V2 when unavailable. Manual local schema helper: ```js import { ensurePgvectorMemorySchema } from './memory-v2-pgvector-schema.mjs'; await ensurePgvectorMemorySchema(pool, { tableName: 'memory_embeddings', dimensions: 1536, createExtension: true, createVectorIndex: false, }); ``` Dry-run CLI: ```bash node scripts/setup-memory-v2-pgvector-schema.mjs \ --table memory_embeddings \ --dimensions 1536 \ --create-extension ``` Apply CLI: ```bash MEMORY_PGVECTOR_DATABASE_URL='postgresql://...' \ node scripts/setup-memory-v2-pgvector-schema.mjs \ --apply \ --table memory_embeddings \ --dimensions 1536 \ --create-extension ``` The CLI defaults to dry-run and never reads the existing MySQL `DATABASE_URL`. `--apply` requires an explicit PostgreSQL URL environment variable. Expected table shape: ```sql CREATE TABLE memory_embeddings ( id BIGSERIAL PRIMARY KEY, user_id TEXT NOT NULL, content TEXT NOT NULL, embedding VECTOR(1536) NOT NULL, type TEXT NOT NULL DEFAULT 'fact', source_memory_id TEXT, source_session_id TEXT, source_message_id TEXT, metadata JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); ``` Rollout rule: Keep `MEMORY_BACKEND=legacy` until pgvector schema, embedding generation, backfill, and canary verification are designed separately. Production migration considerations: 1. Confirm pgvector is installed on the target PostgreSQL server. 2. Run `CREATE EXTENSION IF NOT EXISTS vector` only through the approved DB migration path. 3. Create the empty `memory_embeddings` table first; do not backfill in the same release. 4. Keep `MEMORY_BACKEND=legacy` after table creation. 5. Build a separate backfill job from `h5_user_memory_items` into pgvector with idempotent checkpoints. 6. Verify row counts, embedding dimensions, and retrieval quality before canarying `MEMORY_BACKEND=pgvector`. 7. Rollback for the app remains `MEMORY_BACKEND=legacy` or `MEMORY_ENABLED=0`; database rollback should not delete populated memory rows without an explicit retention decision. ## pgvector Backfill Skeleton `memory-v2-pgvector-backfill.mjs` defines the future MySQL-to-pgvector backfill contract. Source: ```text h5_user_memory_items WHERE status = 'active' ORDER BY updated_at ASC, id ASC ``` Destination: ```text memory_embeddings.source_memory_id = h5_user_memory_items.id ``` The `source_memory_id` unique index makes backfill idempotent. Re-running the same batch updates content, embedding, type, source pointers, metadata, and `updated_at`. Current constraints: - Defaults to dry-run. - Dry-run reads MySQL only and does not call embedding or PostgreSQL. - Apply requires `pgPool.query(...)`. - Apply requires an explicit `embedMemory(memory) => number[]`. - Backfill is paginated with `{ updatedAt, id }` checkpoints. - Backfill does not change `MEMORY_BACKEND`. - Backfill does not create schema; run schema setup separately first. Dry-run CLI: ```bash MEMORY_BACKFILL_MYSQL_URL='mysql://...' \ node scripts/backfill-memory-v2-pgvector.mjs \ --limit 100 \ --cursor-updated-at 0 \ --cursor-id '' ``` Apply CLI: ```bash MEMORY_BACKFILL_MYSQL_URL='mysql://...' \ MEMORY_PGVECTOR_DATABASE_URL='postgresql://...' \ node scripts/backfill-memory-v2-pgvector.mjs \ --apply \ --limit 100 \ --embedding-module ./scripts/embed-memory-v2-pgvector.mjs ``` The apply command requires the embedding module to export `embedMemory(memory)` or a default function. The CLI intentionally uses `MEMORY_BACKFILL_MYSQL_URL` instead of the app's normal MySQL env so production backfills are explicit and auditable. Production backfill sequence: 1. Run schema migration and keep `MEMORY_BACKEND=legacy`. 2. Run dry-run batches and inspect counts/checkpoints. 3. Run apply batches with a fixed embedding model and recorded dimensions. 4. Store the last checkpoint externally after each successful batch. 5. Compare MySQL active memory count against pgvector distinct `source_memory_id` count. 6. Validate retrieval quality on a canary user. 7. Only then consider `MEMORY_BACKEND=pgvector` canary. ## pgvector Smoke Test `memory-v2-pgvector-smoke.mjs` and `scripts/smoke-memory-v2-pgvector.mjs` provide a local pgvector read/write smoke test. The smoke test: - uses synthetic `memory-v2-smoke-*` rows only - inserts deterministic vectors - verifies the nearest vector is returned first through the Memory V2 pgvector adapter - cleans synthetic rows by default - never runs from app startup - never reads the MySQL `DATABASE_URL` Existing-schema smoke: ```bash MEMORY_PGVECTOR_DATABASE_URL='postgresql://...' \ npm run smoke:memory-v2-pgvector ``` Local setup smoke that also creates an empty schema: ```bash MEMORY_PGVECTOR_DATABASE_URL='postgresql://...' \ npm run smoke:memory-v2-pgvector -- \ --create-schema \ --create-extension \ --table memory_embeddings \ --dimensions 3 ``` Production usage is limited to post-migration verification. Do not use the smoke script as a migration mechanism unless the migration plan explicitly approves `--create-schema`. ## Local Canary Env `docs/memory-v2/local-canary.env.example` contains copyable environment examples for: - legacy no-op facade canary - pgvector local semantic canary - Qdrant and Weaviate read-only canaries - Mem0, Letta, Neo4j, Redis Streams, and LangGraph external canaries For local semantic smoke only, `scripts/embed-memory-v2-local-hash.mjs` exports a deterministic hash embedding function: ```bash MEMORY_PGVECTOR_EMBEDDING_MODULE=./scripts/embed-memory-v2-local-hash.mjs MEMORY_V2_LOCAL_EMBEDDING_DIMENSIONS=3 ``` This module is intentionally simple and deterministic. It is useful for local plumbing checks, but not for production retrieval quality. ## Qdrant Read-Only Adapter `memory-v2-qdrant.mjs` is the first external plugin adapter beyond pgvector. Current constraints: - No Qdrant SDK is imported. - The adapter uses a minimal read-only HTTP client wrapper. - It only calls Qdrant search when `MEMORY_QDRANT_ENABLED=1`, `MEMORY_QDRANT_URL`, and `MEMORY_QDRANT_EMBEDDING_MODULE` are configured. - It never creates collections. - It never writes or upserts points. - `write(...)` and `compact(...)` continue to use legacy memory. Runtime read-only configuration: ```bash MEMORY_ENABLED=1 MEMORY_BACKEND=qdrant MEMORY_QDRANT_ENABLED=1 MEMORY_QDRANT_URL='http://127.0.0.1:6333' MEMORY_QDRANT_COLLECTION='memind_memory' MEMORY_QDRANT_EMBEDDING_MODULE='./scripts/embed-memory-v2-qdrant.mjs' ``` Read-only smoke: ```bash MEMORY_QDRANT_URL='http://127.0.0.1:6333' \ npm run smoke:memory-v2-qdrant -- \ --collection memind_memory \ --vector 1,0,0 \ --limit 1 ``` Expected without embedding module: ```text selectedBackend=legacy-conversation-memory qdrant.available=false qdrant.reason=embedding_module_not_configured ``` Expected with embedding module and reachable Qdrant: ```text selectedBackend=qdrant write_uses_legacy=true compact_uses_legacy=true ``` ## Mem0 Extraction Adapter `memory-v2-mem0.mjs` defines the Memory Extraction backend boundary. Current constraints: - No Mem0 SDK is imported. - Runtime uses a lightweight HTTP client only when `MEMORY_MEM0_ENABLED=1` and `MEMORY_MEM0_API_KEY` are configured. - HTTP paths are configurable with `MEMORY_MEM0_WRITE_PATH` and `MEMORY_MEM0_COMPACT_PATH`. - Without a full configuration, status reports `mem0.available=false` with a concrete reason and Memory V2 falls back to legacy. - `MEMORY_BACKEND=mem0` does not affect `resolve(...)`. - `write(...)` and `compact(...)` can use Mem0 when explicitly selected; legacy remains the default and rollback path. Runtime configuration: ```bash MEMORY_ENABLED=1 MEMORY_BACKEND=mem0 MEMORY_MEM0_ENABLED=1 MEMORY_MEM0_API_KEY='...' MEMORY_MEM0_PROJECT_ID='memind_project' MEMORY_MEM0_BASE_URL='https://api.mem0.ai' ``` Smoke: ```bash npm run smoke:memory-v2-external -- --backend mem0 --operation write ``` ## Letta Lifecycle Adapter `memory-v2-letta.mjs` defines the Memory Lifecycle Management backend boundary. Current constraints: - No Letta SDK is imported. - Runtime uses a lightweight HTTP client only when `MEMORY_LETTA_ENABLED=1`, `MEMORY_LETTA_API_KEY`, and `MEMORY_LETTA_AGENT_ID` are configured. - HTTP paths are configurable with `MEMORY_LETTA_RESOLVE_PATH`, `MEMORY_LETTA_WRITE_PATH`, and `MEMORY_LETTA_COMPACT_PATH`. - Without a full configuration, status reports `letta.available=false` with a concrete reason and Memory V2 falls back to legacy. - `resolve(...)`, `write(...)`, and `compact(...)` can use Letta when explicitly selected. Runtime configuration: ```bash MEMORY_ENABLED=1 MEMORY_BACKEND=letta MEMORY_LETTA_ENABLED=1 MEMORY_LETTA_API_KEY='...' MEMORY_LETTA_PROJECT_ID='memind_project' MEMORY_LETTA_AGENT_ID='agent_1' MEMORY_LETTA_BASE_URL='https://api.letta.com' ``` Smoke: ```bash npm run smoke:memory-v2-external -- --backend letta --operation resolve ``` Do not enable Letta as the selected backend in production until sampling, retention, conflict resolution, and rollback rules are defined for long/short-term lifecycle ownership. ## Remaining External Adapters The remaining optional backends now have runtime-wired clients: | Backend | File | Category | Runtime role | |---|---|---|---| | Weaviate | `memory-v2-weaviate.mjs` | semantic | GraphQL vector search | | Neo4j | `memory-v2-neo4j.mjs` | behavior | HTTP transaction behavior graph | | Redis Streams | `memory-v2-redis-streams.mjs` | behavior | Redis `XADD` event tracking | | LangGraph | `memory-v2-langgraph.mjs` | policy | HTTP policy resolve | Current constraints: - Weaviate and LangGraph use lightweight HTTP clients. - Neo4j uses the HTTP transactional endpoint; `neo4j-driver` is not introduced. - Redis Streams uses the existing `redis` dependency. - No default runtime path opens network connections; each adapter is dormant unless its feature flag and required config are present. - If configured incompletely, each adapter reports `available=false` with a concrete reason and Memory V2 falls back to legacy. - `redis-streams` is write-only and cannot affect `resolve(...)`. Runtime flags: ```bash MEMORY_WEAVIATE_ENABLED=1 MEMORY_WEAVIATE_URL='https://weaviate.example' MEMORY_WEAVIATE_COLLECTION='MemindMemory' MEMORY_WEAVIATE_EMBEDDING_MODULE='./scripts/embed-memory-v2-weaviate.mjs' MEMORY_NEO4J_ENABLED=1 MEMORY_NEO4J_HTTP_URL='http://localhost:7474' MEMORY_NEO4J_USER='neo4j' MEMORY_NEO4J_PASSWORD='...' MEMORY_NEO4J_DATABASE='neo4j' MEMORY_REDIS_STREAMS_ENABLED=1 MEMORY_REDIS_STREAMS_URL='redis://localhost:6379' MEMORY_REDIS_STREAMS_STREAM='memind:memory-events' MEMORY_LANGGRAPH_ENABLED=1 MEMORY_LANGGRAPH_URL='https://langgraph.example' MEMORY_LANGGRAPH_POLICY_ID='memory_policy' ``` Smoke examples: ```bash npm run smoke:memory-v2-external -- --backend weaviate --operation resolve npm run smoke:memory-v2-external -- --backend neo4j --operation write npm run smoke:memory-v2-external -- --backend redis-streams --operation write npm run smoke:memory-v2-external -- --backend langgraph --operation resolve ``` Future production canaries must define per-backend ownership boundaries before any of these adapters can take over `resolve(...)` or `write(...)`. ## Verification Targeted regression command: ```bash node --test memory-v2.test.mjs conversation-memory.test.mjs user-memory-profile.test.mjs tkmind-proxy.test.mjs node --test memory-v2-adapter-scaffold.test.mjs node --test memory-v2-backend-contract.test.mjs node --test memory-v2-health.test.mjs node --test memory-v2-runtime.test.mjs node --test memory-v2-plugin-backends.test.mjs node --test memory-v2-pgvector.test.mjs node --test memory-v2-pgvector-schema.test.mjs node --test memory-v2-pgvector-backfill.test.mjs node --test memory-v2-pgvector-smoke.test.mjs node --test memory-v2-qdrant.test.mjs node --test memory-v2-mem0.test.mjs node --test memory-v2-letta.test.mjs node --test memory-v2-external-adapters.test.mjs node --test scripts/check-memory-v2-contracts.test.mjs node --test scripts/check-memory-v2-health.test.mjs node --test scripts/setup-memory-v2-pgvector-schema.test.mjs node --test scripts/backfill-memory-v2-pgvector.test.mjs node --test scripts/scaffold-memory-v2-backend.test.mjs node --test scripts/smoke-memory-v2-pgvector.test.mjs node --test scripts/smoke-memory-v2-qdrant.test.mjs node --test scripts/smoke-memory-v2-external.test.mjs node --check server.mjs ``` The targeted tests cover: - legacy backend adaptation - backend adapter scaffold generation - backend adapter contract gate - local Memory V2 health gate checks - runtime pgvector wiring only when explicitly configured - configured backend selection and fallback - future plugin slot visibility without runtime selection - profile suppression via policy - fail-open resolve behavior - event-log write gating - session start memory injection through Memory V2 - disabled Memory V2 not touching legacy memory or injecting stored memories - runtime status exposing Memory V2 policy and backend contract - reply path passing the current user prompt into `resolve(...)` while keeping the existing `/reply` execution path - pgvector adapter disabled-by-default behavior and parameterized semantic lookup - Qdrant read-only HTTP client, smoke CLI, and fallback behavior - Mem0 extraction HTTP client and runtime fallback behavior - Letta lifecycle HTTP client and runtime fallback behavior - Weaviate, Neo4j, Redis Streams, and LangGraph runtime client behavior - manual pgvector schema SQL generation and execution ordering - pgvector schema CLI dry-run and explicit apply guard - pgvector backfill dry-run, checkpoint pagination, and idempotent upsert contract - pgvector backfill CLI dry-run and explicit apply guard - pgvector local smoke fixture and CLI guards