LanceDB-backed long-term memory provider for OpenCode.
- Supported: OpenCode
1.2.27+ - Configuration model: sidecar config file at
~/.config/opencode/lancedb-opencode-pro.json - Not recommended: top-level
memoryinopencode.json, because current OpenCode versions reject that key during config validation
- Register the published package name in
~/.config/opencode/opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
"oh-my-opencode",
"lancedb-opencode-pro"
]
}If you already use other plugins, keep them and append "lancedb-opencode-pro".
- For OpenCode
1.2.27+, create the sidecar config file~/.config/opencode/lancedb-opencode-pro.json:
{
"provider": "lancedb-opencode-pro",
"dbPath": "~/.opencode/memory/lancedb",
"embedding": {
"provider": "ollama",
"model": "nomic-embed-text",
"baseUrl": "http://127.0.0.1:11434"
},
"retrieval": {
"mode": "hybrid",
"vectorWeight": 0.7,
"bm25Weight": 0.3,
"minScore": 0.2,
"rrfK": 60,
"recencyBoost": true,
"recencyHalfLifeHours": 72,
"importanceWeight": 0.4
},
"includeGlobalScope": true,
"globalDetectionThreshold": 2,
"globalDiscountFactor": 0.7,
"unusedDaysThreshold": 30,
"minCaptureChars": 80,
"maxEntriesPerScope": 3000
}- Set
embedding.baseUrlto the Ollama endpoint that is reachable from that host.
- Same machine as OpenCode:
http://127.0.0.1:11434 - Another machine on the network: for example
http://192.168.11.206:11434
You do not need LANCEDB_OPENCODE_PRO_OLLAMA_BASE_URL if the sidecar file already contains the correct embedding.baseUrl. Use the environment variable only when you want to override the file at runtime.
- Make sure Ollama is reachable from that host, then start or restart OpenCode:
curl http://127.0.0.1:11434/api/tagsor, for a remote Ollama server:
curl http://192.168.11.206:11434/api/tagsAfter the first successful memory operation, LanceDB files should appear under:
~/.opencode/memory/lancedb
You can verify that the directory exists:
ls -la ~/.opencode/memory/lancedbUse this only when npm registry install is unavailable (for example, restricted network, offline staging, or registry outage).
- Download the latest published release asset:
curl -fL "https://github.com/tryweb/lancedb-opencode-pro/releases/latest/download/lancedb-opencode-pro.tgz" -o /tmp/lancedb-opencode-pro.tgz- Install into the fixed local plugin directory:
mkdir -p ~/.config/opencode/plugins/lancedb-opencode-pro
npm install --prefix ~/.config/opencode/plugins/lancedb-opencode-pro /tmp/lancedb-opencode-pro.tgz- Register the plugin as a
file://path in~/.config/opencode/opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
"oh-my-opencode",
"file:///home/<user>/.config/opencode/plugins/lancedb-opencode-pro/node_modules/lancedb-opencode-pro/dist/index.js"
]
}-
Reuse the same sidecar config from the primary install flow, then start/restart OpenCode.
-
Verify plugin file path:
ls -la ~/.config/opencode/plugins/lancedb-opencode-pro/node_modules/lancedb-opencode-pro/dist/index.jsThen verify memory store initialization.
Environment variables are optional. The recommended default is:
- keep durable settings in
~/.config/opencode/lancedb-opencode-pro.json - avoid setting
LANCEDB_OPENCODE_PRO_OLLAMA_BASE_URLunless you intentionally want a temporary or host-specific override
Example override:
export LANCEDB_OPENCODE_PRO_OLLAMA_BASE_URL="http://192.168.11.206:11434"This override has higher priority than the sidecar file.
Use this when you need an unpublished local build (for example, testing unreleased commits).
npm ci
npm run typecheck
npm run build
npm packThen install the generated tarball:
mkdir -p ~/.config/opencode/plugins/lancedb-opencode-pro
npm install --prefix ~/.config/opencode/plugins/lancedb-opencode-pro ./lancedb-opencode-pro-<version>.tgzUse the same file:// plugin registration shown in the fallback section above.
Use a sidecar config file. This is the supported configuration model for current OpenCode versions.
Create ~/.config/opencode/lancedb-opencode-pro.json:
{
"provider": "lancedb-opencode-pro",
"dbPath": "~/.opencode/memory/lancedb",
"embedding": {
"provider": "ollama",
"model": "nomic-embed-text",
"baseUrl": "http://127.0.0.1:11434"
},
"retrieval": {
"mode": "hybrid",
"vectorWeight": 0.7,
"bm25Weight": 0.3,
"minScore": 0.2,
"rrfK": 60,
"recencyBoost": true,
"recencyHalfLifeHours": 72,
"importanceWeight": 0.4
},
"includeGlobalScope": true,
"globalDetectionThreshold": 2,
"globalDiscountFactor": 0.7,
"unusedDaysThreshold": 30,
"minCaptureChars": 80,
"maxEntriesPerScope": 3000
}Optional project override path:
.opencode/lancedb-opencode-pro.json
Higher priority overrides lower priority:
- Environment variables (
LANCEDB_OPENCODE_PRO_*) LANCEDB_OPENCODE_PRO_CONFIG_PATH- Project sidecar:
.opencode/lancedb-opencode-pro.json - Global sidecar:
~/.config/opencode/lancedb-opencode-pro.json - Legacy sidecar:
~/.opencode/lancedb-opencode-pro.json - Legacy
config.memory - Built-in defaults
Supported environment variables:
LANCEDB_OPENCODE_PRO_CONFIG_PATHLANCEDB_OPENCODE_PRO_PROVIDERLANCEDB_OPENCODE_PRO_EMBEDDING_PROVIDER(ollamaoropenai, defaultollama)LANCEDB_OPENCODE_PRO_DB_PATHLANCEDB_OPENCODE_PRO_EMBEDDING_MODELLANCEDB_OPENCODE_PRO_OLLAMA_BASE_URLLANCEDB_OPENCODE_PRO_OPENAI_API_KEYLANCEDB_OPENCODE_PRO_OPENAI_MODELLANCEDB_OPENCODE_PRO_OPENAI_BASE_URLLANCEDB_OPENCODE_PRO_OPENAI_TIMEOUT_MSLANCEDB_OPENCODE_PRO_EMBEDDING_TIMEOUT_MSLANCEDB_OPENCODE_PRO_RETRIEVAL_MODELANCEDB_OPENCODE_PRO_VECTOR_WEIGHTLANCEDB_OPENCODE_PRO_BM25_WEIGHTLANCEDB_OPENCODE_PRO_MIN_SCORELANCEDB_OPENCODE_PRO_RRF_KLANCEDB_OPENCODE_PRO_RECENCY_BOOSTLANCEDB_OPENCODE_PRO_RECENCY_HALF_LIFE_HOURSLANCEDB_OPENCODE_PRO_IMPORTANCE_WEIGHTLANCEDB_OPENCODE_PRO_INCLUDE_GLOBAL_SCOPELANCEDB_OPENCODE_PRO_GLOBAL_DETECTION_THRESHOLDLANCEDB_OPENCODE_PRO_GLOBAL_DISCOUNT_FACTORLANCEDB_OPENCODE_PRO_UNUSED_DAYS_THRESHOLDLANCEDB_OPENCODE_PRO_MIN_CAPTURE_CHARSLANCEDB_OPENCODE_PRO_MAX_ENTRIES_PER_SCOPE
- Auto-capture of durable outcomes from completed assistant responses.
- Hybrid retrieval (vector + lexical) for future context injection.
- Project-scope memory isolation (
project:*+ optionalglobal). - Cross-project memory sharing via global scope with automatic detection.
- Memory tools:
memory_searchmemory_deletememory_clearmemory_statsmemory_feedback_missingmemory_feedback_wrongmemory_feedback_usefulmemory_effectivenessmemory_scope_promotememory_scope_demotememory_global_listmemory_port_plan
The provider can now record structured feedback about long-memory quality in addition to storing and recalling memories.
memory_feedback_missing: report information that should have been stored but was missedmemory_feedback_wrong: report a stored memory that should not have been keptmemory_feedback_useful: report whether a recalled memory was helpfulmemory_effectiveness: return machine-readable capture, recall, and feedback metrics for the active scope
Use memory_search or recalled memory ids from injected context when you need to reference a specific memory entry in feedback.
Use memory_effectiveness to inspect machine-readable effectiveness data for the active scope.
memory_effectiveness
Example output:
{
"scope": "project:my-project",
"totalEvents": 14,
"capture": {
"considered": 4,
"stored": 3,
"skipped": 1,
"successRate": 0.75,
"skipReasons": {
"below-min-chars": 1
}
},
"recall": {
"requested": 4,
"injected": 2,
"returnedResults": 3,
"hitRate": 0.75,
"injectionRate": 0.5,
"auto": {
"requested": 3,
"injected": 2,
"returnedResults": 2,
"hitRate": 0.67,
"injectionRate": 0.67
},
"manual": {
"requested": 1,
"returnedResults": 1,
"hitRate": 1
},
"manualRescueRatio": 0.33
},
"feedback": {
"missing": 1,
"wrong": 0,
"useful": {
"positive": 2,
"negative": 0,
"helpfulRate": 1
},
"falsePositiveRate": 0,
"falseNegativeRate": 0.25
}
}Key fields:
capture.successRate: how often a considered candidate was stored.recall.hitRate: blended rate across auto and manual recall — how often any recall request returned at least one result.recall.auto.*: metrics for automatic recall injected into the system prompt duringexperimental.chat.system.transform.recall.manual.*: metrics for user-initiatedmemory_searchcalls;injectedis always false for manual searches.recall.manualRescueRatio:manual.requested / auto.requested— a proxy for how often users still need to search manually despite automatic recall.feedback.falsePositiveRate: wrong-memory reports divided by stored memories.feedback.falseNegativeRate: missing-memory reports relative to capture attempts.
In real OpenCode usage, auto-capture and recall happen in the background, so explicit memory_feedback_* events are often sparse.
- Treat
capture.*andrecall.*as system-health metrics: they show whether the memory pipeline is running. - Treat
recall.auto.*andrecall.manual.*separately: auto metrics reflect pipeline health; manual metrics reflect whether users still need to rescue context manually. - Treat
recall.manualRescueRatioas a proxy for manual rescue rate: a high ratio suggests automatic recall is not surfacing relevant context on its own. - Treat repeated-context reduction, clarification burden, manual memory rescue, correction signals, and sampled audits as product-value signals: they show whether memory actually helped the user.
- Treat
feedback.* = 0as insufficient evidence, not proof that memory quality is good. - Treat a high
recall.hitRateorrecall.injectionRateas recall availability only; those values do not prove usefulness by themselves.
Recommended review order in low-feedback environments:
- Check
capture.successRate,capture.skipReasons,recall.hitRate, andrecall.injectionRatefor operational health. - Review whether users repeated background context less often or needed fewer clarification turns.
- Check whether users still needed manual rescue through
memory_searchor issued correction-like responses. - Run a bounded audit of recalled memories or skipped captures before concluding the system is helping.
Default behavior stays on Ollama. To use OpenAI embeddings, set embedding.provider to openai and provide API key + model.
Example sidecar:
{
"provider": "lancedb-opencode-pro",
"dbPath": "~/.opencode/memory/lancedb",
"embedding": {
"provider": "openai",
"model": "text-embedding-3-small",
"baseUrl": "https://api.openai.com/v1",
"apiKey": "sk-your-openai-key"
},
"retrieval": {
"mode": "hybrid",
"vectorWeight": 0.7,
"bm25Weight": 0.3,
"minScore": 0.2,
"rrfK": 60,
"recencyBoost": true,
"recencyHalfLifeHours": 72,
"importanceWeight": 0.4
},
"includeGlobalScope": true,
"globalDetectionThreshold": 2,
"globalDiscountFactor": 0.7,
"unusedDaysThreshold": 30,
"minCaptureChars": 80,
"maxEntriesPerScope": 3000
}Recommended env overrides for OpenAI:
export LANCEDB_OPENCODE_PRO_EMBEDDING_PROVIDER="openai"
export LANCEDB_OPENCODE_PRO_OPENAI_API_KEY="$OPENAI_API_KEY"
export LANCEDB_OPENCODE_PRO_OPENAI_MODEL="text-embedding-3-small"
export LANCEDB_OPENCODE_PRO_OPENAI_BASE_URL="https://api.openai.com/v1"lancedb-opencode-pro.json is parsed as plain JSON, so ${...} interpolation is not performed. Prefer environment variables for secrets.
Validation behavior:
- If
embedding.provider=openaiand API key is missing, initialization fails with an explicit configuration error. - If
embedding.provider=openaiand model is missing, initialization fails with an explicit configuration error. - Ollama remains the default provider when
embedding.provideris omitted.
Use memory_port_plan before writing docker-compose.yml to avoid host port collisions across projects on the same machine.
- Reads existing reservations from
globalscope - Probes live host port availability
- Returns non-conflicting assignments
- Optionally persists reservations for future projects (
persist=true)
Example tool input:
{
"project": "project-alpha",
"services": [
{ "name": "web", "containerPort": 3000, "preferredHostPort": 23000 },
{ "name": "api", "containerPort": 3001 }
],
"rangeStart": 23000,
"rangeEnd": 23999,
"persist": true
}Example output (trimmed):
{
"project": "project-alpha",
"persistRequested": true,
"persisted": 2,
"assignments": [
{
"project": "project-alpha",
"service": "web",
"hostPort": 23000,
"containerPort": 3000,
"protocol": "tcp"
},
{
"project": "project-alpha",
"service": "api",
"hostPort": 23001,
"containerPort": 3001,
"protocol": "tcp"
}
],
"warnings": []
}Map assignments into docker-compose.yml:
services:
web:
ports:
- "23000:3000"
api:
ports:
- "23001:3001"Notes:
- This is best-effort conflict avoidance, not a hard distributed lock.
- For safer operation in automation, run planning immediately before
docker compose up. - Reservations are upserted by
project + service + protocolwhenpersist=true.
npm install
npm run typecheck
npm run buildThe project provides layered validation workflows that can run locally or inside the Docker environment.
| Command | What it covers |
|---|---|
npm run test:foundation |
Write-read persistence, scope isolation, vector compatibility, timestamp ordering |
npm run test:regression |
Auto-capture extraction, search output shape, delete/clear safety, pruning |
npm run test:effectiveness |
Foundation + regression workflows covering effectiveness events, feedback commands, and summary output |
npm run test:retrieval |
Recall@K and Robustness-δ@K against synthetic fixtures |
npm run benchmark:latency |
Search p50/p99, insert avg, list avg with hard-gate enforcement |
npm run verify |
Typecheck + build + effectiveness workflow + retrieval (quick release check) |
npm run verify:full |
All of the above + benchmark + npm pack (full release gate) |
Threshold policy and benchmark profiles are documented in docs/benchmark-thresholds.md.
Acceptance evidence mapping and archive/ship gate policy are documented in docs/release-readiness.md.
Use this flow when publishing a new version to npm.
- Update
package.jsonversion andCHANGELOG.md. - Run the canonical release gate in Docker:
docker compose build --no-cache && docker compose up -d
docker compose exec app npm run release:check- Confirm npm authentication:
npm whoamiIf not logged in yet:
npm login- Publish from the host:
npm publish- Verify the package is live:
npm view lancedb-opencode-pro name versionNotes:
prepublishOnlyrunsnpm run verify:full, sonpm publishis blocked if the release gate fails.publishConfig.access=publickeeps first publish public.- For CI provenance attestation, publish from a supported CI provider with
npm publish --provenance. - If your npm account enforces 2FA, complete the browser or OTP challenge during publish.
If npm publish fails with errors like TS5033 ... EACCES: permission denied for files under dist/ or dist-test/, some build artifacts were likely created by root inside Docker.
Fix ownership from the container, then re-run publish:
docker compose up -d
docker compose exec -T -u root app sh -lc 'chown -R 1000:1000 /workspace/dist /workspace/dist-test 2>/dev/null || true'
npm publishYou can validate ownership first:
ls -l dist dist-test/src 2>/dev/nulldocker compose build --no-cache && docker compose up -d
docker compose exec app npm run typecheck
docker compose exec app npm run builddocker compose build --no-cache && docker compose up -d
# Quick release check
docker compose exec app npm run verify
# Full release gate (includes benchmark + pack)
docker compose exec app npm run verify:full
# Individual workflows
docker compose exec app npm run test:foundation
docker compose exec app npm run test:regression
docker compose exec app npm run test:retrieval
docker compose exec app npm run benchmark:latencyAfter running npm run verify:full, operators can inspect the following:
# Confirm the packaged build is installable
docker compose exec app ls -la lancedb-opencode-pro-*.tgz
# Confirm typecheck and build succeeded
docker compose exec app npm run typecheck
docker compose exec app npm run build
# Check resolved default storage path
docker compose exec app node -e "import('./dist/index.js').then(() => console.log('plugin loaded'))"
docker compose exec app sh -lc 'ls -la ~/.opencode/memory/lancedb 2>/dev/null || echo "No data yet (expected before first use)"'Use this checklist when you want to verify that lancedb-opencode-pro provides durable long-term memory instead of in-process temporary state.
docker compose build --no-cache && docker compose up -dThe E2E script loads dist/index.js, so build artifacts must exist first.
docker compose exec app npm install
docker compose exec app npm run builddocker compose exec app npm run test:e2eExpected success output:
E2E PASS: auto-capture, search, delete safety, clear safety, and clear execution verified.
This verifies all of the following in one run:
- assistant output is buffered and auto-captured
session.idletriggers durable persistencememory_searchcan retrieve the stored memorymemory_deleterequiresconfirm=truememory_clearrequiresconfirm=true
The E2E script uses /tmp/opencode-memory-e2e as its test database path.
docker compose exec app ls -la /tmp/opencode-memory-e2eIf files appear in that directory after the E2E run, memory was written to disk instead of only being kept in process memory.
When running through the normal plugin config, the default durable storage path is:
~/.opencode/memory/lancedb
Check it inside the container with:
docker compose exec app sh -lc 'ls -la ~/.opencode/memory/lancedb'Long memory is only convincing if retrieval still works after the runtime is restarted.
docker compose restart app
docker compose exec app npm run test:e2e
docker compose exec app ls -la /tmp/opencode-memory-e2eIf the search step still succeeds after restart and the database files remain present, that is strong evidence that the memory is durable.
Treat the feature as verified only when all of these are true:
docker compose exec app npm run test:e2epasses/tmp/opencode-memory-e2econtains LanceDB files after the run- the memory retrieval step still succeeds after container restart
- the configured OpenCode storage path exists when running real plugin integration
- Default storage path:
~/.opencode/memory/lancedb - Embedding provider defaults to
ollama;openaiis supported viaembedding.provider=openai - The provider keeps schema metadata (
schemaVersion,embeddingModel,vectorDim) to guard against unsafe vector mixing.