Skip to content
Conduit

mcp server

bdh-graph-harness

Graph-based retrieval for Obsidian vaults with Hebbian synaptic plasticity

albidev/bdh-graph-harnesscommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

This project does not publish an install method we can turn into a config. Follow the README instead.

README

synced Oct 26, 2026

BDH Graph Harness

A living memory system for Obsidian vaults and AI agents.

BDH turns Markdown notes into a queryable, self-maintaining knowledge graph. It combines explicit structure (wikilinks), semantic retrieval (embeddings + optional BM25), learned associations (Hebbian dynamic edges), and maintenance cycles that prune noise and consolidate useful structure over time.

This is not a RAG wrapper with a graph visualizer glued on. Retrieval, learning, ingestion, quality control, consolidation, and agent interfaces are separate but connected subsystems. The graph changes as the vault changes and as it is used.

⚠️ Experimental research software. APIs, configuration, and storage formats may change. The project independently explores ideas from the Dragon Hatchling paper (Kosowski et al., 2025); it is not an official implementation.

System at a glance

LayerWhat BDH does
IngestWatches Obsidian and optional read-only external Markdown sources; incrementally rebuilds graph, embeddings, and lexical indexes.
ConnectPreserves explicit wikilinks, resolves federated source IDs, and adds bounded phantom links for validated semantic proximity.
RetrieveCombines vector/BM25 seed selection, multi-query fusion, adaptive attention spread, and provenance-rich results.
LearnStrengthens co-activated notes with Hebbian plasticity; trusted learned-only edges can extend traversal beyond declared links.
MaintainScores node quality, marks dormant material, runs sleep-cycle downscaling/pruning, and refreshes structural relationships.
GrowExtracts genuinely useful concepts through filtered neurogenesis with provenance and semantic deduplication.
ExposeServes REST, WebSocket visualization, CLI, and MCP tools to humans and AI agents.

Knowledge lifecycle

Obsidian vault + federated Markdown sources
                  │
                  ▼
    watcher → source-aware graph + Chroma + BM25
                  │
                  ▼
query → hybrid seeds → attention traversal → cited context / response
                  │                      │
                  │                      └─ static + trusted dynamic Hebbian edges
                  ▼
Hebbian plasticity → neurogenesis (filtered) → incremental watcher update
                  │
                  ▼
sleep-cycle consolidation → downscale → prune → quality re-evaluation → phantom refresh

Core capabilities

  • Semantic + structural retrieval — Chroma embeddings, optional BM25/RRF, multi-query fusion, adaptive thresholds, and k-hop graph attention with per-note provenance.
  • Trusted dynamic Hebbian edges — repeated co-activation creates learned relations that can improve reachability without mutating the declared wikilink graph. Frequency, consolidation, and recency produce an explicit trust factor; dynamic-only results are shadow-instrumented without storing query text.
  • Federated, multi-vault knowledge — independent vault contexts prevent state or embedding contamination. Read-only external Markdown sources use source-aware IDs and can participate in cross-source links.
  • Continuous ingestion — a debounced, source-aware watcher handles vault edits and external-source updates incrementally instead of requiring manual full rebuilds.
  • Quality and consolidation — node quality detects dormant material; scheduled sleep cycles downscale, prune stale weak synapses, remove persistent dormant nodes, and refresh phantom relationships.
  • Controlled neurogenesis — LLM-generated concepts pass prompt constraints, deterministic blocklists, semantic deduplication, and source-provenance checks before becoming notes.
  • Agent-native interfaces — REST API, WebSocket event stream, CLI, and MCP server all use the same core retrieval and memory model.
  • Inspectable visualization — a WebGL force graph renders activation, edge families, learned strength, dormant state, and live updates.

Start safely

Use the standalone demo quickstart before connecting a real vault. It supplies fictional linked notes, isolated storage, local-only completion settings and an executable read-only smoke check. Hermes / Mission Control / Curate are optional, not prerequisites. Review operations/privacy and compatibility and the alpha release checklist; this remains an experimental alpha, not a production stability claim.

