OpenCode Brain

OpenCode Brain

MCP server that gives OpenCode a persistent, graph-based memory with semantic search, iterative retrieval, and session-aware knowledge management. It supports capturing fleeting and permanent notes, distilling insights, tracking mistakes and fixes, and auto-archiving stale memories.

Category
Visit Server

README

OpenCode Brain — Tier 3 Persistent Memory System

A local A-MEM–style agentic memory system for OpenCode with noise filtering, selective forgetting, and iterative retrieval. Built on your existing stack.

Stack:

  • Qdrant — vector store (you already have this)
  • sentence-transformers/all-MiniLM-L6-v2 — 384-dim embeddings (you already have this)
  • NetworkX — knowledge graph, persisted to JSON on disk
  • Qwen3-4B / Qwen3-30B — routed note enrichment, link decisions, memory evolution
  • Obsidian Local REST API — human-readable vault mirror (optional)
  • FastMCP / MCP — exposes everything as an MCP server to OpenCode

Qdrant client compatibility: OpenCode Brain supports both legacy search(...) and modern query_points(...) APIs via an internal adapter.


Architecture

OpenCode ──MCP──► mcp_server.py (18 tools)
                        │
        ┌───────────────┼──────────────────┐
        ▼               ▼                  ▼
    Qdrant          NetworkX           Obsidian
 (vectors +       (link graph,        (markdown
  payloads,        typed edges,        mirror,
  access_count,    JSON on disk)       optional)
  last_accessed)
        │               │
        └───────┬────────┘
                ▼
      Qwen3 router
    (note construction,
     link decisions,
     memory evolution,
     context distillation)

Three Core Improvements over Basic A-MEM

Improvement Module Research Basis
Noise filtering via distillation brain/distiller.py MEM1 (Zhou et al., 2025): agents that discard irrelevant info outperform by 3.5x
Selective forgetting / decay brain/decay.py SimpleMem (Liu et al., 2026): selective retention achieves 26.4% F1 improvement
Iterative retrieval brain/deep_search.py Structural Memory (Zeng et al., 2024): iterative retrieval outperforms single-step across all benchmarks

Memory Pipeline

add_memory (permanent note)

  1. Note Construction — routed LLM generates title, context, keywords, tags
  2. Embedding — all-MiniLM-L6-v2 encodes content + context → 384-dim vector
  3. Upsert — stored in Qdrant with access_count=0, last_accessed=""
  4. Link Generation — top-K semantic candidates fetched; routed LLM decides which to link
  5. Memory Evolution — for high-similarity links, hard model re-generates old notes' context; old vectors updated. Runs in a background thread so add_memory returns immediately.
  6. Graph Update — NetworkX edges added with typed relations; mutations are batched and flushed once, not per-edge
  7. Obsidian Sync — markdown written to vault (best-effort, non-blocking)

add_memory (fleeting note)

Steps 1–3 only. Link generation and memory evolution are skipped — fleeting notes are cheap quick captures. Hard LLM calls happen at distillation time, not capture time. Fleeting notes are excluded from all search results by default.

distill_to_permanent

Runs the full permanent pipeline on a fleeting note: re-enrichment → re-embedding → link generation → memory evolution → Obsidian sync. Promotes memory_type to "permanent".

deep_search (iterative retrieval)

Hop 0: embed(query) → search Qdrant → [A, B, C]
       stamp_access(A, B, C)  ← decay tracking
Hop 1: embed(query + titles of A,B,C) → search Qdrant → [D, E, F]
+ graph.get_links(A,B,C) → pre-fetch neighbours
stamp_access(neighbours) ← graph-expanded notes are also tracked for decay
Hop 2: embed(query + titles of D,E,F) → search Qdrant → [G, H]
Final: union of all hops, ranked by best score seen across hops

Decay (selective forgetting)

Every search_notes call stamps last_accessed and increments access_count on returned notes. Graph-expanded neighbors surfaced by deep_search are also stamped (BUG-4 fix — previously only direct vector-search hits were tracked).

