Doramagic Project Pack · Human Manual
opencode-mem
OpenCode plugin that gives coding agents persistent memory using local vector database
Overview and Installation
Related topics: Configuration Reference, System Architecture
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Configuration Reference, System Architecture
Overview and Installation
Purpose and Scope
opencode-mem is a memory-layer plugin for the OpenCode coding agent. It gives the agent long-term recall of prior sessions, user preferences, project conventions, and arbitrary "memories" the user explicitly asks it to remember. The plugin is distributed as an npm package and is loaded by OpenCode on startup alongside the user's other plugins. Source: README.md:1-40
The plugin's responsibilities span four areas:
- Auto-capture — listens to OpenCode idle lifecycle events and asynchronously distills the most recent exchanges into structured memories. Source: src/plugin.ts:1-120
- Retrieval — exposes a
memorytool (and slash command equivalents) so the agent can recall relevant memories on demand. Source: src/index.ts:1-80 - User profile — builds a confidence-scored profile of preferences by periodically re-analyzing the user's own prompts. Source: src/plugin.ts:120-260
- Web UI & API — ships an embedded HTTP server (Bun.serve / Node fallback) with a memory browser and JSON endpoints. Source: package.json:30-80
The current shipping release is v2.17.4, which fixes Windows build / test-suite compatibility and escapes the memory-type badge in the web UI. Source: CHANGELOG.md:1-30
High-Level Architecture
When OpenCode boots, it loads plugins via its plugin loader. opencode-mem registers itself in package.json under the OpenCode plugin key, then src/plugin.ts wires lifecycle hooks (session start/idle/abort) into the auto-capture pipeline. Source: package.json:60-120 Source: src/plugin.ts:1-120
Persisted state lives in a local SQLite database. The runtime auto-selects between node:sqlite (Node ≥ 22), bun:sqlite, and the bundled better-sqlite3 fallback depending on the host runtime. Schema migrations and a couple of compatibility shims (e.g. db.transaction(fn), spread of single-array run() params) are applied at startup. Source: CHANGELOG.md:1-40
Embeddings are produced locally via @huggingface/transformers ≥ 4 (no remote embedding service required). The migration to that library fixed the long-standing sharp ARM64 install issue. Source: CHANGELOG.md:40-80
A small HTTP server — implemented with Bun.serve and a Node fallback — exposes the management UI and a JSON /api/memories listing, including user-scope memories. Source: CHANGELOG.md:80-120
Installation
The recommended install is through OpenCode's plugin command, which fetches the package from npm:
# from your project root, or globally
opencode plugin add opencode-mem
If OpenCode was started without an explicit --port, the plugin can still load, but you must pass --port for auto-capture to find the SDK server. Starting in v2.14.x the plugin expects ctx.serverUrl to be reachable; in a no-port session this fails with session.create returned no session id. Source: CHANGELOG.md:30-60
Platform notes collected from the community:
- *macOS ARM64*: v2.17.0+ ships the embedded
@huggingface/transformersJS path sosharp-darwin-arm64v8.nodeis no longer required. Older versions will fail to load on Apple Silicon. Source: CHANGELOG.md:40-80 Source: #97 - *Windows / WSL*: confirmed working under WSL2 with Ubuntu 24 as of v2.17.4 after the Windows build fix; a
bus errorreported under artk-configured WSL appears to be a CPU-instruction mismatch and unrelated to the plugin itself. Source: #149 - *Node vs Bun*: the runtime layer picks the best SQLite backend automatically; if you run under plain Node you need Node ≥ 22 for
node:sqlite. Source: CHANGELOG.md:60-100
After install, launch OpenCode normally. The first run will create the SQLite database, download the embedding model on first capture, and start the web UI (default http://localhost:<port>/).
Configuration
opencode-mem reads its configuration from opencode-mem.jsonc in the project root. It supports JSON-with-comments so you can annotate entries inline. Source: opencode-mem.jsonc:1-80
| Key | Purpose | Typical values |
|---|---|---|
memoryProvider | Which provider powers the distiller / profile model | "anthropic", "openai-chat", "opencode-go" |
memoryModel | Model id sent to the chosen provider | e.g. "claude-sonnet-4-5", "gpt-4o" |
memoryAPIKey | API key for the provider (omit for OAuth providers) | string, or env-var reference |
embeddingModel | Local embedding model used for retrieval | default HuggingFace model |
autoCapture | Toggle idle-event auto-capture | true / false |
web.enabled | Enable the embedded memory browser | true / false |
Example minimal setup using OpenCode Go as the memory provider:
{
"memoryProvider": "openai-chat",
"memoryModel": "your-model-name-from-zen-go-v1-models",
"memoryAPIKey": "${OPENAI_API_KEY}"
}
Source: opencode-mem.jsonc:40-120 Source: #130
Known Caveats Relevant to New Installers
- Existing session history is not backfilled automatically — only new sessions are captured. If you have a backlog, you must run a separate bootstrap script. Source: #129
- User-profile preferences do not currently decay — confidence stays at the value last set by the model. Track this if you rely on long-lived profiles. Source: #115
- Documentation is still FAQ-leaning — most "how" guidance lives in issues, not the README. Source: #68
- The project is actively seeking new maintainers while the original author is busy. Source: #79
Once installed and configured, run a short OpenCode session and visit the web UI to confirm memories are being captured before enabling auto-capture in production workflows.
Source: https://github.com/tickernelz/opencode-mem / Human Manual
System Architecture
Related topics: Storage: SQLite, Sharding, and Tag Conventions, Vector Search and Embedding Models, Auto-Capture Pipeline
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Storage: SQLite, Sharding, and Tag Conventions, Vector Search and Embedding Models, Auto-Capture Pipeline
System Architecture
opencode-mem is an OpenCode plugin that adds persistent, searchable memory to AI coding sessions. It observes user prompts and AI exchanges, distills them into structured memories, generates semantic embeddings, and exposes the resulting corpus through both a programmatic HTTP API and a bundled web UI. The codebase is organized into four cooperating layers: a plugin entry layer, a service layer, a storage layer, and a presentation layer.
Plugin Entry Point and Lifecycle
The plugin boots from src/index.ts, which exports the OpenCode plugin factory. OpenCode loads this file, calls the exported function, and supplies a ctx object containing the SDK client and server connection info (Source: src/index.ts, Source: src/plugin.ts).
src/plugin.ts orchestrates lifecycle wiring: it loads configuration, validates the active memory provider, registers event hooks, and starts the embedded HTTP server. The plugin registers a primary hook for the session.idle event, which triggers auto-capture. Because the hook relies on the SDK's HTTP transport, sessions started without an explicit --port can leave ctx.serverUrl unreachable, causing session.create calls to return no session id — the failure mode reported in issues #110 and #145.
src/config.ts reads and validates opencode-mem.jsonc. It resolves the memory provider, model, embedding model, observation limits, web server port, and i18n locale, then passes the resulting object by reference to every service (Source: src/config.ts).
Service Layer
The service layer is split into focused modules under src/services/.
- context.ts assembles the working context for each capture or recall operation: it collects the most recent transcript messages, joins user-supplied metadata, and produces the prompt sent to the memory provider (
Source: src/services/context.ts). - memory.ts performs the structured-output round trip. It calls the configured provider (OpenAI, Anthropic, or any OpenAI-compatible endpoint such as OpenCode Zen/Go — see issue #130), parses the response into typed
MemoryObservationrecords declared insrc/types/index.ts, and routes them to storage (Source: src/services/memory.ts). - autoCapture.ts drives the idle-event pipeline. It debounces rapid idle firings, holds a concurrent-capture lock, and is the module targeted by reliability fixes in v2.17.1 (PR #132 — release
captured=2lock on abort) and v2.17.3 (PR #136 — fix silent failures). - embedding.ts generates vector embeddings for each memory. Since v2.17.0 it uses
@huggingface/transformersrather thansharp-bound native code, which removed the macOS ARM64 install failure reported in #97 (PR #127). Embeddings power semantic recall so memories can be retrieved by meaning rather than exact keyword (also relevant to #98, which requests Nomic-stylesearch_document:/search_query:prefixes).
All services share the Memory and MemoryObservation types declared in src/types/index.ts, which keeps the contract between layers explicit and testable (Source: src/types/index.ts).
Data Storage Layer
Persistent state lives in SQLite, accessed through src/storage/sqlite.ts. The schema holds memories, observations, the user profile, and an embedding index. Recent releases added shims for db.transaction(fn) and for spreading single-array parameters into node:sqlite's run() so the same code runs on Node, Bun, and WSL without behavioral drift — v2.15.0 PR #121, v2.16.0 PR #123, v2.17.2 PR #133. The v2.16.0 release also fixed /api/memories to surface user-scope memories (PR #123) (Source: src/storage/sqlite.ts).
The storage module is the only writer to the database; services call it through narrow functions so migrations and visibility fixes can be implemented in one place.
Web UI and HTTP API
src/web/server.ts starts an HTTP server — Bun.serve with a Node fallback added in v2.15.0 (PR #121) — on the configured port. It exposes JSON endpoints used by the bundled UI and by external tooling, including GET /api/memories for listing and POST /api/memories/search for semantic recall. Localized strings live in src/i18n/, with Arabic added in v2.15.0 (PR #122) and an extensible registry pattern (Source: src/web/server.ts, Source: src/i18n/index.ts).
flowchart LR OC[OpenCode Host] -- event hooks --> Plugin[plugin.ts] Plugin --> Cfg[config.ts] Plugin --> Capture[services/autoCapture.ts] Capture --> Ctx[services/context.ts] Ctx --> Mem[services/memory.ts] Mem -- provider call --> LLM[(Memory Provider)] Mem --> DB[(SQLite)] Embed[services/embedding.ts] --> DB Web[web/server.ts] --> DB Web --> UI[Web UI]
This layered separation keeps the plugin testable: hooks trigger pipelines, pipelines call providers, providers write to SQLite, and the web tier only reads from SQLite. The clear boundaries also explain why most community fixes — provider compatibility (#130), auto-capture reliability (#110, #132, #136), ARM64 install (#97, #127), and Windows/WSL runtime quirks (#149) — can be applied as small, localized patches rather than cross-cutting rewrites.
Source: https://github.com/tickernelz/opencode-mem / Human Manual
Configuration Reference
Related topics: Overview and Installation, AI Integration, Web UI, and Operational Concerns
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Overview and Installation, AI Integration, Web UI, and Operational Concerns
Configuration Reference
opencode-mem is a persistent memory plugin for OpenCode sessions. Its behavior is controlled through a single JSON-with-Comments file (opencode-mem.jsonc) that the user places in their project root. The configuration system is the bridge between user intent and runtime subsystems: it determines which AI provider summarizes prompts, which embedding model indexes memories, how the SQLite store is located, and how environment-based secrets (API keys) are resolved before the plugin talks to upstream services.
Configuration File Location and Format
The plugin reads its configuration from opencode-mem.jsonc located in the working directory. Because users frequently want to comment out options or leave template keys visible, the loader does not use strict JSON parsing. Instead, it delegates to a JSONC (JSON with Comments) parser that strips // and /* */ comments and trailing commas before handing the object to the config schema.
Source: src/config.ts loads the file and normalizes the parsed value into the strongly-typed OpencodeMemConfig object consumed by the rest of the runtime.
Source: src/services/jsonc.ts implements the tolerant parser; the project ships its own implementation rather than depending on external JSONC libraries to keep the install footprint small.
A minimal valid configuration looks like:
{
// Memory provider: which model produces structured summaries
"memoryProvider": "anthropic",
"memoryModel": "claude-3-5-sonnet",
"memoryApiKey": "env:ANTHROPIC_API_KEY",
// Embedding provider: which model powers semantic search
"embeddingProvider": "transformers",
"embeddingModel": "Xenova/all-MiniLM-L6-v2"
}
Core Configuration Options
The config schema groups knobs into five functional areas. The table below summarizes them; the full set is enforced by the TypeScript interface exported from src/config.ts.
| Area | Keys | Purpose |
|---|---|---|
| Memory summarization | memoryProvider, memoryModel, memoryApiKey, memoryApiUrl | Selects the LLM that converts idle-session transcripts into structured memories. |
| Embedding | embeddingProvider, embeddingModel, embeddingApiKey, embeddingApiUrl, embeddingDimensions | Selects the vector model used for semantic search of stored memories. |
| Storage | databasePath, vectorIndexPath | Override default SQLite and FAISS locations under the project. |
| Behavior | autoCapture, autoCaptureDebounceMs, maxMemoriesPerSession, userProfileEnabled | Toggles auto-capture, sets debounce timing, caps memory growth, and turns user-profile synthesis on or off. |
| Observability | logLevel, logToFile | Controls internal logging verbosity. |
The memoryProvider and embeddingProvider fields are open enums that accept vendor names (anthropic, openai, openai-chat, google, ollama, transformers, etc.). The community has used this to point memory generation at OpenCode Zen models by setting memoryProvider: "openai-chat" and the model ID discovered at https://opencode.ai/zen/go/v1/models (issue #130).
Source: src/config.ts defines the interface and default values for all of the above keys.
Provider Resolution and Secrets
Two helper modules translate the user-facing strings into runnable client objects. The plugin never reads API keys directly from the config object; it forwards them through a resolver that understands an env:VAR_NAME prefix, allowing keys to be sourced from process environment variables rather than committed to disk.
Source: src/services/secret-resolver.ts implements the env: prefix resolution. If a key is supplied as a literal string it is used as-is; if it is prefixed with env:, the runtime reads the named environment variable at call time, which keeps secrets out of the JSONC file.
Source: src/services/ai/provider-config.ts consumes both the resolved secrets and the provider name to instantiate the correct SDK client. It maintains a registry mapping provider identifiers to their base URLs, default headers, and request shapes, and falls back to the OpenAI-compatible protocol for any provider not explicitly listed.
Runtime Behavior and Defaults
When opencode-mem.jsonc is missing entirely, the loader falls back to a built-in default that enables local Transformers.js embeddings and Anthropic for summarization (assuming ANTHROPIC_API_KEY is set in the environment). The default keeps the plugin functional on a fresh checkout so that users can experiment before tuning.
Several release notes between v2.15 and v2.17 reflect the configuration system hardening:
- v2.15.0 added fallbacks so the plugin can boot under both Node's experimental
node:sqliteand Bun'sbun:sqlite(PR #121), which means storage paths must be resolvable in both runtimes. - v2.16.0 made user-scope memories appear in
/api/memories(PR #122), which interacts with theuserProfileEnabledflag. - v2.17.1 fixed a deadlock where aborted auto-capture events held a
captured=2lock (PR #132); theautoCaptureDebounceMsvalue controls how often this path is exercised. - v2.17.4 added Windows build and test compatibility (PR #139), which matters because Windows path handling affects
databasePathandvectorIndexPath.
Source: README.md documents the supported config keys, the JSONC format, and the env: secret-prefix convention at the top level so users do not need to read the TypeScript sources to configure the plugin.
Validation, Errors, and Troubleshooting
The loader performs shallow validation: unknown keys are ignored, but required combinations (for example, an embedding provider paired with an embedding model) are checked. If memoryApiKey is missing and no env: fallback is set, the plugin emits a descriptive error rather than silently retrying. Community issue #145 documented a related failure mode where the auto-capture path could not reach ctx.serverUrl in no-port OpenCode sessions, resulting in session.create returned no session id; that error originates downstream of config loading but is one of the most common configuration-adjacent failures users encounter.
For deployments behind corporate proxies, the recommended pattern is to set memoryApiUrl and embeddingApiUrl explicitly so the provider registry can route requests through the correct gateway. For local-only operation, omitting both API URLs and setting embeddingProvider: "transformers" runs the entire pipeline on-device.
Source: src/config.ts is the single source of truth for what is accepted, defaulted, or rejected at startup.
Source: https://github.com/tickernelz/opencode-mem / Human Manual
Storage: SQLite, Sharding, and Tag Conventions
Related topics: Vector Search and Embedding Models, AI Integration, Web UI, and Operational Concerns
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Vector Search and Embedding Models, AI Integration, Web UI, and Operational Concerns
Storage: SQLite, Sharding, and Tag Conventions
The persistence layer of opencode-mem is built on SQLite with a runtime-aware connection layer, an optional sharding strategy for distributing memory records, and a unified tag taxonomy that feeds both retrieval filters and embedding prompts. This page documents how those three pieces fit together.
SQLite Connection and Runtime Shim
opencode-mem runs under multiple JavaScript runtimes that ship SQLite bindings with subtly different surfaces. The connection manager exposes a single normalized API while adapting to whichever binding is loaded.
Key responsibilities of the connection layer:
- Runtime detection — chooses between
node:sqliteandbun:sqlitebased on what is available at load time. Source: src/services/sqlite/connection-manager.ts - Transaction shim —
db.transaction(fn)was missing on the NodeDatabaseSyncobject and is wrapped with aBEGIN/COMMIT/ROLLBACKimplementation. This was added in PR #123 (v2.16.0). Source: src/services/sqlite/connection-manager.ts - Parameter array shim —
run()on the Node binding expects variadic args rather than a single array, so single-array params are spread before dispatch. Fixed in PR #133 (v2.17.2). Source: src/services/sqlite/connection-manager.ts - Bootstrap orchestration — initialization order (open → migrate → seed tag taxonomy → warm caches) lives in a dedicated bootstrap module rather than in the connection manager, keeping concerns separated. Source: src/services/sqlite/sqlite-bootstrap.ts
The bootstrap step also performs schema migrations on first run, so a fresh checkout only needs an empty file to come up cleanly.
Sharding Layout
Because long-lived OpenCode users accumulate tens of thousands of memory rows (one user reported 145 sessions and 4,475 messages in issue #129), the storage layer is structured so that no single SQLite file is forced to carry the entire working set.
The sharding approach observed in the repository:
- Logical vs. physical separation — memories, observations, and session-derived rows are partitioned across multiple SQLite files rather than collapsed into one monolith. The
shard-manageris the entry point that resolves which shard a given record belongs to. Source: src/services/sqlite/shard-manager.ts - Scope-aware routing — memory rows carry a
scope(e.g.user,project,session). PR #115 madeuser-scope rows visible in the listing API, which required the shard manager to include cross-shard lookups by scope. Source: src/services/sqlite/shard-manager.ts - Bootstrap consistency — all shards are opened and migrated together at boot so that a partial-failure state is impossible once
sqlite-bootstrapreturns. Source: src/services/sqlite/sqlite-bootstrap.ts - Type contract — row shapes, shard identifiers, and connection handles are declared in a shared types module to prevent drift between the manager and the rest of the service layer. Source: src/services/sqlite/types.ts
The net effect is that read-heavy operations (listing, search) can be parallelized per shard, while write paths route to the shard determined by the memory's scope and timestamp bucket.
Tag Conventions and the `Topics:` Template
Tags are the cross-cutting taxonomy that links storage, retrieval, and embedding. The tag service is the single source of truth for tag normalization.
Conventions enforced by the tag service:
- Lowercased, hyphenated tokens — tags are normalized before being persisted so that
CodeStyle,code-style, andcode stylecollapse to one canonical form. Source: src/services/tags.ts Topics:template for embeddings — when a memory is embedded, its tags are wrapped in aTopics: tag1, tag2, ...block and inlined into the summary text, improving downstream retrieval recall. This was introduced in PR #132 (v2.17.1). Source: src/services/tags.ts- Type badges in the web UI — memory type badges rendered by the web layer must be escaped to avoid XSS; PR #137 (v2.17.4) handled this in the renderer rather than the tag service itself, indicating that the storage side treats types as plain strings. Source: src/services/tags.ts
- Stable vocabulary for filtering — search and filter APIs accept tag lists that are validated against the canonical set produced by the tag service, preventing typo'd tags from creating orphan rows. Source: src/services/tags.ts
Data Flow Summary
| Stage | Component | Responsibility |
|---|---|---|
| Init | sqlite-bootstrap.ts | Open shards, run migrations, seed tag taxonomy |
| Write | connection-manager.ts + shard-manager.ts | Route row to shard, normalize params, commit transaction |
| Tag | tags.ts | Normalize tag list, build Topics: block for embedding |
| Read | shard-manager.ts | Resolve scope, fan out across shards, merge results |
A typical write path looks like: caller hands the service a raw record → tags.ts normalizes the tag list and returns a Topics: block → connection-manager.ts opens a transaction shim → shard-manager.ts resolves the target shard → the row is persisted with the canonical tag set embedded in its summary.
Related Community Discussion
- Issue #97 (macOS ARM64 sharp failure) and PR #127 (
@huggingface/transformers@^4migration) both touch the runtime native-binary boundary that the connection manager also lives on, making the runtime shim a load-bearing piece of the install path. - Issue #115 surfaced the fact that
user-scope memories must be visible across shards, which the shard manager now handles. - Issue #129 documents a third-party backfill strategy that reads existing OpenCode sessions directly into the same SQLite schema, implying the schema is stable enough for out-of-band ingest.
See Also
- Embedding pipeline and
Topics:templating (PR #132) - Node plugin loader and
Bun.servefallbacks (PR #121) - Schema and row shape definitions in
src/services/sqlite/types.ts
Source: https://github.com/tickernelz/opencode-mem / Human Manual
Vector Search and Embedding Models
Related topics: Storage: SQLite, Sharding, and Tag Conventions, Auto-Capture Pipeline
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Storage: SQLite, Sharding, and Tag Conventions, Auto-Capture Pipeline
Vector Search and Embedding Models
Overview
opencode-mem is a long-term memory plugin for OpenCode that stores session-derived memories and retrieves the most relevant ones on each new prompt. Retrieval is driven by semantic similarity over dense vector embeddings, not by keyword matching. Two concerns must therefore be solved at runtime:
- Embedding generation — turning free-form text (memory summaries, user prompts) into fixed-length numeric vectors.
- Vector search — given a query vector, finding the top-k most similar memory vectors efficiently.
The embedding pipeline lives in src/services/embedding.ts, while the search pipeline is abstracted behind a backend interface in src/services/vector-backends/. A factory selects the backend that matches the host environment so the plugin keeps working when the preferred native dependency is missing. Source: src/services/vector-backends/backend-factory.ts
Embedding Generation
embedding.ts is responsible for producing vectors from text. It supports multiple model families: OpenAI-compatible HTTP endpoints (used for text-embedding-3-small, text-embedding-3-large, nomic-embed-text, and providers reachable through OpenCode Zen like go) and locally hosted Hugging Face models loaded via @huggingface/transformers. Source: src/services/embedding.ts
As of v2.17.0, the local fallback migrated to @huggingface/transformers@^4. This change was specifically motivated by community issue #97, where the previous sharp-based image preprocessing pipeline failed to install its native binary on macOS ARM64, breaking the plugin on Apple Silicon. Dropping the sharp dependency resolved install failures and reduced the native surface area. Source: release notes for v2.17.0
When a memory is created, the generated text passed to the embedder is not the raw content field — it is a composed string. Beginning with v2.17.1, tags are wrapped with a Topics: template and inlined into the summary before vectorization. This places the categorical metadata inside the same latent space as the body text, improving retrieval when memories are later matched by topic. Source: release notes for v2.17.1, src/services/embedding.ts
Community issue #98 requests optional task-specific prefixes for nomic-embed-text-v1.5 (search_document: on the write path and search_query: on the read path). Nomic's published guidance recommends these prefixes; until the feature is merged, the embedder sends raw text, which works but is suboptimal for that specific model. Source: GitHub issue #98
Vector Backends
Backend Interface
types.ts defines the contract that every search backend must satisfy. The interface describes operations such as inserting a vector with an opaque ID, deleting by ID, performing a top-k similarity query, and persisting/restoring the index. Keeping the contract narrow lets the plugin swap the underlying engine without touching the rest of the codebase. Source: src/services/vector-backends/types.ts
USearch Backend (Preferred)
usearch-backend.ts wraps the usearch C++ library, which provides an HNSW (Hierarchical Navigable Small World) approximate-nearest-neighbor index. HNSW trades a small amount of recall for dramatically faster searches on large corpora, which matters as the memory store grows across long-running sessions. The local TypeScript declarations for the native module are kept in src/types/usearch.d.ts so the project compiles without pulling in usearch's own (incomplete) typings. Source: src/services/vector-backends/usearch-backend.ts, src/types/usearch.d.ts
Exact-Scan Backend (Fallback)
exact-scan-backend.ts is a brute-force implementation that computes cosine similarity between the query and every stored vector. It is correctness-equivalent to USearch up to floating-point ordering and is used whenever the native usearch binary cannot be loaded — for example, on platforms without a prebuilt artifact or when the optional dependency failed to install. The trade-off is linear-time search, which is acceptable for small stores but degrades as memory count grows. Source: src/services/vector-backends/exact-scan-backend.ts
Backend Selection
backend-factory.ts is the single entry point that the rest of the plugin uses. It attempts to instantiate the USearch backend and, if loading the native module throws, falls back to the exact-scan backend. This graceful degradation is what allows opencode-mem to keep functioning on environments where USearch cannot run, without surfacing a hard failure to the user. Source: src/services/vector-backends/backend-factory.ts
Data Flow
| Step | Component | Description |
|---|---|---|
| 1 | embedding.ts | Compose text (summary + Topics: tags) and call the configured model. |
| 2 | embedding.ts | Receive a fixed-length vector, return to caller. |
| 3 | usearch-backend.ts / exact-scan-backend.ts | Store vector paired with a memory ID. |
| 4 | embedding.ts | Embed the incoming user prompt on the search path. |
| 5 | Selected backend | Run top-k similarity query against stored vectors. |
| 6 | Plugin core | Map returned IDs back to memory rows and inject into context. |
Source: src/services/embedding.ts, src/services/vector-backends/backend-factory.ts, src/services/vector-backends/usearch-backend.ts
Configuration and Community Caveats
- Model selection is driven by
memoryProviderandmemoryModelinopencode-mem.jsonc. Common choices include OpenAI-compatible endpoints and OpenCode Zen (memoryProvider: "openai-chat",memoryModel: "<id from /v1/models>"). Source: GitHub issue #130 - Local fallback uses
@huggingface/transformers@^4; it no longer requiressharp, which previously blocked installs on macOS ARM64 (issue #97, fixed in v2.17.0). - Prefix support for Nomic models is not yet implemented; users on
nomic-embed-text-v1.5currently get raw-text embeddings (issue #98). - Backend fallback is transparent: if USearch fails to load, the exact-scan backend takes over with no configuration change required. Source: src/services/vector-backends/backend-factory.ts
Together, these files implement the "recall" half of opencode-mem: turning memories into vectors, storing them, and surfacing the most relevant ones for the next prompt.
Source: https://github.com/tickernelz/opencode-mem / Human Manual
Auto-Capture Pipeline
Related topics: User Profile Learning System, AI Integration, Web UI, and Operational Concerns
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: User Profile Learning System, AI Integration, Web UI, and Operational Concerns
Auto-Capture Pipeline
The Auto-Capture Pipeline is the subsystem of opencode-mem responsible for observing OpenCode sessions, detecting conversational moments worth remembering, and persisting them as structured memories without requiring explicit user invocation. It runs as part of the plugin lifecycle, hooks into idle and prompt events, and feeds the downstream storage, deduplication, and cleanup layers.
Purpose and Scope
Auto-capture exists so that the memory layer can grow passively as the user works in OpenCode. Rather than asking the user to manually tag or save insights, the plugin watches the conversation stream and decides which messages carry durable knowledge — preferences, decisions, codebase facts, or workflow conventions — and routes those into long-term storage. Source: src/services/auto-capture.ts:1-40.
The pipeline's scope is bounded by three concerns:
- Trigger surface — events surfaced by the OpenCode SDK (
session.idle, prompt boundaries, and abort signals). - Quality control — deduplication, privacy filtering, and language detection run before persistence.
- Backpressure — a captured-state lock prevents duplicate ingestion of the same conversational turn. Source: src/services/auto-capture.ts:60-110.
Pipeline Stages
The capture flow is a fixed pipeline of seven stages. Each stage is implemented in its own service so failures stay isolated.
| Stage | Service | Responsibility |
|---|---|---|
| 1. Event observation | auto-capture.ts | Subscribes to SDK lifecycle events and debounces idle signals |
| 2. Session creation | auto-capture.ts | Allocates a short-lived SDK session for structured extraction |
| 3. Prompt analysis | user-prompt-manager.ts | Feeds captured turns into the configured memory provider |
| 4. Privacy gating | privacy.ts | Strips secrets, PII, and blocked patterns before storage |
| 5. Deduplication | deduplication-service.ts | Compares embeddings to reject near-duplicates |
| 6. Language detection | language-detector.ts | Tags the memory with locale metadata for later retrieval |
| 7. Cleanup handoff | cleanup-service.ts | Marks obsolete or stale records for pruning |
Source: src/services/auto-capture.ts:40-180, src/services/user-prompt/user-prompt-manager.ts:1-80, src/services/deduplication-service.ts:1-60.
The handoff between stages is event-driven rather than sequential: each stage resolves a promise that the next stage awaits, allowing the pipeline to short-circuit cleanly when a turn is rejected by privacy or dedup.
flowchart LR
A[OpenCode SDK events] --> B[auto-capture.ts]
B --> C[user-prompt-manager.ts]
C --> D[privacy.ts]
D --> E[deduplication-service.ts]
E --> F[language-detector.ts]
F --> G[(SQLite memory store)]
G --> H[cleanup-service.ts]Concurrency and Failure Handling
Auto-capture runs on every idle event, so race conditions are a first-class concern. The service maintains an in-process captured state machine with three values — 0 (idle), 1 (capturing), 2 (captured-and-released) — keyed on the prompt id. If an abort arrives mid-capture, the state must transition back to 0; otherwise subsequent prompts can become stuck. Source: src/services/auto-capture.ts:120-200.
Silent failure is the worst-case mode for an auto-capture system: the user never sees an error, and memory simply does not grow. Recent releases have closed two specific silent paths:
- v2.17.1 released the
captured=2lock on abort so a failed prompt does not block later ones. Source: src/services/auto-capture.ts:180-220. - v2.17.3 surfaced silent failures from the auto-capture path with a structured error log instead of swallowing them. Source: src/services/auto-capture.ts:90-130.
Both fixes are referenced from the release notes for v2.17.1 and v2.17.3.
Configuration and Known Limitations
Auto-capture is governed by opencode-mem.jsonc. Relevant keys include:
memoryProvider— the model used for structured extraction (e.g.openai-chat, an Anthropic model, or the OpenCode Go provider as documented in issue #130).memoryModel— the specific model id passed to that provider.autoCapture— boolean toggle that enables or disables the entire pipeline.
Source: src/services/auto-capture.ts:30-70.
Community-reported limitations worth knowing before relying on auto-capture:
- SDK drift — older
@opencode-ai/sdkversions (notably v1.14.48) did not exposesession.create, causingauto-captureto fail withsession.create returned no session id. Reported in issue #110. - No-port sessions — when OpenCode is launched without
--port,ctx.serverUrlis unreachable and auto-capture cannot create the structured-output session. Reported in issue #145. - Fresh sessions only — auto-capture does not retroactively index pre-existing OpenCode sessions; users with large histories must use a backfill script. Discussed in issue #129.
- Native binaries — capture relies on
sharpandnode:sqlite; the macOS ARM64 build ofsharpwas missing until the Hugging Face transformers migration in v2.17.0 and Windows shims were completed in v2.17.2.
Related Modules
The pipeline does not exist in isolation. The deduplication layer (src/services/deduplication-service.ts) shares embedding infrastructure with retrieval, the privacy gate (src/services/privacy.ts) is the same module invoked on manual memory writes, and cleanup (src/services/cleanup-service.ts) prunes both auto-captured and manually stored memories. Language tags assigned by src/services/language-detector.ts feed downstream filters and were extended with Arabic localization in v2.15.0.
Together these services form the passive ingestion half of opencode-mem, complementing the explicit /remember and backfill pathways documented elsewhere in the wiki.
Source: https://github.com/tickernelz/opencode-mem / Human Manual
User Profile Learning System
Related topics: Auto-Capture Pipeline, AI Integration, Web UI, and Operational Concerns
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Auto-Capture Pipeline, AI Integration, Web UI, and Operational Concerns
User Profile Learning System
The User Profile Learning System is the subsystem inside opencode-mem that turns an opaque stream of OpenCode user prompts into a structured, queryable User Profile. The profile captures the user's coding style, tool and language preferences, expertise signals, and other recurring habits, and is then fed back into OpenCode as context so that future model calls reflect what the plugin has learned.
Source: src/services/user-profile/user-profile-manager.ts:1-1 Source: src/services/user-memory-learning.ts:1-1
Purpose and Scope
The system exists to close the loop between raw conversation history and personalized behavior. Rather than treating every prompt as stateless input, the plugin samples recent user messages, asks the configured memory model to extract structured profile items, validates the result against a fixed schema, persists it to SQLite, and renders a compact context snippet for downstream prompts.
Per issue #115, profile items are organized into categories such as *preferences* (code style, tooling, language) and carry a per-item satisfaction score intended to reflect how well-attested each item is.
Source: src/services/user-profile/types.ts:1-1
Architecture
The feature is decomposed across six files that separate I/O, orchestration, validation, and presentation:
| File | Role |
|---|---|
user-profile-manager.ts | Owns the profile lifecycle, SQLite I/O, and confidence bookkeeping |
profile-context.ts | Renders the stored profile into a prompt-injectable string |
profile-utils.ts | Pure helpers for merging, scoring, normalizing, and (intended) decaying items |
types.ts | Shared TypeScript shapes for UserProfileItem, ProfileSection, etc. |
user-memory-learning.ts | Orchestrator that decides *when* to run an extraction pass |
ai/validators/user-profile-validator.ts | Validates AI output against the profile schema before persistence |
Source: src/services/user-profile/profile-context.ts:1-1 Source: src/services/user-profile/profile-utils.ts:1-1 Source: src/services/ai/validators/user-profile-validator.ts:1-1
Extraction Workflow
The orchestrator (user-memory-learning.ts) is event-driven. On triggers such as session idle, accumulated message count, or a time interval, it gathers a slice of recent user prompts, calls the configured memoryProvider / memoryModel, and pipes the result through user-profile-validator.ts. Only validated payloads are forwarded to user-profile-manager.ts, which performs the upsert and updates the satisfaction score for matching items.
flowchart LR A[OpenCode user prompts] --> B[user-memory-learning.ts] B --> C[memoryProvider call] C --> D[user-profile-validator.ts] D --> E[user-profile-manager.ts] E --> F[(SQLite profile store)] F --> G[profile-context.ts] G --> H[Injected into future prompts]
Source: src/services/user-memory-learning.ts:1-1 Source: src/services/ai/validators/user-profile-validator.ts:1-1 Source: src/services/user-profile/user-profile-manager.ts:1-1 Source: src/services/user-profile/profile-context.ts:1-1
Configuration
The learning system reads its provider and trigger thresholds from opencode-mem.jsonc:
memoryProvider— provider id, e.g.anthropic,openai-chat, or a Zen Go model id (see issue #130).memoryModel— model id used for extraction.- Trigger settings — interval or count thresholds that wake the orchestrator.
Source: src/services/user-memory-learning.ts:1-1
Known Limitations
Because the orchestrator is the only path that updates the profile, anything that prevents it from running freezes learning for the whole session.
- Confidence does not decay (issue #115): items saturate at 100% and never lose weight, even after contradictory evidence. This implies the decay branch expected in
profile-utils.tsis either gated behind an unset flag or never invoked by the orchestrator. - Auto-capture silently fails when the OpenCode SDK version does not expose
session.create(issue #110), so the validator never receives new data. - Unreachable
serverUrlin no-port OpenCode sessions (issue #145) prevents the orchestrator from contacting the memory provider entirely.
Source: src/services/user-profile/profile-utils.ts:1-1 Source: src/services/user-memory-learning.ts:1-1
See Also
Source: https://github.com/tickernelz/opencode-mem / Human Manual
AI Integration, Web UI, and Operational Concerns
Related topics: System Architecture, Configuration Reference, Vector Search and Embedding Models
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: System Architecture, Configuration Reference, Vector Search and Embedding Models
AI Integration, Web UI, and Operational Concerns
AI Provider Abstraction
The plugin ships a pluggable AI layer that lets operators point memory generation, summarization, and embedding work at any compatible model. A BaseProvider defines the contract every backend must satisfy: complete(prompt), embed(texts), health probing, and capability flags (supportsStructuredOutput, supportsEmbeddings). Concrete subclasses implement the wire protocol for each vendor. Source: src/services/ai/providers/base-provider.ts:1-120.
The AiProviderFactory resolves the configured memoryProvider and embeddingProvider strings from opencode-mem.jsonc into instantiated providers. It validates that the chosen model advertises the capability required by the calling subsystem (for example, that the memory provider supports structured JSON output before auto-capture asks it to produce observation records). When validation fails, it throws a descriptive error so misconfiguration surfaces at startup instead of at idle time. Source: src/services/ai/ai-provider-factory.ts:40-160.
Three production adapters are bundled:
- Anthropic Messages — calls
/v1/messageswith system instructions, retries on 529 overload, and parses Claude-style tool/JSON output. Source: src/services/ai/providers/anthropic-messages.ts:60-210. - OpenAI Chat Completions — drives any OpenAI-compatible endpoint (
/chat/completions) including Azure, Groq, Together, and the OpenCode Zengomodels referenced in issue #130. Source: src/services/ai/providers/openai-chat-completion.ts:1-180. - OpenAI Responses — uses the newer
/v1/responsesendpoint for providers that have moved off the chat API. Source: src/services/ai/providers/openai-responses.ts:1-150.
A fourth adapter, OpencodeProvider, is special: it does not call an external HTTP service. Instead it round-trips through the host OpenCode server's session.create + session.message APIs, allowing the plugin to reuse whatever provider the user already configured inside OpenCode (Claude, GPT, Copilot, Zen, etc.). Source: src/services/ai/opencode-provider.ts:30-140.
Auto-Capture Pipeline and SDK Coupling
Auto-capture listens for session.idle events from the OpenCode plugin hook and, when triggered, asks the AI provider to extract observations from the just-finished prompt/response pair. The capture flow is:
- Receive idle event with
ctx.directoryandctx.serverUrl. - If
serverUrlis absent (e.g. OpenCode launched without--port), fall back to a local probe or skip capture with a warning. - Create a transient session via
opencode.session.create({ ... }). - Send the prompt to the provider, asking for structured observations.
- Persist observations through the SQLite memory store and refresh the user-profile embeddings.
Source: src/services/capture/auto-capture.ts:80-260.
This pipeline is fragile when the host SDK and the plugin fall out of step. Issue #110 documents that SDK v1.14.48 removed session.create, causing auto-capture to log session.create returned no session id on every idle event. Issue #145 compounds the problem: even when the method exists, ctx.serverUrl may be unreachable because OpenCode was started without an explicit --port. The captured=2 lock introduced in v2.17.1 and refined through v2.17.3 (PR #136) prevents the system from leaving prompts stuck when an abort fires mid-capture. Source: src/services/capture/auto-capture.ts:300-380.
Web UI and HTTP API
The bundled web layer is a Bun-first HTTP server with Node fallbacks for environments without Bun.serve. The entry point boots an HTTP listener, mounts static assets for the SPA, and registers the JSON API routes under /api. Source: src/web/server.ts:1-140.
The API surface exposed to the UI (and to external tooling such as the backfill scripts in issue #129) includes:
| Endpoint | Method | Purpose |
|---|---|---|
/api/memories | GET | List memories, honoring user/project scope filters. |
/api/memories | POST | Manually inject a memory. |
/api/search | GET | Hybrid keyword + vector search. |
/api/profile | GET/PUT | Inspect or edit the user-profile confidence map. |
/api/health | GET | Provider and database liveness probe. |
The /api/memories listing fix in v2.16.0 (PR #123) ensures user-scope memories are visible alongside project-scope ones, which previously caused user memories to silently disappear. Memory-type badges rendered by the web UI were hardened against XSS in v2.17.4 (PR #137). Source: src/web/routes/api.ts:40-260.
Operational Concerns and Known Footguns
Several recurring operational issues surface in the community:
- Confidence decay absent (#115). The user-profile map records a satisfaction ratio per preference but never decrements stale entries. Users on v2.14.2 see flat 100% values because the decay job is not scheduled. Source: src/config/schema.ts:120-180.
- macOS ARM64 sharp load failure (#97, #94). The plugin depends on
sharpfor image-to-text observation capture. v2.17.0 (PR #127) migrates embeddings to@huggingface/transformers@^4, which removes the sharp dependency on Apple Silicon. Older installs still fail at plugin load withCannot find module sharp-darwin-arm64v8.node. - Windows WSL bus error (#149). Reported when
rtkis layered on top of opencode-mem inside WSL2; reproduction is sensitive to CPU feature flags. No plugin-side fix is shipped as of v2.17.4. - No-port OpenCode sessions (#145). When
ctx.serverUrlis empty the auto-capture path must detect the condition and degrade to a no-op rather than throwing. - Backfill gap (#129). Capture begins from fresh sessions only; community-contributed scripts (
bootstrap-memories.ts,rebuild-embeddings.ts) reconstruct history from the OpenCode session JSONL store. - Embedding prefix tuning (#98). Nomic
nomic-embed-text-v1.5benefits fromsearch_document:/search_query:prefixes; the plugin currently sends raw text. A config opt-in is the proposed remedy.
Operators should pin the host OpenCode SDK to a version known to retain session.create, set memoryProvider explicitly even when relying on the OpenCode adapter, and treat the confidence map as advisory until a decay scheduler lands.
Source: https://github.com/tickernelz/opencode-mem / Human Manual
Doramagic Pitfall Log
Source-linked risks stay visible on the manual page so the preview does not read like a recommendation.
May increase setup, validation, or first-run risk for the user.
May increase setup, validation, or first-run risk for the user.
May increase setup, validation, or first-run risk for the user.
May increase setup, validation, or first-run risk for the user.
Doramagic Pitfall Log
Found 12 structured pitfall item(s), including 1 high/blocking item(s). Top priority: Security or permission risk - Security or permission risk requires verification.
1. Security or permission risk: Security or permission risk requires verification
- Severity: high
- Finding: Project evidence flags a security or permission risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: community_evidence:github | https://github.com/tickernelz/opencode-mem/issues/110
2. Installation risk: Installation risk requires verification
- Severity: medium
- Finding: Project evidence flags a installation risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: community_evidence:github | https://github.com/tickernelz/opencode-mem/issues/115
3. Configuration risk: Configuration risk requires verification
- Severity: medium
- Finding: Project evidence flags a configuration risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: capability.host_targets | https://github.com/tickernelz/opencode-mem
4. Configuration risk: Configuration risk requires verification
- Severity: medium
- Finding: Project evidence flags a configuration risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: community_evidence:github | https://github.com/tickernelz/opencode-mem/issues/145
5. Capability evidence risk: Capability evidence risk requires verification
- Severity: medium
- Finding: README/documentation is current enough for a first validation pass.
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: capability.assumptions | https://github.com/tickernelz/opencode-mem
6. Runtime risk: Runtime risk requires verification
- Severity: medium
- Finding: Project evidence flags a runtime risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: community_evidence:github | https://github.com/tickernelz/opencode-mem/issues/149
7. Runtime risk: Runtime risk requires verification
- Severity: medium
- Finding: Project evidence flags a runtime risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: packet_text.keyword_scan | https://github.com/tickernelz/opencode-mem
8. Maintenance risk: Maintenance risk requires verification
- Severity: medium
- Finding: Project evidence flags a maintenance risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: evidence.maintainer_signals | https://github.com/tickernelz/opencode-mem
9. Security or permission risk: Security or permission risk requires verification
- Severity: medium
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: downstream_validation.risk_items | https://github.com/tickernelz/opencode-mem
10. Security or permission risk: Security or permission risk requires verification
- Severity: medium
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: risks.scoring_risks | https://github.com/tickernelz/opencode-mem
11. Maintenance risk: Maintenance risk requires verification
- Severity: low
- Finding: issue_or_pr_quality=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: evidence.maintainer_signals | https://github.com/tickernelz/opencode-mem
12. Maintenance risk: Maintenance risk requires verification
- Severity: low
- Finding: release_recency=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Recommended check: Reproduce the official install and quickstart path in an isolated environment.
- Evidence: evidence.maintainer_signals | https://github.com/tickernelz/opencode-mem
Source: Doramagic discovery, validation, and Project Pack records
Community Discussion Evidence
These external discussion links are review inputs, not standalone proof that the project is production-ready.
Count of project-level external discussion links exposed on this manual page.
Open the linked issues or discussions before treating the pack as ready for your environment.
Community Discussion Evidence
Doramagic exposes project-level community discussion separately from official documentation. Review these links before using opencode-mem with real data or production workflows.
- [[Maintainer Wanted] New Leadership Needed for opencode-mem](https://github.com/tickernelz/opencode-mem/issues/79) - github / github_issue
- Why don't user profile preferences decay? I currently have 100% satisfac - github / github_issue
- Community source 3 - github / github_issue
- Auto-capture uses unreachable ctx.serverUrl in no-port OpenCode sessions - github / github_issue
- Auto-capture broken: SDK v1.14.48 has no session.create method - github / github_issue
- Using Opencode Go as a provider - github / github_issue
- v2.17.4 - github / github_release
- v2.17.3 - github / github_release
- v2.17.2 - github / github_release
- v2.17.1 - github / github_release
- v2.17.0 - github / github_release
- v2.16.0 - github / github_release
Source: Project Pack community evidence and pitfall evidence