Documentation map

NeedRead
Why this architecture existsdocs/philosophy.md
Learned dynamic-edge contract, trust, telemetry, and evaluation limitsdocs/hebbian-dynamic-edges.md
MCP clients and transportsdocs/mcp-server.md
Real-time graph visualizationdocs/visualization.md
Testing and coverage policydocs/testing.md
Standalone demo and learning opt-indocs/quickstart.md
Backup/restore, privacy and troubleshootingdocs/operations.md
Tested versions and alpha limitsdocs/compatibility.md
Directed merge evidencedocs/directed-merge-evidence.md
Configurable behaviorbdh-config.yaml

Neurogenesis Signal Filtering

Neurogenesis creates new notes from concepts the LLM identifies in its response. Without filtering, this generates ~50% noise (model names, internal plumbing, generic process words). Three layers ensure signal-first neurogenesis:

  1. Prompt engineering — the system prompt explicitly instructs the LLM what to extract (algorithms, architectures, patterns, lessons) vs what to reject (model names, API providers, internal plumbing, generic words)
  2. Regex blocklist — deterministic post-LLM filter catches model names (glm-*, gemma*, mistral-*, etc.), BDH plumbing (graph-refresh, hebbian-update, etc.), generic process words, and too-short slugs
  3. Semantic dedup — ChromaDB cosine similarity (threshold 0.65) catches spelling variants and semantic duplicates that exact-string matching misses (e.g. sleepcycle-consolidation vs sleep-cycle-consolidation)

See bdh_graph_harness/neurogenesis/creator.py and dedupe.py for implementation.

Architecture

Obsidian Vault → Embed (Ollama) → ChromaDB + Graph
                                    ↓
Query → Vector Search → Attention Spread (max_hop=2)
                                    ↓
Hebbian Update (co-activation strengthening) → LLM Response (OpenAI-compatible)
                                    ↓
WebSocket → force-graph (WebGL) (nodes light up, synapses pulse)

Sleep Cycle (periodic):
  Synaptic Downscaling (×0.9) → Stale-Weak Pruning → Prune (< floor) → Quality Re-eval → Stale Removal

Package structure

bdh_graph_harness/
├── __main__.py              # CLI entry point (--serve, --mcp, positional query, --refresh-embeddings, --no-cache)
├── config.py                # Config loading, env var expansion, retry logic
├── vaults.py                # VaultConfig, VaultContext, VaultRegistry (multi-vault isolation)
├── mcp_server.py            # MCP server (FastMCP, stdio + HTTP transport)
├── graph/
│   ├── parser.py            # Frontmatter + wikilink parsing
│   ├── builder.py           # Legacy graph construction + incremental cache
│   ├── sources.py           # Vault/external Markdown source scanners
│   ├── federated.py         # Source-aware IDs + federated graph builder
│   └── cache.py             # Graph cache serialization
├── okf/
│   └── export.py            # OKF v0.2 validator + sanitized bundle exporter
├── retrieval/
│   ├── embeddings.py        # Ollama embedding client
│   ├── chroma_store.py      # ChromaDB vector store
│   ├── bm25.py              # BM25 lexical index (configurable; hybrid_search is currently true)
│   ├── hybrid.py            # Vector + BM25 fusion (configurable; hybrid_search is currently true)
│   └── attention.py         # Seed selection + k-hop spread + adaptive threshold
├── memory/
│   ├── hebbian.py           # Synaptic weight update + decay
│   ├── quality.py           # Node quality scoring + dormant pruning
│   ├── consolidation.py     # Sleep-cycle: downscaling + pruning + stale removal
│   └── state_store.py       # Persistent state (file-locked)
├── llm/
│   ├── providers.py         # LLM factory + payload builder
│   ├── ollama.py            # Ollama backend
│   ├── openrouter.py        # OpenRouter backend (OpenAI-compatible)
│   └── prompt.py            # System prompt + context formatting
├── neurogenesis/
│   ├── creator.py           # Concept extraction + note creation + noise filtering + source provenance IDs
│   └── dedupe.py            # Exact + semantic duplicate detection (ChromaDB cosine similarity)
├── api/
│   ├── server.py            # aiohttp app setup + WebSocket
│   ├── routes.py            # REST endpoints
│   ├── ws.py                # WebSocket handlers
│   └── watcher.py           # Source-aware polling watcher with debounce
└── visualization/
    └── templates/           # 3D force-graph (WebGL) real-time graph UI + controls + WebSocket