archive_stale_notes soft-archives notes that have:

  • Not been accessed in > 45 days, AND
  • Fewer than 2 lifetime accesses, AND
  • decay_score < 0.3 (recency 60% + access importance 40%)

archive_stale_notes paginates through all notes regardless of vault size (pages through the full collection — no hidden cap).

Archived notes (memory_type="archived") vanish from all searches but are never deleted. restore_note(zk_id) reverses archival instantly.


Payload Schema (Qdrant)

Every point stored in Qdrant has these fields:

Field Type Description
zk_id str "ZK-<8-char-hex>" — unique note ID
title str Single atomic claim (LLM-generated)
content str Raw knowledge content
context str Why this matters — LLM-generated, evolves over time
keywords list[str] 5–10 searchable terms (LLM-generated)
tags list[str] 2–5 category tags
links list[str] zk_ids of linked notes (bidirectional)
memory_type str fleeting | permanent | archived
note_kind str mistake | fix | pattern | research | decision | general | open_thread | session_summary | project_context
pinned bool Protect high-value notes from automatic archival
project str rag-chatbot | resume-screener | nano-r1 | general
created str ISO-8601 timestamp
updated str ISO-8601 timestamp
access_count int Lifetime search retrieval count (decay tracking)
last_accessed str ISO-8601 timestamp of last search hit (decay tracking)

MCP Tools Reference (18 tools)

Tool When to Use
open_session(project) Preferred one-call session bootstrap; wraps get_session_context plus get_inbox
get_session_context(project) Canonical session-start fallback; loads full project state
get_inbox(project) Canonical second fallback call; surfaces fleeting notes for distillation
distill_to_permanent(zk_id) Promote a fleeting note to permanent knowledge
deep_search(query, project, hops, include_contextual=false) Complex questions — multi-hop iterative retrieval
search_memory(query, top_k, project, include_contextual=false) Simple direct lookups only
get_related(zk_id, depth) Follow graph links from a specific note
add_memory(content, project, memory_type, note_kind, ...) Store new knowledge
update_memory(zk_id, new_content) Correct or extend an existing note
archive_stale_notes(dry_run) Soft-archive unused notes (weekly maintenance)
close_session(project, objective, completed, blockers, next_steps, key_files=None) Preferred one-call handoff capture; writes session_summary plus active open_thread
brain_stats() Health check — counts by type, graph size, Qdrant status
record_failure(content, project, failure_signature) Capture a fresh failure as a fleeting mistake note
record_resolution(failure_zk_id, fix_content, project) Capture a validated fix linked to prior failure
preflight_check(task, project, top_k) Retrieve similar past mistakes/fixes before coding
restore_note(zk_id) Restore one archived note to permanent
decay_report(limit) Inspect lowest-decay notes likely to go stale next
consolidate_clusters(project, dry_run, selected_cluster_ids, ...) Manual cluster consolidation with dry-run and selected apply

Session Ergonomics

Use open_session(project) at the start of coding-agent work when available. It preserves the canonical get_session_context(project) and get_inbox(project) payloads while reducing session startup to one call.

Use close_session(...) at handoff. It writes one session_summary, writes one active open_thread, and archives older open_thread notes only after the new handoff is durable.

When to use deep_search vs search_memory

Simple direct lookup    →  search_memory  ("what port does Qdrant use?")
Complex / causal        →  deep_search    ("why does the CrossEncoder slow things down?")
Session start context   →  open_session   (preferred; canonical fallback is get_session_context + get_inbox)
"How did we solve X?"   →  deep_search    (may span multiple sessions and notes)
After finding a note    →  get_related    (follow the knowledge graph)

File Structure

