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_messagesandh5_user_memory_items - capability-driven memory hooks such as
memory_store,context_memory, andchat_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-memorysystem
Current Implementation
The first implementation is intentionally narrow:
memory-v2.mjsdefines the facade.memory-v2-runtime.mjswires server runtime backends from explicit environment variables.memory-v2-plugin-backends.mjsdefines disabled-by-default plugin slots for future backends.memory-v2-pgvector.mjsdefines a disabled-by-default semantic memory adapter skeleton.memory-v2-pgvector-schema.mjsdefines explicit, manual pgvector schema setup helpers.createLegacyMemoryBackend(...)adapts the existing conversation memory service.resolveMemoryV2Policy(...)maps feature flags into a stable policy object.server.mjscreates Memory V2 from the existing conversation memory service.tkmind-proxy.mjsreads user memories throughmemoryV2.resolve(...)before passing them into the existing session reconcile path./user-memory/v1/remember-recentwrites throughmemoryV2.write(...)./user-memory/v1/synccompacts throughmemoryV2.compact(...).- Runtime startup does not create schema.
- No goosed or SSE behavior is changed.
Runtime control flags
The memind_adm Memory V2 page exposes runtime controls separately from
MEMORY_ENABLED. They are disabled by default. Agent resolve currently
supports shadow observation and explicit active hidden-context injection;
promotion, Compact V2, and Reflection remain guarded for later stages:
| Admin field | Environment override | Default |
|---|---|---|
runtimeControl.agentResolveEnabled |
MEMORY_AGENT_RESOLVE_ENABLED |
0 |
runtimeControl.agentInjectionMode |
MEMORY_AGENT_INJECTION_MODE |
off |
runtimeControl.agentCanaryUserIds |
MEMORY_AGENT_CANARY_USER_IDS |
empty |
runtimeControl.agentResolveLimit |
MEMORY_AGENT_RESOLVE_LIMIT |
3 |
runtimeControl.agentResolveTimeoutMs |
MEMORY_AGENT_RESOLVE_TIMEOUT_MS |
1200 |
runtimeControl.promotionEnabled |
MEMORY_PROMOTION_ENABLED |
0 |
runtimeControl.compactionV2Enabled |
MEMORY_COMPACTION_V2_ENABLED |
0 |
runtimeControl.reflectionEnabled |
MEMORY_REFLECTION_ENABLED |
0 |
runtimeControl.lifecycleWorkerEnabled |
MEMORY_LIFECYCLE_WORKER_ENABLED |
0 |
runtimeControl.lifecycleRolloutMode |
MEMORY_LIFECYCLE_ROLLOUT_MODE |
off |
runtimeControl.lifecycleRolloutUserIds |
MEMORY_LIFECYCLE_ROLLOUT_USER_IDS |
empty |
The controls are reported in memoryV2.getStatus().runtimeControl. Turning
them off must leave the existing legacy conversation-memory path unchanged.
The additive user-scoped management API is:
GET /user-memory/v1/itemsto list active (or requested-status) items.DELETE /user-memory/v1/items/:memoryIdto forget one item when lifecycle forgetting is enabled.
Lifecycle workers are disabled by default. When explicitly enabled they run
expiration, conservative compaction observation, candidate promotion, and
reflection observation according to the rollout mode; none of these operations
blocks the chat path. off creates no worker scope and performs no lifecycle
mutation, canary is always user-scoped to the configured rollout IDs, and only
active permits an unscoped global worker run.
When candidate persistence is enabled, the Portal runtime idempotently creates
h5_memory_v2_candidates before enabling the MySQL candidate store. DDL failure
is fail-open for Portal chat and falls back to bounded in-memory candidates;
memind_adm uses the same schema helper during bootstrap and fails startup rather
than serving a permanently broken candidate API. The table is additive and must
not be dropped as part of an application rollback.
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=1MEMORY_PGVECTOR_DATABASE_URLis setMEMORY_PGVECTOR_EMBEDDING_MODULEpoints to a module exportingembedQuery(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:
{
"userId": "h5 user id",
"sessionId": "goosed session id",
"query": "current user prompt",
"limit": 40
}
Returns a normalized payload:
{
"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:
{
"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:
{
"userId": "h5 user id",
"sessionId": "goosed session id"
}
getStatus()
Returns the facade policy and backend contract status for read-only observability.
Example:
{
"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 byMEMORY_BACKEND
Optional fields:
isAvailable(): returnsfalsewhen the backend is configured but not usableresolve(input): returns profile, semantic memories, behavior summary, active goals, or legacy memorieswrite(input): records or queues memory evidencecompact(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=<future-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:
resolvemay use a semantic backend such as pgvector.writemust fall back to the first available backend that supports writes, usually legacy conversation memory.compactmust 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, orcompact - expose
isAvailable()or default to available - expose an unavailable reason when
isAvailable() === false - use a known plugin category when
categoryis present - keep feature flags in
SCREAMING_SNAKE_CASEwhenflagis present
The default gate checks legacy plus all disabled plugin slots:
npm run check:memory-v2-contracts
End-to-end local validation can now be run from one entrypoint:
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/statuscanary - real cookie-backed
register/login -> agent/start -> agent/runs -> SSE -> remember-recent -> syncsession 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:
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:
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 selectpgvectormemoryV2.write(...)andmemoryV2.compact(...)remain legacy-first/user-memory/v1/remember-recentand/user-memory/v1/syncstill reconcile back into the active session- the live Portal path
agent/start -> agent/runs -> SSE Finish -> session detailcontinues 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:
npm run scaffold:memory-v2-backend -- \
--name qdrant \
--category semantic \
--role scale-out-vector-store \
--capability resolve
Write a Mem0 adapter scaffold:
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:
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_RETRIEVER_EPISODIC_ENABLED
Enables the historical-session recall capability, but does not by itself authorize retrieval.
MEMORY_RETRIEVER_ENABLED must also be enabled and MEMORY_RETRIEVER_EPISODIC_MODE must be
canary or active. Missing or invalid mode is treated as off.
MEMORY_RETRIEVER_EPISODIC_MODE
Historical recall rollout mode: off, canary, or active. In canary mode only users listed in
MEMORY_RETRIEVER_EPISODIC_CANARY_USER_IDS may be indexed or queried.
MEMORY_RETRIEVER_EPISODIC_CANARY_USER_IDS
Comma- or whitespace-separated user IDs for historical recall canary testing.
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.
MEMORY_PGVECTOR_SYNC_USER_LIMIT
Maximum number of recent active memories synchronously upserted for the current
user after a successful write(...), compact(...), or candidate promotion.
Defaults to 50. When a session ID is available the sync is additionally scoped
to that session. This immediate, idempotent sync keeps newly saved memories
recallable without waiting for the separate global checkpoint backfill.
Current Integration Points
Memory V2 is only allowed to sit above existing memory code.
Read path:
tkmind-proxy
-> memoryV2.resolve
-> legacy conversation-memory backend
-> existing reconcileAgentSession
-> existing buildSessionMemoryEntries
-> existing harness remember/bootstrap
Explicit historical recall is a separate bounded read path layered beside personal memory:
direct chat / Agent memory context
-> episodicMemory.resolve
-> h5_episodic_memory_items (same user, excluding current session)
-> bounded h5_session_snapshots fallback for pre-index history
Write path:
/user-memory/v1/remember-recent
-> memoryV2.write
-> legacy conversation-memory saveAndAnalyze
Compact path:
/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:
/api/runtime/status
-> memory: memoryV2.getStatus()
This is read-only and exists only to make rollout/debugging visible.
Local health gate:
npm run check:memory-v2-contracts
npm run check:memory-v2 -- --require-enabled --expect-backend legacy
Local app canary against a running Portal service:
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:
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.
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:
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:
- Keep
MEMORY_BACKEND=legacy. - Enable
MEMORY_ENABLED=1. - Keep
MEMORY_VECTOR_ENABLED=0. - Verify
/api/runtime/statusreportsmemory.enabled=trueandselectedBackend=legacy-conversation-memory. - Run targeted tests before release.
Rollback:
MEMORY_ENABLED=0
Rollback must stop Memory V2 reads/writes while preserving non-memory session reconcile behavior.
Future backend rollout:
- Add the backend adapter behind
MEMORY_BACKEND=<name>. - Replace the matching unavailable placeholder with the real adapter only inside Memory V2 wiring.
- Verify
getStatus().backends[]reports capability support. - Keep backend disabled or unavailable by default.
- Run local retrieval/write tests without changing goosed/SSE.
- 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 injectedembedQuery(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:
import { ensurePgvectorMemorySchema } from './memory-v2-pgvector-schema.mjs';
await ensurePgvectorMemorySchema(pool, {
tableName: 'memory_embeddings',
dimensions: 1536,
createExtension: true,
createVectorIndex: false,
});
Dry-run CLI:
node scripts/setup-memory-v2-pgvector-schema.mjs \
--table memory_embeddings \
--dimensions 1536 \
--create-extension
Apply CLI:
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:
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:
- Confirm pgvector is installed on the target PostgreSQL server.
- Run
CREATE EXTENSION IF NOT EXISTS vectoronly through the approved DB migration path. - Create the empty
memory_embeddingstable first; do not backfill in the same release. - Keep
MEMORY_BACKEND=legacyafter table creation. - Build a separate backfill job from
h5_user_memory_itemsinto pgvector with idempotent checkpoints. - Verify row counts, embedding dimensions, and retrieval quality before canarying
MEMORY_BACKEND=pgvector. - Rollback for the app remains
MEMORY_BACKEND=legacyorMEMORY_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:
h5_user_memory_items
WHERE status = 'active'
ORDER BY updated_at ASC, id ASC
Destination:
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:
MEMORY_BACKFILL_MYSQL_URL='mysql://...' \
node scripts/backfill-memory-v2-pgvector.mjs \
--limit 100 \
--cursor-updated-at 0 \
--cursor-id ''
Apply CLI:
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:
- Run schema migration and keep
MEMORY_BACKEND=legacy. - Run dry-run batches and inspect counts/checkpoints.
- Run apply batches with a fixed embedding model and recorded dimensions.
- Store the last checkpoint externally after each successful batch.
- Compare MySQL active memory count against pgvector distinct
source_memory_idcount. - Validate retrieval quality on a canary user.
- Only then consider
MEMORY_BACKEND=pgvectorcanary.
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:
MEMORY_PGVECTOR_DATABASE_URL='postgresql://...' \
npm run smoke:memory-v2-pgvector
Local setup smoke that also creates an empty schema:
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:
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, andMEMORY_QDRANT_EMBEDDING_MODULEare configured. - It never creates collections.
- It never writes or upserts points.
write(...)andcompact(...)continue to use legacy memory.
Runtime read-only configuration:
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:
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:
selectedBackend=legacy-conversation-memory
qdrant.available=false
qdrant.reason=embedding_module_not_configured
Expected with embedding module and reachable Qdrant:
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=1andMEMORY_MEM0_API_KEYare configured. - HTTP paths are configurable with
MEMORY_MEM0_WRITE_PATHandMEMORY_MEM0_COMPACT_PATH. - Without a full configuration, status reports
mem0.available=falsewith a concrete reason and Memory V2 falls back to legacy. MEMORY_BACKEND=mem0does not affectresolve(...).write(...)andcompact(...)can use Mem0 when explicitly selected; legacy remains the default and rollback path.
Runtime configuration:
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:
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, andMEMORY_LETTA_AGENT_IDare configured. - HTTP paths are configurable with
MEMORY_LETTA_RESOLVE_PATH,MEMORY_LETTA_WRITE_PATH, andMEMORY_LETTA_COMPACT_PATH. - Without a full configuration, status reports
letta.available=falsewith a concrete reason and Memory V2 falls back to legacy. resolve(...),write(...), andcompact(...)can use Letta when explicitly selected.
Runtime configuration:
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:
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-driveris not introduced. - Redis Streams uses the existing
redisdependency. - 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=falsewith a concrete reason and Memory V2 falls back to legacy. redis-streamsis write-only and cannot affectresolve(...).
Runtime flags:
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:
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:
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/replyexecution 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