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
访问服务器

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

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选