opencode-brain/
├── brain/
│   ├── embedder.py       # sentence-transformers wrapper (singleton, normalised)
│   ├── vector_store.py   # all Qdrant ops: upsert, search, scroll, update, decay tracking
│   ├── graph.py          # NetworkX DiGraph: typed edges, JSON persistence
│   ├── note_builder.py   # routed LLM prompts: construct_note, decide_link, evolve_context
│   ├── model_router.py   # centralised LLM routing: fast/hard model selection
│   ├── memory_evolution.py  # A-MEM core: link generation + retroactive context updates
│   ├── obsidian_sync.py  # vault write-back via Local REST API (best-effort)
│   ├── distiller.py      # noise filtering: inbox management + fleeting→permanent promotion
│   ├── decay.py          # selective forgetting: decay scores + stale note archival
│   ├── deep_search.py    # iterative retrieval: search → graph expand → search again
│   ├── consolidation.py  # cluster detection and merge (manual-only, dry-run first)
│   ├── session.py        # session context, startup wrapper support, close handoff helpers
│   └── mistake_memory.py # mistake-aware helpers: build_failure_note, rank_preflight_results
├── mcp_server.py     # FastMCP entry point, all 18 tools defined here
├── config.py         # all configuration, all overridable via .env
├── requirements.txt
├── .env.example      # copy to .env and fill in HUGGINGFACE_API_KEY at minimum
└── AGENTS.md         # brain protocol — paste into your OpenCode AGENTS.md

Setup

1. Prerequisites

Qdrant must be running locally:

docker run -p 6333:6333 qdrant/qdrant

2. Install dependencies

cd opencode-brain
pip install -r requirements.txt --break-system-packages

Key dependencies:

  • qdrant-client>=1.13,<2.0 — constrained for predictable compatibility; supports both search() and query_points() APIs
  • huggingface-hub>=0.23.0 — HuggingFace Inference API for Qwen routing
  • sentence-transformers>=3.0.0 — local embedding model

3. Configure

cp .env.example .env
# Edit .env — set HUGGINGFACE_API_KEY at minimum
# Everything else has working defaults

4. Test the server runs

python mcp_server.py
# Should print:
#   [embedder] Loading sentence-transformers/all-MiniLM-L6-v2 …
#   [vector_store] Created collection 'opencode_brain'   (first run)
#   [graph] Loaded 0 nodes, 0 edges
# Then waits for stdio MCP input — Ctrl+C to exit

5. Add to OpenCode config

Edit ~/.config/opencode/config.json:

{
  "mcp": {
    "opencode-brain": {
      "command": [
"python",
            "C:\\Projects\\Brainn\\mcp_server.py"
      ],
      "type": "local"
    }
  }
}

6. (Optional) Enable Obsidian vault sync

  1. Obsidian → Settings → Community Plugins → search "Local REST API" → Install → Enable
  2. Copy the API key from the plugin settings page
  3. Add to .env:
    OBSIDIAN_API_KEY=your-key-here
    OBSIDIAN_VAULT_SUBFOLDER=brain
    

Notes will be written to <your-vault>/brain/zk/ZK-XXXXXXXX.md with full YAML frontmatter.

7. Add the brain protocol to your AGENTS.md

Copy the contents of AGENTS.md (in this repo) into your existing OpenCode AGENTS.md. The protocol defines exactly when each tool should be called during a session.


Seed Your Brain (Recommended First Step)

Run once to pre-load your existing project knowledge:

import sys
sys.path.insert(0, ".")
from brain import vector_store
from mcp_server import add_memory

vector_store.ensure_collection()

