opencode-metis
v0.3.4
Published
Persistent memory system for OpenCode sessions
Readme
opencode-metis
Persistent memory system for OpenCode sessions. Captures observations, compresses them with AI, enables semantic search, and preserves context across compaction.
Features
- AI-compressed observations — Tool outputs are distilled into structured knowledge by Gemini, OpenRouter, or Anthropic
- Semantic search — Find relevant memories by meaning via ChromaDB vector embeddings, not just keywords
- Context injection — Relevant past observations are automatically injected at session start
- Compaction survival — Saves and restores context when OpenCode compacts messages
- Multi-session support — Run multiple OpenCode sessions concurrently with session-scoped data isolation
- Privacy protection —
<private>tag stripping and automatic secret detection (API keys, tokens, PEM keys) before storage - Crash recovery — Pending message queue with at-least-once delivery ensures no observations are lost; automatic token refresh on worker restart
- Quality checks — TDD enforcement and file-length warnings on every edit
- Tool redirection — Block or redirect specific tools via configuration
- Local-only — All data stays on your machine at
~/.config/opencode/memory/; only AI compression calls leave the machine
Prerequisites
| Tool | Version | Required | Purpose |
|------|---------|----------|---------|
| Bun | >= 1.0.0 | Yes | Runtime for the worker daemon, plugin, and build system |
| uv | latest | No | Runs chroma-mcp for semantic vector search (installed automatically by init) |
| API key | — | No | One of GEMINI_API_KEY, OPENROUTER_API_KEY, or ANTHROPIC_API_KEY for AI observation compression |
ChromaDB is managed automatically via chroma-mcp (a Model Context Protocol server launched with uvx). If uv is not installed, the system falls back to SQLite FTS5 keyword search — no semantic search, but everything else works.
Installation
Install globally:
bun add -g opencode-metisRun the setup command from your project root (or any directory):
opencode-metis initThis merges the required
pluginandmcpentries into~/.config/opencode/opencode.json, copies framework files (agents, commands, skills) into~/.config/opencode/, and checks for ChromaDB availability. Existing config is backed up before modification.(Optional) Set an AI provider for observation compression:
# Pick one: export GEMINI_API_KEY="your-key" export OPENROUTER_API_KEY="your-key" export ANTHROPIC_API_KEY="your-key"Without an API key, observations are stored with basic string truncation instead of AI compression.
CLI
| Command | Description |
|---------|-------------|
| opencode-metis init | Configure opencode.json and copy framework files |
| opencode-metis start | Start the memory worker and launch opencode |
| opencode-metis stop | Stop the memory worker |
| opencode-metis start-mcp | Start the MCP server (used by opencode via opencode.json config) |
Configuration
The memory system has its own optional config file at ~/.config/opencode/memory/settings.json (separate from OpenCode's config). All fields have sensible defaults:
Worker
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| workerPort | number | 41777 | HTTP port for the worker daemon |
| workerBind | string | "127.0.0.1" | Bind address for the worker |
Storage
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| chromaDbUrl | string | "http://localhost:8000" | ChromaDB server URL |
| chromaDbEnabled | boolean | true | Enable ChromaDB vector store |
AI Compression
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| aiProvider | string | null | null | AI provider: "gemini", "openrouter", or "anthropic". Set to null to disable AI compression. |
| aiModel | string | null | null | Override the default model for the selected provider |
| aiCompressionTimeoutMs | number | 15000 | Timeout per AI compression call |
| aiCompressionMaxRetries | number | 3 | Max retries for failed compressions |
| aiSkipTools | string[] | [] | Tool names to skip during observation capture |
Enabling AI Compression
AI compression transforms raw tool outputs into structured, searchable knowledge. To enable it:
Set the corresponding API key environment variable:
# Pick one provider: export GEMINI_API_KEY="your-key" # For Google Gemini export OPENROUTER_API_KEY="your-key" # For OpenRouter export ANTHROPIC_API_KEY="your-key" # For Anthropic ClaudeCreate or edit
~/.config/opencode/memory/settings.json:{ "aiProvider": "gemini" }Replace
"gemini"with"openrouter"or"anthropic"based on your chosen provider.(Optional) Override the default model:
{ "aiProvider": "anthropic", "aiModel": "claude-sonnet-4-20250514" }
Default Models
| Provider | Default Model | Environment Variable |
|----------|---------------|---------------------|
| gemini | gemini-2.0-flash | GEMINI_API_KEY |
| openrouter | anthropic/claude-haiku | OPENROUTER_API_KEY |
| anthropic | claude-haiku-4-5-20251001 | ANTHROPIC_API_KEY |
Without aiProvider set, observations are stored with basic string truncation instead of AI-powered structured extraction.
Privacy
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| secretDetectionEnabled | boolean | true | Detect and redact secrets before storage |
| secretPatterns | array | [] | Additional custom secret patterns { name, pattern } |
Retention & Context
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| recencyWindowDays | number | 90 | Days to boost recent results |
| retentionDays | number | 365 | Days before observations expire |
| contextTokenBudget | number | 2000 | Max tokens for context injection at session start |
Quality Checks
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| tddEnabled | boolean | true | Enforce test-file checks on edit |
| fileLengthWarn | number | 300 | Line count warning threshold |
| fileLengthCritical | number | 500 | Line count critical threshold |
| testFilePatterns | string[] | ["*.test.ts", "*.spec.ts", "*_test.go", "test_*.py"] | Glob patterns for test files |
| toolRedirectRules | array | [] | Rules to deny or redirect specific tools |
Architecture
The system has four components, each built as a separate bundle under dist/:
- CLI (
dist/cli.cjs) — Orchestrates init, start, and stop commands - Plugin (
dist/plugin.cjs) — Hooks into OpenCode's lifecycle events to capture observations, enforce quality checks, and inject context - Worker (
dist/worker.cjs) — Bun HTTP daemon with bearer token auth that stores observations in SQLite (WAL mode), manages ChromaDB via chroma-mcp, runs AI compression, serves search queries, and broadcasts session-scoped SSE events - MCP Server (
dist/mcp-server.cjs) — Exposes memory tools to the AI via the Model Context Protocol
Data Flow
OpenCode Session
│
├─ session.created ──────► Worker /api/context/inject ──► SQLite + ChromaDB query ──► context injected
│
├─ tool.execute.after ───► Worker /api/memory/save ────► privacy strip ──► SQLite write (session-scoped)
│ │
│ └──► AI compression queue ──► Gemini/OpenRouter/Anthropic
│ │
│ └──► update observation with structured data
│
├─ session.idle ─────────► Worker /api/memory/save ────► session summary stored
│
├─ SSE connection ───────► Worker /api/events?sessionId= ► session-scoped event stream
│
└─ session.compacted ───► Worker /api/context/inject ──► context restored after compactionSecurity
- Bearer token auth — A cryptographically random token is generated per worker instance and stored in the PID file with
0o600permissions (owner read/write only). All non-health endpoints requireAuthorization: Bearer <token>. - Automatic token refresh — When the worker restarts, sessions automatically re-read the PID file and retry with the new token (single retry to prevent crash-loop storms).
- Privacy stripping —
<private>tags are removed at the hook layer before data leaves the plugin process. Secrets (AWS keys, GitHub tokens, API keys, PEM keys, JWTs) are detected via regex and redacted with[REDACTED]. - Secure API key transmission — Gemini API keys are sent via
x-goog-api-keyheader rather than URL query parameters to prevent exposure in logs and proxy traces. - Localhost binding — The worker binds to
127.0.0.1by default.
MCP Tools
Tools available to the AI through the MCP server:
| Tool | Description |
|------|-------------|
| search | Semantic or keyword search across observations. Accepts query, optional limit, type, and project filters. |
| timeline | Chronological context around an observation. Accepts anchor (ID, session ID, timestamp, or query) with depth_before/depth_after. |
| get_observations | Fetch full details for specific observation IDs. |
| save_memory | Save a new observation with text, optional title and project. |
| decisions | Search decision-type observations. Optional query and project filters. |
| changes | Get recent change-type observations. Optional project and limit. |
The plugin also registers memory_search and memory_save as custom tools directly in OpenCode.
Plugin Hooks
| Hook | Purpose |
|------|---------|
| session.created | Injects relevant memory context at session start |
| tool.execute.before | Enforces tool redirect rules (deny/redirect) before execution |
| tool.execute.after | Captures tool executions as observations (with AI compression) |
| session.idle | Saves session summaries when the session goes idle |
| experimental.session.compacting | Saves active plan and task state before compaction |
| session.compacted | Restores memory context after compaction |
| file.edited | Checks for missing test files and warns on file length |
Local Development
Setup
Build and link the plugin into OpenCode's config directory using GNU Stow:
bun install
bun run build
bun run link # stows into ~/.config/opencode/plugins/Then add the MCP server to ~/.config/opencode/opencode.json (no plugin entry needed — stow handles that):
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-metis"],
"mcp": {
"mem-search": {
"type": "local",
"command": ["opencode-metis", "start-mcp"]
}
}
}To remove the symlink:
bun run unlink # unstowCommands
bun test # Run tests
bun run build # Build all four bundles
bun run lint # Lint and format with Biome
bun run typecheck # TypeScript type checking
bun run dev # Watch mode for workerProject Structure
src/
cli/ CLI commands (init, start, stop, start-mcp) and worker lifecycle
mcp/ MCP server and worker proxy
plugin/ OpenCode plugin hooks, privacy filters, and custom tools
hooks/ Hook handlers (session-start, tool-after, session-idle, etc.)
privacy/ Secret detection, <private> tag stripping, privacy filter
tools/ Custom MCP tools registered in OpenCode
services/ Business logic layer
ai-compression/ Multi-provider AI agents (Gemini, OpenRouter, Anthropic), queue processor
shared/ Configuration, types, constants
storage/
sqlite/ Database, migrations, FTS5 indexing
vector/ ChromaDB connection, chroma-mcp manager, vector sync
worker/ HTTP server, router, routes, SSE broadcaster, auth middleware
test/ Integration and E2E testsLicense
MIT