harness.py is a compatibility shim that re-exports from the package — tests use import harness.

Setup

  1. Ollama running locally with nomic-embed-text-v2-moe pulled (for embeddings)
  2. LLM provider — any OpenAI-compatible endpoint:
    • OpenRouter: set OPENROUTER_API_KEY env var (default config uses openrouter/free)
    • Ollama Cloud: set OLLAMA_API_KEY and point openrouter_url to https://ollama.com/v1/chat/completions
    • Local Ollama: switch llm_provider: ollama in config
    • Local oMLX: use llm_provider: omlx with the OpenAI-compatible server on http://127.0.0.1:8083/v1
    • Any other OpenAI-compatible API works — just set openrouter_url, openrouter_key, and llm_model
  3. Python 3.11+ with dependencies:
pip install -r requirements.txt
  1. Configure the vault path:
cp bdh-config.yaml bdh-config.local.yaml
# Edit vault_path/provider/storage in your copied config
# Pass --config bdh-config.local.yaml explicitly; automatic discovery does not select it

Usage

# Start server
python -m bdh_graph_harness --config bdh-config.local.yaml --serve

# Single query (CLI)
python -m bdh_graph_harness --config bdh-config.local.yaml "come funziona l'apprendimento Hebbian?"

# Skip graph cache (not a learned-memory reset)
python -m bdh_graph_harness --config bdh-config.local.yaml --no-cache --stats

# Refresh embeddings (different from graph rebuild)
python -m bdh_graph_harness --config bdh-config.local.yaml --refresh-embeddings

# Read-only source scan (no ChromaDB, embeddings, LLM, or writes)
python -m bdh_graph_harness --config bdh-config.local.yaml --scan-sources

OKF read compatibility

BDH can read the Open Knowledge Format (OKF) v0.2 document conventions without replacing its runtime graph. Enable the read adapter in bdh-config.local.yaml:

okf_mode: read

okf_mode is false by default. In read mode BDH parses typed YAML metadata, preserves unknown fields under node["okf"], resolves local Markdown links alongside Obsidian wikilinks, ignores external URLs as graph edges, and keeps index.md/log.md out of the neuron graph. Hebbian synapses, embeddings, consolidation state, and neurogenesis runtime metadata remain BDH-owned and are not converted into OKF links.

When okf_mode is enabled, the read-only OKF retrieval policy is enabled by default. It keeps the signals separate while ranking: status: draft/deprecated are demoted, verified documents receive a small bonus, explicit stale_after dates demote stale documents, and documents with source provenance (sources) receive a small evidence bonus. Legacy nodes without OKF metadata remain neutral. The applied components are returned in routing.okf_policy and each activation detail, without exposing raw source paths. Set okf_retrieval_policy_enabled: false to run an A/B comparison; the policy never mutates Hebbian state.

okf_mode: read
okf_retrieval_policy_enabled: true

The exporter and validator are separate from the vault read path. They write a new bundle and never rewrite the source vault:

from bdh_graph_harness import build_graph
from bdh_graph_harness.okf import export_okf_bundle, validate_okf_bundle

nodes, edges = build_graph("/path/to/vault", use_cache=False, okf_mode=True)
export_okf_bundle(nodes, edges, "/tmp/bdh-okf-bundle")
result = validate_okf_bundle("/tmp/bdh-okf-bundle")
assert result.valid, result.errors

The bundle contains typed concept documents plus conformant root index.md and log.md. Only OKF document metadata and structural links are exported; Hebbian/phantom edges, activation history, embeddings, and local filesystem paths are excluded or redacted. Broken links and unknown type values remain valid according to OKF v0.2's permissive consumer contract.

# Open visualization
open http://localhost:8643

# List configured vaults (multi-vault mode)
python -m bdh_graph_harness --config bdh-config.local.yaml --list-vaults

# Target a configured vault from the CLI
python -m bdh_graph_harness --config bdh-config.local.yaml --vault-id research --stats

Multi-vault API

Keep the legacy vault_path config for one vault, or use the vaults: list shown in bdh-config.yaml. Each entry is isolated: it has its own graph, Hebbian state, watcher, BM25 index, lock, and ChromaDB collection.

# Query one vault explicitly
curl -X POST http://localhost:8643/api/query \
  -H 'Content-Type: application/json' \
  -d '{"vault_id":"research","query":"How does retrieval work?","learn":false,"respond":false}'

# Read stats for one vault, or discover configured vaults
curl 'http://localhost:8643/api/stats?vault_id=research'
curl http://localhost:8643/api/vaults

vault_id is also accepted by MCP tools such as query(question="...", vault_id="research"). Omitting it selects default_vault (or the first configured vault).

Per-vault LLM routing

The top-level llm_provider, llm_model, llm_base_url, and llm_api_key values remain the global defaults. A vault can override them with a nested llm block without mutating or affecting any other vault:

vaults:
  - id: default
    name: Default Vault
    path: /path/to/vault
    # No llm block: inherits the global provider/model configuration.

Supported nested fields are provider, model, base_url, temperature, max_ctx, max_tokens, timeout, api_key, api_key_env, and local_only. Prefer api_key_env for cloud vaults; credentials are resolved from the process environment and are never required in the YAML. base_url maps to the native Ollama host for ollama and to the OpenAI-compatible base for ollama-cloud/openrouter. local_only: true gates completion routing: it accepts only the ollama or omlx providers and loopback endpoints (localhost, 127.0.0.1, or ::1). It does not gate the separate embedding URL, bridge rewrite models or optional integrations; review those independently.

The effective provider is resolved per request for REST, streaming, MCP, CLI, and neurogenesis paths. GET /api/stats?vault_id=... exposes the selected provider, model, transport, and endpoint for verification.

LLM failover

BDH can fail over through an ordered list of completion candidates when the current provider returns an HTTP error (including 429) or cannot be reached. Retries are exhausted for one candidate before the next candidate is tried. For example, keep cloud inference as the primary and use local oMLX as the offline fallback:

llm_provider: ollama-cloud
llm_model: deepseek-v4-flash:0731
llm_base_url: https://ollama.com/v1
llm_api_key: ${OLLAMA_API_KEY}
llm_fallbacks:
  - provider: omlx
    model: qwen3.8-27b-oq4e-mtp
    base_url: http://127.0.0.1:8083/v1

oMLX is local and OpenAI-compatible, so it receives no cloud Authorization header. The failover is implemented inside BDH; Hermes' separate global fallback_providers chain does not control BDH subprocess calls.

Multi-query retrieval

When multi_query_enabled: true in config, clients can send query_variants alongside the primary query. The server retrieves seed candidates for each variant, merges them via reciprocal-rank fusion (RRF) or weighted-max, then performs one canonical graph expansion for the primary query. It returns canonical notes with per-note provenance (matched_by, variant_hits). This lets callers explore a query from multiple angles (e.g. Italian + English rewrites, paraphrases, keyword decompositions) in a single round-trip. The feature is opt-in; when disabled, supplied variants are ignored and the legacy single-query path is used.

# Multi-query: fan out across variant rewrites
curl -X POST http://localhost:8643/api/query \
  -H 'Content-Type: application/json' \
  -d '{
    "query": "How does retrieval work?",
    "query_variants": [
      {"query": "How does retrieval work?", "language": "en"},
      {"query": "Come funziona il retrieval?", "language": "it"}
    ]
  }'

The routing object in the response always includes these contract fields regardless of path:

FieldDescription
multi_query_enabledWhether multi-query was active for this request
multi_query_variant_countNumber of variants actually evaluated (1 when no variants)
query_variantsArray of variant objects (query, language, weight)
multi_query_fusionFusion strategy used ('rrb', 'weighted', or null)
multi_query_unique_notesCount of distinct notes returned
multi_query_multivariant_hitsNotes matched by 2+ variants (0 for single-query)
sourceThe source value from the request (or null for interactive queries)
source_policyObject with frequency_increment, provenance_label, and use_user_prompt_for_retrieval for the active source

Source policy and session_synthesis contract

Every source string accepted by /api/query is registered in an explicit policy (bdh_graph_harness/memory/source_policy.py). The registry controls:

SourceFrequency incrementRetrieval routingNeurogenesis
(None / interactive)1.0Uses queryYes
user_query1.0Uses queryYes
assistant_response0.3Uses queryYes
nightly_semantic_consolidation0.3Uses queryYes
session_synthesis0.2Uses user_promptYes
cron0.3Uses queryNo
automatic_retrieval1.0Uses queryYes

session_synthesis is designed for the bridge's session-synthesis payload where the actual transcript is in user_prompt while query is a fixed generic label. The policy ensures:

  1. Dampened plasticity (0.2 increment) — synthetic/derived signals don't pollute learned associations at full interactive strength.
  2. Correct retrieval — the attention pass uses user_prompt (the real session evidence) instead of the unrelated fixed query string.
  3. Provenance — newly created synapses carry a source: "session_synthesis" label, and the routing response includes source_policy for traceability.
  4. No silent fall-through — unrecognised source strings raise ValueError instead of defaulting to full strength.

Running as a service (macOS)

The concrete plist is machine-specific (absolute home paths) and is not committed; the tracked template is ai.bdh.graph-harness.plist.example.

export BDH_ROOT="$HOME/Projects/bdh-graph-harness"
export BDH_HOME="$HOME/.hermes"
envsubst '$BDH_ROOT $BDH_HOME' \
  < ai.bdh.graph-harness.plist.example \
  > ai.bdh.graph-harness.plist
$EDITOR ai.bdh.graph-harness.plist        # set UserName to your account
cp ai.bdh.graph-harness.plist ~/Library/LaunchAgents/
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/ai.bdh.graph-harness.plist

The service auto-restarts on crash (KeepAlive: true). Logs at $BDH_HOME/logs/bdh-server.log. The start-server.sh wrapper loads the configured provider credential from the environment (OLLAMA_API_KEY, OPENROUTER_API_KEY, or OPENCODE_ZEN_API_KEY) before launching.

Human-confirmed merge reconciliation

Directed merge preview exposes conflict: {required, signals, provenance_flag}. These negation/replacement matches are lexical warnings, not semantic verdicts. The existing guard remains in force until a caller supplies an exact-text assessment and a separate human decision; automatic apply is unchanged.

POST /api/synthesis/merge-reconciliation registers advice only. Supply the stored candidate correlation (candidate_id, synthesis_id, session_id, source, vault_id), target_node_id, both preview revisions, and assessment with classification (compatible, conflicting, or uncertain), reason, exact candidate_quote and target_quote, provider, and model. Full text exceeds 60,000 combined characters: HTTP 413, never silent truncation. Invalid quotes: HTTP 400. Stale revisions: HTTP 409.

The endpoint writes only an immutable advice record under <vault>/.bdh-audit/merge-reconciliations/<reconciliation_id>.json; it does not edit notes, approve candidates, advance the curate audit, or create an operation journal. The Curate plugin owns the model call; BDH only validates and stores the opinion.

A flagged POST /api/synthesis/merge still needs the normal identity, revisions and confirmed: true, plus reconciliation_id and literal conflict_confirmed: true. BDH reloads the record under the vault runtime lock and requires a compatible opinion bound to the same candidate, vault, target and both texts. Uncertain/conflicting, foreign or stale records fail closed. The human-confirmed ID is recorded in the curate audit. Existing reversible note assimilation and idempotent retry remain intact.

Config

See bdh-config.yaml for shipped settings and config.py for internal defaults. The table below is an orientation guide; provider/model/storage values are deployment-specific, not a required maintainer setup. The demo has its own explicit settings. Key ones:

ParameterDefaultDescription
seed_count5Top-k embedding matches as seed nodes
max_hop2Graph traversal depth from seeds
active_threshold0.25Min activation score (overridden by adaptive)
alpha0.7Frequency weight in Hebbian
beta0.3Recency weight in Hebbian
decay0.95Per-session decay for unused synapses
hybrid_searchtrueEnable BM25 hybrid mode (currently true in both shipped config and internal defaults)
hybrid_alpha0.7Vector search weight (only when hybrid_search: true)
hybrid_beta0.3BM25 search weight (only when hybrid_search: true)
llm_providerollamaollama (local), ollama-cloud, or openrouter (OpenAI-compatible endpoints)
llm_modelgemma4:12b-mlxModel name for chosen provider
llm_base_url—OpenAI-compatible base URL for ollama-cloud
llm_api_key—Environment-expanded credential for the selected provider
api_port8643Server port
quality_threshold0.25Quality score below this → node marked dormant
quality_reactivation_score0.50Activation score to re-awaken a dormant node
quality_prune_interval50Re-evaluate node quality every N queries
graph_ignore[]fnmatch patterns to exclude nodes from the graph (e.g. [".bdh-*"])
external_sources[]Read-only Markdown sources with per-source include/exclude glob lists; optional explicit counterparts link vault/external anchor notes
consolidation_downscale_factor0.90Global weight multiplier per sleep cycle
consolidation_prune_weight_floor0.02Delete synapses below this weight after downscaling
consolidation_weak_weight_threshold0.15Weak-tail threshold for stale-weak retention (not an online creation threshold)
consolidation_weak_max_frequency1.0Stale weak traces above this frequency survive
consolidation_weak_min_age_hours48Fresh weak traces get a grace period before pruning
neurogenesis_source_edges_enabledtrueMaterialize validated neurogenesis_source generated edges from activated_from_ids frontmatter
multi_query_enabledfalseEnable multi-query fan-out; when false, query_variants in requests are silently ignored
multi_query_max_variants3Hard cap on evaluated variants per request (after dedup and empty-drop)
hebbian_dynamic_edges_enabledtrueTraverse eligible learned-only relations alongside static adjacency
hebbian_dynamic_min_weight0.15Ignore learned dynamic edges below this weight
hebbian_dynamic_top_n3Maximum learned candidates considered per active note
hebbian_dynamic_gain1.5Score multiplier for learned-only traversal
hebbian_dynamic_hop_decay0.6Per-hop decay for learned-only traversal
hebbian_dynamic_shadow_enabledtrueEmit privacy-safe telemetry when dynamic-only results are recovered
consolidation_dormant_persist_cycles3Remove nodes dormant for N+ consolidation cycles
consolidation_prune_dormant_nodestrueDelete stale dormant nodes (not just hide)
session_synthesis_staging_enabledfalseGate session_synthesis writes behind Curate: extract concepts into pending candidate files instead of creating/merging notes directly
session_synthesis_staging_dir.bdh-candidatesDirectory (relative to the vault root) holding staged Curate candidate files
session_synthesis_staging_max_concepts5Cap on staged candidates per synthesis

Tests

# Install runtime + test tooling
pip install -r requirements-dev.txt

# Full suite
python -m pytest -q

# Include statement and branch coverage
python -m pytest -q --cov=bdh_graph_harness --cov-branch --cov-report=term-missing

Test totals change; use the exact CI run or local verification receipt, not a frozen count. The suite includes regression coverage for multi-query retrieval, API contracts, provenance, WebSocket ordering, the retrieval inspector UI, and trusted dynamic Hebbian traversal.

See docs/testing.md for the coverage policy, exact commands, and multi-vault regression requirements. docs/coverage.md records the current versioned baseline; GitHub Actions keeps the XML and JSON report for every later develop or main run.

Visualization

The web UI at :8643 shows a real-time 3D force-graph (WebGL via 3d-force-graph) with:

  • 3D node rendering — Three.js objects with custom geometries, label sprites, rings, and dashed semantic links
  • Nodes colored by activation state or by Obsidian tags (toggle); neurogenesis nodes use aqua (#00E5FF)
  • Wikilink edges + Hebbian synapses + Phantom links + Counterpart/project-context edges + Neurogenesis source edges with hover tooltips (weight, type, connected notes)
  • Live neurogenesis — new edges appear in real-time as concepts are created; provenance edges connect newborn notes to their source nodes
  • Hover highlighting — node hover shows 1-hop subgraph, edge hover highlights the edge
  • Node drag, viewport-preserving updates, camera-preserving structural updates, and touch-first mobile graph interactions
  • Z-order — wikilinks (bottom) → phantom (middle) → hebbian (top)
  • Orphan nodes toggle, tag legend overlay, dark theme, mobile responsive (iPhone safe area, touch dismiss, tab-based layout)
  • Node quality — dormant nodes dimmed (gray, 30% opacity) with 💤 tooltip; stats bar shows dormant count and Hebbian tail metrics
  • Persisted controls — slider values saved in localStorage, restored on refresh
  • Hebbian tail metrics — hebbian_strong_synapses, hebbian_weak_synapses, hebbian_stale_weak_synapses exposed via /api/stats
  • Query-response WebSocket event — final ordered event after LLM response so plugin-launched queries are visible in the graph UI

See docs/visualization.md for full details on controls, tooltips, and mobile support. See docs/visualization-3d-migration.md for the 3D migration architecture, lifecycle, and validation details.

MCP Server

The harness includes a Model Context Protocol server that exposes the Hebbian graph as tools to any MCP-compatible client (Claude Desktop, Cursor, Windsurf, Continue).

# stdio mode (Claude Desktop, Cursor)
python -m bdh_graph_harness --config bdh-config.local.yaml --mcp

# HTTP mode (web clients)
python -m bdh_graph_harness --config bdh-config.local.yaml --mcp --mcp-transport http --mcp-port 8644

Tools: query (grounded Q&A with citations), stats (graph overview), hebbian (learned synapses), graph (full network), refresh (rebuild embeddings).

The MCP tools first use the configured Harness HTTP API (api_host/api_port). If it is unreachable, they can initialize an in-process backend. This backend selection is separate from MCP stdio/HTTP transport; direct fallback can index/cache and execute learning. See the detailed MCP guide before running both against the same vault.

See docs/mcp-server.md for client configuration (Claude Desktop, Cursor, etc.).

Hermes Agent integration

bdh-hermes-bridge plugin (recommended)

The bdh-hermes-bridge plugin provides bidirectional integration between Hermes Agent and BDH:

  • Write path — eligible completed turns can be submitted for learning; enabling the plugin is not a read-only action. See the bridge isolated onboarding first
  • Read path — bdh_query tool lets Hermes pull context from the knowledge graph on demand
  • Echo-loop dampening — assistant responses are flagged with source: "assistant_response" to prevent feedback amplification
  • Session synthesis — session-synthesis payloads use source: "session_synthesis" with dampened plasticity (0.2 increment) and user_prompt-based retrieval; see Source policy
  • User context capture — original user prompt is included in write payloads, enabling proper question→answer synaptic associations
# Install
git clone https://github.com/albidev/bdh-hermes-bridge.git ~/.hermes/plugins/bdh-hermes-bridge

# Enable in ~/.hermes/config.yaml
plugins:
  enabled:
    - bdh-hermes-bridge

Hermes skill (CLI)

The harness ships a canonical, standards-compliant Hermes Agent skill at skills/research/bdh-graph-harness/SKILL.md. Install it directly — no manual copy from docs/ is needed:

hermes skills install https://raw.githubusercontent.com/albidev/bdh-graph-harness/main/skills/research/bdh-graph-harness/SKILL.md
hermes skills list

The skill and the bdh-hermes-bridge plugin are separate components. The plugin must be installed and enabled independently; it supplies the Hermes-native bdh_query and bdh_stats tools. The skill does not start BDH or change graph state.

Once the skill and bridge are available, Hermes can:

  • Query the graph through bdh_query with source citations
  • Read graph metrics through the read-only bdh_stats tool
  • Fall back to the configured BDH CLI/API when native bridge tools are unavailable
  • Inspect Hebbian state and explain the difference between read-only operations and state-mutating retrieval/maintenance

The effective BDH API host and port are deployment-specific. Resolve them from the active configuration or bridge setup; examples elsewhere in this README are not universal defaults.

Obsidian Sync Plugin

Auto-sync vault changes to the BDH server via an Obsidian plugin — no manual refresh needed.

Obsidian edit → Plugin detects → Debounce 1s → POST /api/node-update
    → Server diffs graph → WebSocket broadcast → Viz updates in real-time

Setup:

  1. Build the plugin: cd plugins/obsidian && npm install && npm run build
  2. Copy manifest.json + main.js to your vault's .obsidian/plugins/bdh-graph-harness-sync/
  3. Enable "BDH Graph Harness Sync" in Obsidian Settings → Community Plugins

Features:

  • Debounced updates (1s, configurable) — no server spam
  • Status bar indicator (○ idle, ◎ syncing, ● ok, ✗ error)
  • Ignores non-.md files and .obsidian/ directory
  • Configurable server URL, debounce delay, enable/disable

Pulse animation: Hebbian edges get animated particles during query — color transitions from green through blue, particle count scales with weight gain. Activation state is managed via external Maps to avoid mutating force-graph's live objects.

Sleep-Cycle Consolidation

Periodic graph maintenance that mirrors biological sleep consolidation. Run it manually or schedule it (e.g. nightly via cron):

# Trigger a consolidation cycle
curl -X POST http://localhost:8643/api/consolidate

# Dry run (see what would change without committing)
curl -X POST http://localhost:8643/api/consolidate -H "Content-Type: application/json" -d '{"dry_run": true}'

# View config and cycle count
curl http://localhost:8643/api/consolidation-stats

Cycle steps:

  1. Synaptic downscaling — multiply all Hebbian weights by consolidation_downscale_factor (default 0.90). Prevents runaway strengthening.
  2. Stale-weak pruning — delete synapses that are below consolidation_weak_weight_threshold (default 0.15), have frequency ≤ consolidation_weak_max_frequency (default 1.0), and were last co-activated more than consolidation_weak_min_age_hours (default 48) ago. Fresh and frequently reinforced weak synapses survive.
  3. Structural pruning — delete synapses with weight below consolidation_prune_weight_floor (default 0.02) after downscaling.
  4. Quality re-evaluation — recalculate node quality scores and update dormant state.
  5. Stale removal — delete nodes dormant for more than consolidation_dormant_persist_cycles (default 3) consecutive cycles, if consolidation_prune_dormant_nodes is true.

No tokens consumed — pure algorithmic operation on the local graph state. Safe to run while the server is serving queries (file-locked state access).

Graph Quality Audit

A read-only audit script is available to check structural and Hebbian invariants without mutating state:

python scripts/audit_graph_quality.py --url http://localhost:8643

Reports: node count, structural edges, invalid endpoints, self-loops, duplicate edges, Hebbian tail classification (strong/weak/stale-weak), generated edge types, and source distribution.

License

MIT

↑↓ navigate openesc close