seeds = [
    {
        "content": (
            "RAG Chatbot stack: Qdrant hybrid search (dense + BM25) + CrossEncoder reranking "
            "+ LangGraph with SqliteSaver for persistent memory + Qwen3 routed models + "
            "all-mpnet-base-v2 embeddings + Chainlit UI. "
            "Location: C:/Projects/agentic-rag/. "
            "Known fix: QdrantClient shutdown ResourceWarning → atexit.register(client.close)."
        ),
        "project": "rag-chatbot",
        "memory_type": "permanent",
    },
    {
        "content": (
            "AI Resume Screener deployed to HuggingFace Spaces (Adityaladi/Ai_resume_screener). "
            "Stack: TF-IDF + Naive Bayes/KNN/SVC, 88-92% accuracy across 25 categories. "
            "Flask REST API + Streamlit frontend + Docker + GitHub Actions CI/CD. "
            "Known issue: IT category bias — fix with class_weight='balanced' or SMOTE."
        ),
        "project": "resume-screener",
        "memory_type": "permanent",
    },
    {
        "content": (
            "Nano-R1: QLoRA/GRPO fine-tune of Qwen2.5-3B-Instruct replicating DeepSeek-R1 "
            "chain-of-thought reasoning. Trained via Unsloth/TRL targeting GSM8K math. "
            "Published at HuggingFace: Adityaladi/Nano-R1. "
            "Remaining work: GSM8K evaluation via lm-evaluation-harness to close the metric gap."
        ),
        "project": "nano-r1",
        "memory_type": "permanent",
    },
    {
        "content": (
            "OpenCode MCP config pattern: 'type: remote' silently fails for remote MCP servers. "
            "Correct pattern: type='local', command='npx', args=['mcp-remote', '<url>']. "
            "Applies to Consensus, Exa, Context7, HuggingFace servers."
        ),
        "project": "general",
        "memory_type": "permanent",
    },
]

for seed in seeds:
    result = add_memory(
        seed["content"],
        project=seed["project"],
        memory_type=seed["memory_type"],
    )
    print(result)

Weekly Maintenance

# Check what is going stale (dry run — safe)
result = archive_stale_notes(dry_run=True)

# Apply if the list looks reasonable
result = archive_stale_notes(dry_run=False)

# Health check
print(brain_stats())

Schema Conformance and Migrations

Run these in order when upgrading or validating data integrity:

# 1) Lifecycle/semantics split migration (strict scope)
python tools/migrate_memory_schema.py
python tools/migrate_memory_schema.py --apply

# 2) note_kind enum conformance migration
python tools/migrate_note_kind_conformance.py
python tools/migrate_note_kind_conformance.py --apply

# 3) memory_type enum conformance for semantic-type drift (for example memory_type="research")
python tools/migrate_memory_type_conformance.py
python tools/migrate_memory_type_conformance.py --apply

# 4) CI-safe schema drift check (non-zero exit if invalid payloads exist)
python tools/check_schema_health.py

Backups are written before --apply runs:

  • backups/memory-pre-migration-<timestamp>.json
  • backups/notekind-pre-migration-<timestamp>.json
  • backups/memorytype-pre-migration-<timestamp>.json

Tunable Config Values (.env)

Variable Default Description
HUGGINGFACE_API_KEY — Required
FAST_MODEL_ID Qwen/Qwen3-4B-Instruct-2507 Fast default LLM for note construction and extraction
FAST_MODEL_PROVIDER featherless-ai Inference provider for the fast model (overridden to nscale in .env)
HARD_MODEL_ID Qwen/Qwen3-30B-A3B-Instruct-2507 Hard fallback model for synthesis and memory evolution
HARD_MODEL_PROVIDER featherless-ai Inference provider for the hard model
QDRANT_HOST localhost Qdrant server host
QDRANT_PORT 6333 Qdrant server port
QDRANT_COLLECTION opencode_brain Collection name
EMBEDDING_MODEL sentence-transformers/all-MiniLM-L6-v2 Embedding model
OBSIDIAN_API_KEY (empty — sync disabled) Obsidian Local REST API key
OBSIDIAN_HOST http://localhost:27123 Obsidian REST API URL
OBSIDIAN_VAULT_SUBFOLDER brain Subfolder in vault for notes
STALE_DAYS_THRESHOLD 45 Days before a note is stale
STALE_ACCESS_THRESHOLD 2 Min accesses to be immune from archival
GRAPH_PATH ~/.opencode-brain/graph.json Knowledge graph persistence path

Research Basis

Core Architecture

Three Core Improvements

Supporting Research

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured