Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

m1nd is a neuro-symbolic connectome engine built in Rust. It ingests a codebase into a weighted, directed graph — then lets you fire queries into that graph and watch where the energy goes. Signal propagates across structural, semantic, temporal, and causal dimensions. The graph learns from every interaction via Hebbian plasticity: paths you use get stronger, paths you ignore decay. The result is a code intelligence layer that adapts to how you think about your codebase. It ships as an MCP server with 43 tools, runs on stdio, and works with any MCP-compatible client.

The Problem

AI coding agents are powerful reasoners but terrible navigators. An LLM can analyze a function you paste into its context window, but it cannot find the right function in a codebase of 10,000 files without burning tokens on speculative grep, glob, and tree walks. The existing tools fail in different ways:

  • Full-text search finds what you said, not what you meant. Searching for “authentication” won’t surface the session middleware that enforces it.
  • RAG retrieves chunks by embedding similarity, but each retrieval is stateless. It has no memory of what it retrieved last time, and no way to express relationships between results.
  • Static analysis produces call graphs and ASTs, but they are frozen snapshots. They cannot answer “what if I remove this module?” or “what changed since my last session?”
  • Knowledge graphs require manual curation and only return what was explicitly encoded.

m1nd solves this with a fundamentally different approach: spreading activation on a learned graph. Instead of matching tokens or embedding vectors, it propagates energy from seed nodes through weighted edges and observes the activation pattern that emerges. The graph topology determines where signal flows. Hebbian learning determines how strongly.

335 files -> 9,767 nodes -> 26,557 edges in 0.91s
activate in 31ms | impact in 5ms | trace in 3.5ms | learn in <1ms

No LLM calls. No API keys. No network. The binary is ~8MB of pure Rust.

Key Capabilities

Spreading Activation

The core query primitive. Fire a signal into the graph from one or more seed nodes. The signal propagates through the CSR adjacency structure, decaying at each hop, inhibited by negative edges, and scored across four dimensions:

DimensionSourceWhat It Captures
StructuralCSR adjacency + PageRankGraph distance, edge types, centrality
SemanticTrigram matching on identifiersNaming patterns, token overlap
TemporalGit history + learn feedbackCo-change frequency, recency decay
CausalStacktrace mapping + call chainsError proximity, suspiciousness

The engine selects between a wavefront (BFS-parallel) and a heap (priority-queue) propagation strategy at runtime based on seed density and average degree. Results are merged with adaptive dimension weighting and a resonance bonus for nodes that score across 3 or 4 dimensions.

// Ask: "What's related to authentication?"
{"method": "tools/call", "params": {
  "name": "m1nd.activate",
  "arguments": {"query": "authentication", "agent_id": "dev"}
}}
// -> auth.py fires -> propagates to session, middleware, JWT, user model
//    4-dimensional relevance ranking in 31ms

Hebbian Plasticity

The graph learns. When you tell m1nd that results were useful, edge weights strengthen along the activated paths (delta_w = learning_rate * activation_src * activation_tgt). When results go unused, inactive edges decay. After sustained strengthening, edges receive a permanent Long-Term Potentiation (LTP) bonus. After sustained weakening, Long-Term Depression (LTD) applies a permanent penalty. Homeostatic normalization prevents runaway weights by scaling incoming edges when their sum exceeds a ceiling.

// Tell the graph what was useful
{"method": "tools/call", "params": {
  "name": "m1nd.learn",
  "arguments": {
    "feedback": "correct",
    "node_ids": ["file::auth.py", "file::middleware.py"],
    "agent_id": "dev"
  }
}}
// -> 740 edges strengthened. Next query for "authentication" is smarter.

Plasticity state persists to disk. Across sessions, the graph evolves to match how your team navigates the codebase.

Structural Hole Detection

m1nd.missing finds gaps in the graph — nodes or edges that should exist based on the surrounding topology but don’t. If every other module in a cluster has error handling and one doesn’t, that’s a structural hole. If two subsystems communicate through a single bridge node, that’s a fragility point. This turns the graph into a specification-free audit tool.

Counterfactual Simulation

“What breaks if I delete worker.py?” The counterfactual engine uses a zero-allocation bitset mask (no graph clone) to virtually remove nodes and their incident edges, then re-runs spreading activation to measure the impact. The output includes orphaned nodes, weakened nodes, reachability loss, and cascade depth. Synergy analysis reveals whether removing two modules together is worse than the sum of removing them individually.

counterfactual("worker.py") -> 4,189 affected nodes, cascade at depth 3
counterfactual("config.py")  -> 2,531 affected nodes (despite being universally imported)

Perspectives

Stateful graph navigation. Open a perspective anchored to a node, list available routes, follow edges, peek at source code, and branch explorations. Perspectives carry confidence calibration and epistemic safety checks. Two agents can open independent perspectives on the same graph and later compare them to find shared nodes and divergent conclusions.

XLR Noise Cancellation

Borrowed from professional audio engineering. Like a balanced XLR cable, m1nd transmits signal on two inverted channels — hot (from your query seeds) and cold (from automatically selected anti-seeds that are structurally similar but semantically distant). The cold signal cancels common-mode noise: the generic infrastructure that every query touches. What survives is the differential signal specific to your actual question. Sigmoid gating, density-adaptive strength, and seed immunity prevent over-cancellation.

Who This Is For

  • AI agent developers who need their agents to navigate code without wasting context tokens on trial-and-error search.
  • IDE and tool builders looking for an MCP-compatible code intelligence backend that goes beyond static analysis.
  • Anyone using MCP clients (Claude Code, Cursor, Windsurf, Zed, Cline, Roo Code, Continue, OpenCode, Amazon Q, GitHub Copilot) who wants a graph that learns from their workflow.
  • Multi-agent orchestrators who need a shared, persistent code graph that multiple agents can query, learn from, and lock regions of concurrently.

m1nd is not a replacement for full-text search, and it does not use neural embeddings (v1 uses trigram matching for the semantic dimension). If you need “find code that means X but never uses the word,” m1nd will not do it yet. See the FAQ for more on current limitations.

How to Read This Wiki

This wiki is organized into four sections. Read them in order for a full understanding, or jump to the section you need.

Architecture — How m1nd is built. The three crates (m1nd-core, m1nd-ingest, m1nd-mcp), the CSR graph representation, the activation engine hierarchy, and the MCP server protocol layer. Start here if you want to contribute or understand the internals.

Concepts — The ideas behind the implementation. Spreading activation, Hebbian plasticity, XLR noise cancellation, and structural hole detection explained with enough depth to reason about behavior and tune parameters.

API Reference — All 43 MCP tools documented with parameters, return types, and usage examples. Organized by function: activation and queries, analysis and prediction, memory and learning, exploration and discovery, perspective navigation, and lifecycle administration.

Tutorials — Practical walkthroughs. Quick start, your first query, and multi-agent workflows with trail save/resume/merge.

The Benchmarks page has real performance numbers from a production codebase (335 files, ~52K lines, 9,767 nodes, 26,557 edges). The Changelog tracks what changed between versions.


cargo build --release
./target/release/m1nd-mcp

The server starts, listens on stdio, and waits for JSON-RPC. The graph is empty until you call m1nd.ingest. From there, every query teaches it something.

System Architecture Overview

m1nd is a neuro-symbolic connectome engine that transforms codebases (and other structured domains) into a weighted property graph, then runs biologically-inspired algorithms over it: spreading activation, Hebbian plasticity, spectral noise cancellation, standing wave resonance, and temporal co-change analysis. It exposes these capabilities as 43 MCP tools over JSON-RPC stdio, serving multiple agents concurrently from a single process.

Three-Crate Workspace

The system is organized as a Cargo workspace with three crates:

[workspace]
members = ["m1nd-core", "m1nd-ingest", "m1nd-mcp"]
resolver = "2"

[workspace.dependencies]
thiserror = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
smallvec = { version = "1.13", features = ["serde"] }
parking_lot = "0.12"
rayon = "1.10"
static_assertions = "1"
CrateRoleKey Dependency
m1nd-coreGraph engine, activation, plasticity, XLR, resonance, temporal, semanticparking_lot, smallvec, static_assertions
m1nd-ingestFile walking, language-specific extraction, reference resolution, diffrayon, walkdir, regex
m1nd-mcpJSON-RPC transport, tool dispatch, session management, persistencetokio, serde_json

Dependencies flow strictly downward: m1nd-mcp depends on both m1nd-core and m1nd-ingest; m1nd-ingest depends on m1nd-core; m1nd-core has no internal crate dependencies.

Data Flow

graph TD
    subgraph "m1nd-mcp (Transport)"
        STDIO["JSON-RPC stdio<br/>dual: framed + line"]
        DISPATCH["Tool Dispatch<br/>43 tools"]
        SESSION["SessionState<br/>SharedGraph + Engines"]
        PERSIST["Auto-Persist<br/>every 50 queries"]
    end

    subgraph "m1nd-ingest (Extraction)"
        WALK["DirectoryWalker<br/>walkdir + binary detect"]
        EXTRACT["Parallel Extraction<br/>rayon threadpool"]
        RESOLVE["ReferenceResolver<br/>proximity disambiguation"]
        DIFF["GraphDiff<br/>incremental updates"]
        MEMORY["MemoryAdapter<br/>markdown → graph"]
    end

    subgraph "m1nd-core (Engine)"
        GRAPH["CSR Graph<br/>forward + reverse"]
        NODES["NodeStorage (SoA)<br/>hot|warm|cold paths"]
        ACT["Activation Engine<br/>hybrid heap/wavefront"]
        SEM["Semantic Engine<br/>trigram TF-IDF + PPMI"]
        TEMP["Temporal Engine<br/>co-change + decay"]
        PLAST["Plasticity Engine<br/>Hebbian LTP/LTD"]
        XLR["XLR Engine<br/>spectral noise cancel"]
        RES["Resonance Engine<br/>standing waves"]
        SNAP["Snapshot<br/>atomic JSON persist"]
    end

    STDIO --> DISPATCH
    DISPATCH --> SESSION
    SESSION --> PERSIST
    PERSIST --> SNAP

    SESSION -->|ingest| WALK
    WALK --> EXTRACT
    EXTRACT --> RESOLVE
    RESOLVE --> GRAPH
    DIFF -->|incremental| GRAPH

    SESSION -->|activate| ACT
    SESSION -->|impact/predict| TEMP
    SESSION -->|learn| PLAST
    SESSION -->|activate+xlr| XLR
    SESSION -->|resonate| RES

    ACT --> GRAPH
    ACT --> SEM
    ACT --> NODES
    SEM --> GRAPH
    TEMP --> GRAPH
    PLAST --> GRAPH
    XLR --> GRAPH
    RES --> GRAPH
    GRAPH --> NODES

Request Lifecycle

A typical m1nd.activate query flows through these stages:

  1. Transport: JSON-RPC message arrives on stdin (either Content-Length framed or raw line JSON).
  2. Dispatch: McpServer.serve() parses the JSON-RPC request, matches the tool name, extracts parameters.
  3. Session: The tool handler acquires a read lock on SharedGraph (Arc<parking_lot::RwLock<Graph>>).
  4. Seed Finding: SeedFinder locates matching nodes via a 5-level cascade: exact label, prefix, substring, tag, fuzzy trigram.
  5. Activation: HybridEngine auto-selects heap or wavefront strategy based on seed ratio and average degree.
  6. Dimensions: Four dimensions run: Structural (BFS/heap propagation), Semantic (trigram TF-IDF + co-occurrence PPMI), Temporal (decay + velocity), Causal (forward/backward with discount).
  7. Merge: merge_dimensions() combines results with adaptive weights [0.35, 0.25, 0.15, 0.25] and resonance bonus (4-dim: 1.5x, 3-dim: 1.3x).
  8. XLR: If enabled, AdaptiveXlrEngine runs spectral noise cancellation with dual hot/cold pulses and sigmoid gating.
  9. Plasticity: PlasticityEngine.update() runs the 5-step Hebbian cycle on co-activated edges.
  10. Response: Results serialized to JSON-RPC response, written to stdout in the same transport mode as the request.

Key Design Decisions

Compressed Sparse Row (CSR) Graph

The graph uses CSR format rather than adjacency lists or adjacency matrices. CSR provides O(1) neighbor iteration start, cache-friendly sequential access, and compact memory layout. Edge weights are stored as AtomicU32 (bit-reinterpreted f32) for lock-free plasticity updates via CAS with a 64-retry limit.

Forward and reverse CSR arrays are maintained in parallel, enabling both outgoing and incoming edge traversal without full graph scans.

Struct-of-Arrays Node Storage

NodeStorage uses SoA layout with explicit hot/warm/cold path separation:

  • Hot path (every query): activation: Vec<[FiniteF32; 4]>, pagerank: Vec<FiniteF32>
  • Warm path (plasticity): plasticity: Vec<PlasticityNode>
  • Cold path (seed finding, export): label, node_type, tags, last_modified, change_frequency, provenance

This layout ensures that activation queries touch only hot-path arrays, maximizing L1/L2 cache utilization.

FiniteF32 Type Safety

All floating-point values in the graph are wrapped in FiniteF32, a #[repr(transparent)] newtype that makes NaN and Infinity impossible by construction:

#![allow(unused)]
fn main() {
#[derive(Clone, Copy, Default, PartialEq)]
#[repr(transparent)]
pub struct FiniteF32(f32);

impl FiniteF32 {
    #[inline]
    pub fn new(v: f32) -> Self {
        debug_assert!(v.is_finite(), "FiniteF32::new received non-finite: {v}");
        Self(if v.is_finite() { v } else { 0.0 })
    }
}
}

Because NaN is excluded, FiniteF32 can safely implement Ord, Eq, and Hash – operations that are unsound on raw f32. Related newtypes PosF32 (strictly positive), LearningRate (0, 1]), and DecayFactor (0, 1]) provide compile-time invariant enforcement for their respective domains.

String Interning

All node labels, tags, and relation names are interned via StringInterner. Once interned, string comparisons become u32 == u32 (zero-allocation, single CPU cycle). The interner maps strings to InternedStr(u32) handles and provides O(1) bidirectional lookup.

Parallel Extraction, Sequential Building

Ingestion uses rayon for parallel file reading and language-specific extraction across all CPU cores, but graph construction is single-threaded. This avoids the complexity of concurrent graph mutation while still saturating I/O bandwidth during the most expensive phase (parsing hundreds of files).

Atomic Persistence

Graph and plasticity state are saved via atomic write: serialize to a temporary file, then rename() over the target. This prevents corruption on crash (FM-PL-008). The NaN firewall at the export boundary rejects any non-finite values that might have leaked through.

Performance Characteristics

Benchmarks on the a production codebase (335 files, ~52K lines, Python + Rust + TypeScript):

OperationTimeNotes
Full ingest~910msWalk + parallel extract + resolve + finalize (CSR + PageRank)
Activate query~31ms4-dimension with XLR, top-20 results
Impact analysis~5msBFS blast radius, 3-hop default
Predict (co-change)~8msCo-change matrix lookup + velocity scoring
Graph persist~45msAtomic JSON write, ~2MB snapshot
Plasticity update~2ms5-step Hebbian cycle on co-activated edges

Memory footprint scales linearly with graph size. A 10K-node graph with 25K edges uses approximately 15MB of heap (graph + all engine indexes). The CSR representation is 3-5x more compact than equivalent adjacency list representations.

Scaling Bounds

  • Node limit: NodeId(u32) supports up to ~4 billion nodes. Practical limit is the max_nodes config (default: 500K).
  • Edge weights: AtomicU32 CAS with 64-retry limit. Under high contention (>32 concurrent plasticity updates on the same edge), CAS may exhaust retries and return CasRetryExhausted.
  • Co-occurrence index: Disabled above 50K nodes (COOCCURRENCE_MAX_NODES) to avoid O(N * walks * length) random walk cost.
  • Co-change matrix: Hard budget of 500K entries with per-row cap of 100 entries.
  • Resonance pulse budget: 50K pulses per standing wave analysis to prevent combinatorial explosion in dense subgraphs.

Multi-Agent Model

m1nd runs as a single process serving multiple agents through the same JSON-RPC stdio channel. All agents share one graph and one set of engines. Writes (plasticity updates, ingestion) are immediately visible to all readers through SharedGraph = Arc<parking_lot::RwLock<Graph>>.

Each agent gets its own AgentSession tracking first/last seen timestamps and query count. The perspective system (per-agent branching views) and lock system (per-agent change tracking) provide isolation where needed without duplicating the underlying graph.

parking_lot::RwLock is used instead of std::sync::RwLock to prevent writer starvation – a critical property when plasticity updates (writes) must interleave with activation queries (reads).

Graph Engine (m1nd-core)

m1nd-core is the computational core of the connectome. It owns the graph data structure, all activation and analysis engines, the plasticity system, and the type-safe numeric primitives that prevent NaN/Inf corruption system-wide.

Source: mcp/m1nd/m1nd-core/src/

Type System

Numeric Primitives

Every floating-point value in m1nd flows through one of four newtype wrappers defined in types.rs:

TypeInvariantUse
FiniteF32Never NaN or InfAll activation scores, edge weights, scores
PosF32Strictly positive, finiteWavelength, frequency, half-life, decay rate, threshold
LearningRate(0.0, 1.0]Plasticity learning rate
DecayFactor(0.0, 1.0]Signal decay per hop

FiniteF32 is the foundation. In debug builds, constructing one from a non-finite value panics. In release builds, it clamps to 0.0. Because NaN is excluded by construction, FiniteF32 implements Ord, Eq, and Hash – all unsound on raw f32:

#![allow(unused)]
fn main() {
impl Ord for FiniteF32 {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.0.total_cmp(&other.0)
    }
}
}

Index Types

Thin #[repr(transparent)] wrappers over u32 provide type-safe indexing:

TypeWrapsPurpose
NodeId(u32)Node indexIndex into NodeStorage parallel arrays
EdgeIdx(u32)Edge indexIndex into CSR parallel arrays
InternedStr(u32)String handleOpaque index into StringInterner
CommunityId(u32)CommunityLouvain community membership
Generation(u64)Mutation counterPlasticity engine desync detection

Node and Edge Classification

#![allow(unused)]
fn main() {
#[repr(u8)]
pub enum NodeType {
    File = 0, Directory = 1, Function = 2, Class = 3,
    Struct = 4, Enum = 5, Type = 6, Module = 7,
    Reference = 8, Concept = 9, Material = 10, Process = 11,
    Product = 12, Supplier = 13, Regulatory = 14, System = 15,
    Cost = 16, Custom(u8),
}

#[repr(u8)]
pub enum EdgeDirection {
    Forward = 0,
    Bidirectional = 1,
}
}

Variants 0-8 are code-domain types. Variants 9-16 support non-code domains (supply chain, manufacturing). Custom(u8) is the extension point for future domains.

Dimension System

Activation operates across four dimensions with fixed weights:

#![allow(unused)]
fn main() {
pub enum Dimension {
    Structural = 0,  // Graph topology (BFS/heap propagation)
    Semantic = 1,    // Text similarity (trigram TF-IDF + co-occurrence)
    Temporal = 2,    // Time-based (decay + velocity)
    Causal = 3,      // Causal chains (forward/backward with discount)
}

pub const DIMENSION_WEIGHTS: [f32; 4] = [0.35, 0.25, 0.15, 0.25];
}

When fewer than 4 dimensions contribute to a result, weights are adaptively redistributed. Results that fire across multiple dimensions receive a resonance bonus: 1.5x for all 4, 1.3x for 3.

Property Graph Model

String Interner

All strings pass through StringInterner before entering the graph. The interner maps strings to InternedStr(u32) handles via a HashMap<String, InternedStr> and resolves handles back via a Vec<String> indexed by the u32 value.

Once interned, all string comparisons become integer comparisons – zero-allocation, single CPU cycle.

#![allow(unused)]
fn main() {
pub struct StringInterner {
    strings: Vec<String>,
    index: HashMap<String, InternedStr>,
}
}

CSR Graph

The graph uses Compressed Sparse Row (CSR) format with both forward and reverse adjacency. For node i, outgoing edges span offsets[i]..offsets[i+1] into parallel arrays for targets, weights, inhibitory flags, relations, directions, and causal strengths.

#![allow(unused)]
fn main() {
pub struct CsrGraph {
    // Forward CSR
    pub offsets: Vec<u64>,              // num_nodes + 1
    pub targets: Vec<NodeId>,           // total_edges
    pub weights: Vec<AtomicU32>,        // bit-reinterpreted f32 for lock-free CAS
    pub inhibitory: Vec<bool>,          // edge polarity
    pub relations: Vec<InternedStr>,    // edge type (interned)
    pub directions: Vec<EdgeDirection>, // forward or bidirectional
    pub causal_strengths: Vec<FiniteF32>,

    // Reverse CSR (built at finalize)
    pub rev_offsets: Vec<u64>,
    pub rev_sources: Vec<NodeId>,
    pub rev_edge_idx: Vec<EdgeIdx>,     // maps back to forward arrays

    // Pre-finalize staging
    pub pending_edges: Vec<PendingEdge>,
}
}

The CSR is immutable after finalization. Edges are added to pending_edges during graph building, then sorted by source and compacted into the CSR arrays by Graph.finalize(). Bidirectional edges are expanded into two forward entries during finalization.

Atomic Weight Updates

Edge weights are stored as AtomicU32 rather than f32. The plasticity engine updates weights concurrently with read queries using Compare-And-Swap:

#![allow(unused)]
fn main() {
pub fn atomic_max_weight(
    &self, edge: EdgeIdx, new_val: FiniteF32, max_retries: u32,
) -> M1ndResult<()> {
    let slot = &self.weights[edge.as_usize()];
    let new_bits = new_val.get().to_bits();
    for _ in 0..max_retries {
        let old_bits = slot.load(Ordering::Relaxed);
        let old_val = f32::from_bits(old_bits);
        if old_val >= new_val.get() { return Ok(()); }
        if slot.compare_exchange_weak(
            old_bits, new_bits, Ordering::Release, Ordering::Relaxed
        ).is_ok() { return Ok(()); }
    }
    Err(M1ndError::CasRetryExhausted { edge, limit: max_retries })
}
}

Two CAS operations are provided: atomic_max_weight (only increases, for activation scatter-max) and atomic_write_weight (unconditional, for plasticity). Both retry up to 64 times (constant CAS_RETRY_LIMIT).

Node Storage (SoA)

All per-node data lives in NodeStorage, organized as Struct-of-Arrays with explicit cache-path separation:

#![allow(unused)]
fn main() {
pub struct NodeStorage {
    pub count: u32,

    // Hot path: activation engine reads every query
    pub activation: Vec<[FiniteF32; 4]>,  // [structural, semantic, temporal, causal]
    pub pagerank: Vec<FiniteF32>,

    // Warm path: plasticity reads per query
    pub plasticity: Vec<PlasticityNode>,

    // Cold path: seed finding, display, export
    pub label: Vec<InternedStr>,
    pub node_type: Vec<NodeType>,
    pub tags: Vec<SmallVec<[InternedStr; 6]>>,
    pub last_modified: Vec<f64>,
    pub change_frequency: Vec<FiniteF32>,
    pub provenance: Vec<NodeProvenance>,
}
}

The SmallVec<[InternedStr; 6]> for tags avoids heap allocation for nodes with 6 or fewer tags (the common case), while still supporting arbitrary tag counts.

Node Provenance

Each node carries source metadata for tracing back to the original code:

#![allow(unused)]
fn main() {
pub struct NodeProvenance {
    pub source_path: Option<InternedStr>,
    pub line_start: u32,
    pub line_end: u32,
    pub excerpt: Option<InternedStr>,
    pub namespace: Option<InternedStr>,
    pub canonical: bool,
}
}

Generation Counter

Generation(u64) tracks graph mutations. The plasticity engine stores the generation at initialization. Every plasticity operation asserts that the current graph generation matches; a mismatch (from concurrent ingestion) causes a controlled rebuild rather than operating on stale state.

Activation Propagation

Engine Selection (HybridEngine)

The HybridEngine auto-selects between two propagation strategies based on graph characteristics:

flowchart TD
    START["HybridEngine.propagate()"]
    CHECK{"seed_ratio < 0.001<br/>AND avg_degree < 8?"}
    HEAP["HeapEngine<br/>(priority queue)"]
    WAVE["WavefrontEngine<br/>(BFS depth-parallel)"]
    MERGE["Collect sparse results"]

    START --> CHECK
    CHECK -->|yes| HEAP
    CHECK -->|no| WAVE
    HEAP --> MERGE
    WAVE --> MERGE
  • HeapEngine: Max-heap priority queue. Processes strongest signal first. Early-terminates when the heap top drops below threshold. Uses a double-hashing BloomFilter for O(1) amortized visited checks. Best for sparse queries with few seeds in low-degree graphs.
  • WavefrontEngine: BFS depth-parallel. All active nodes at current depth fire simultaneously. Signal accumulated via scatter-max into next depth’s buffer. Best for dense queries or high-degree graphs.

Structural Propagation (D1)

The wavefront engine is the reference implementation. Signal propagates depth by depth:

  1. Seed nodes initialized with their scores (capped at saturation_cap).
  2. For each depth (up to max_depth=5, hard cap 20):
    • Each frontier node src with activation above threshold=0.04 fires.
    • For each outgoing edge: signal = src_activation * weight * decay(0.55).
    • Inhibitory edges: signal = -signal * inhibitory_factor(0.5), subtracted from target (floored at 0).
    • Excitatory edges: scatter-max into target (keep strongest arrival).
  3. Collect all nodes with non-zero activation, sorted descending.

Semantic Dimension (D2)

Two indexes power semantic matching:

CharNgramIndex: FNV-1a 24-bit trigram hashing over node labels with TF-IDF weighting. An inverted index maps trigram hashes to node lists, enabling O(K) query time (K = number of matching trigrams) instead of O(N) full scan.

CoOccurrenceIndex: DeepWalk-lite random walks (20 walks/node, length 10, window 4) generate co-occurrence counts, normalized to Positive Pointwise Mutual Information (PPMI). Sorted vectors enable O(D) merge-intersection for similarity queries. Disabled above 50K nodes to bound walk cost.

The semantic engine also includes a SynonymExpander with 15 default groups covering code terminology and Portuguese domain terms.

Query pipeline: Phase 1 ngram candidates (3x top_k) -> Phase 2 multi-seed co-occurrence re-rank.

Temporal Dimension (D3)

Three scorers combine:

  • TemporalDecayScorer: Per-NodeType half-lives (File=7 days, Function=14 days, Module/Directory=30 days). Formula: exp(-ln(2) * age_hours / half_life). Dormant nodes (>35 days) get a resurrection bonus with an additive floor.
  • VelocityScorer: Z-score based change velocity. Nodes changing faster than average receive higher scores.
  • CoChangeMatrix: Sparse matrix (budget: 500K entries, 100 per row) bootstrapped from BFS depth 3, refined with git co-change observations.

Causal Dimension (D4)

CausalChainDetector: Budget-limited priority-queue DFS along causal_strength edges. Forward propagation follows contains/imports/calls edges; backward propagation reverses direction. Discount factor 0.7 per hop. Chain depth capped at 6 by default.

Dimension Merging

merge_dimensions() combines all four dimension results:

  1. For each activated node, compute weighted sum: score = sum(dim_score * DIMENSION_WEIGHTS[dim]).
  2. If a dimension produced no results, redistribute its weight proportionally to active dimensions.
  3. Apply resonance bonus: 1.5x if all 4 dimensions contributed, 1.3x if 3 contributed.
  4. Sort by final score, truncate to top_k.

Seed Finding

SeedFinder resolves query strings to graph nodes via a 5-level matching cascade:

  1. Exact label match (highest priority)
  2. Prefix match (e.g., “auth_” matches “auth_handler”)
  3. Substring match (e.g., “handler” matches “auth_handler”)
  4. Tag match (e.g., “#api” tag)
  5. Fuzzy trigram (cosine similarity of trigram vectors, lowest priority)

A semantic re-ranking phase (0.6 basic score / 0.4 semantic blend) refines results when multiple candidates match.

Weight Systems

Hebbian Plasticity

The PlasticityEngine implements biological Hebbian learning with 5 phases executed after every activation query:

flowchart LR
    A["1. Hebbian Strengthen<br/>delta_w = lr * act_src * act_tgt"]
    B["2. Synaptic Decay<br/>w *= (1 - decay_rate) for inactive"]
    C["3. LTP/LTD<br/>permanent bonus/penalty<br/>after N consecutive"]
    D["4. Homeostatic Normalize<br/>scale if total > ceiling"]
    E["5. Record Query<br/>ring buffer + bigrams"]

    A --> B --> C --> D --> E

Constants (from plasticity.rs):

ParameterValuePurpose
DEFAULT_LEARNING_RATE0.08Hebbian weight change rate
DEFAULT_DECAY_RATE0.005Inactive synapse decay per query
LTP_THRESHOLD5Consecutive strengthens before permanent bonus
LTD_THRESHOLD5Consecutive weakens before permanent penalty
LTP_BONUS0.15Permanent weight increase
LTD_PENALTY0.15Permanent weight decrease
HOMEOSTATIC_CEILING5.0Max sum of incoming weights per node
WEIGHT_FLOOR0.05Minimum edge weight (prevents extinction)
WEIGHT_CAP3.0Maximum edge weight

Hebbian update: For each edge where both source and target were activated: delta_w = learning_rate * activation_source * activation_target. Applied via atomic CAS (atomic_write_weight).

Homeostatic normalization: If the sum of incoming weights for any node exceeds HOMEOSTATIC_CEILING, all incoming weights are scaled proportionally to bring the total back under the ceiling. This prevents runaway positive feedback.

Query Memory: A ring buffer of 1000 entries tracks recent queries. Each record stores the query text, seed nodes, activated nodes, and timestamp. The memory tracks:

  • Node frequency: how often each node appears across recent queries.
  • Seed bigrams: co-occurring seed pairs, used for priming signals.

When the buffer wraps, evicted records have their frequency and bigram counts decremented – maintaining accurate sliding-window statistics.

Persistence: Plasticity state (per-edge SynapticState) is exported as JSON using triple-based identity matching (source_label, target_label, relation). This allows plasticity to survive graph rebuilds as long as the semantic structure remains similar.

XLR Differential Processing

XLR (eXcitatory-Lateral-inhibitory Response) is a spectral noise cancellation system that separates signal from noise in activation results.

Constants:

ParameterValuePurpose
F_HOT1.0Hot signal frequency
F_COLD3.7Cold signal frequency
SPECTRAL_BANDWIDTH0.8Gaussian kernel bandwidth
IMMUNITY_HOPS2BFS immunity radius
SIGMOID_STEEPNESS6.0Gating function steepness

6-step pipeline:

  1. Anti-seed selection: Pick nodes dissimilar to seeds (Jaccard similarity < 0.2, degree ratio filter).
  2. Immunity computation: BFS 2 hops from seeds. Immune nodes cannot be suppressed.
  3. Hot propagation: Spread SpectralPulse with frequency F_HOT=1.0 from seed nodes. Pulses carry amplitude, phase, frequency, and a bounded recent path ([NodeId; 3], not unbounded Vec – FM-RES-007).
  4. Cold propagation: Spread from anti-seeds with frequency F_COLD=3.7.
  5. Spectral overlap + density modulation: Compute overlap between hot and cold spectra using Gaussian kernel (bw=0.8). Dense neighborhoods get modulated (clamped to [0.3, 2.0]).
  6. Sigmoid gating: Apply 1 / (1 + exp(-steepness * (hot - cold))) to produce final scores.

Over-cancellation fallback (FM-XLR-010): If all results score zero after gating (cold dominates everywhere), fall back to hot-only results. This prevents total signal erasure in adversarial topologies.

Standing Wave Resonance

The resonance engine discovers structural harmonics by propagating wave pulses through the graph:

#![allow(unused)]
fn main() {
pub struct WavePulse {
    pub node: NodeId,
    pub amplitude: FiniteF32,   // can be negative for destructive interference
    pub phase: FiniteF32,       // [0, 2*pi)
    pub frequency: PosF32,      // MUST be positive (FM-RES-002)
    pub wavelength: PosF32,     // MUST be positive (FM-RES-001)
    pub hops: u8,
    pub prev_node: NodeId,
}
}

Wave interference: Pulses accumulate at each node as complex numbers (real + imaginary). Constructive interference occurs when pulses arrive in phase; destructive when out of phase. The WaveAccumulator tracks sum(amplitude * cos(phase)) and sum(amplitude * sin(phase)), with resultant amplitude sqrt(real^2 + imag^2).

Reflection rules:

  • Dead-end reflection: At leaf nodes (degree 1), pulse reflects with a pi phase shift (REFLECTION_PHASE_SHIFT).
  • Hub partial reflection: At hub nodes (degree > 4x average), 30% of amplitude reflects (HUB_REFLECTION_COEFF).

Budget: DEFAULT_PULSE_BUDGET = 50,000 prevents combinatorial explosion in dense subgraphs. Pulses are processed in BFS order via a VecDeque.

Harmonic analysis: HarmonicAnalyzer sweeps across frequencies, groups nodes by harmonic response, and identifies resonant frequencies (local maxima in the sweep). SympatheticResonanceDetector finds nodes that resonate across regions (outside 2-hop seed neighborhood) – indicating structural coupling without direct edges.

PageRank

Computed once at Graph.finalize() via power iteration:

  • Damping factor: 0.85
  • Max iterations: 50
  • Convergence threshold: 1e-6

PageRank scores are stored in NodeStorage.pagerank and used as a static importance signal in seed finding and result ranking.

Persistence

Graph Snapshot

snapshot.rs serializes the graph to JSON format (version 3):

#![allow(unused)]
fn main() {
struct GraphSnapshot {
    version: u32,           // SNAPSHOT_VERSION = 3
    nodes: Vec<NodeSnapshot>,
    edges: Vec<EdgeSnapshot>,
}
}

Each NodeSnapshot includes external_id, label, node_type, tags, timestamps, and optional provenance. Each EdgeSnapshot includes source/target IDs, relation, weight, direction, inhibitory flag, and causal strength.

Atomic write (FM-PL-008): Write to a temporary file, then rename() over the target. The rename is atomic on POSIX filesystems, so a crash mid-write leaves the previous snapshot intact.

NaN firewall: All values are checked for finiteness at the export boundary. Non-finite values are rejected before writing.

Plasticity State

Per-edge SynapticState (original weight, current weight, strengthen/weaken counts, LTP/LTD flags) is serialized to a separate JSON file. Import uses triple-based matching (source_label, target_label, relation) to reconnect state to edges after graph rebuilds.

Auto-Persist

The MCP server triggers persistence every auto_persist_interval queries (default: 50). Ordering: graph first (source of truth), then plasticity. If graph save fails, plasticity save is skipped to prevent inconsistent state.

Ingestion Pipeline (m1nd-ingest)

m1nd-ingest transforms codebases and structured documents into the property graph consumed by m1nd-core. It handles file discovery, language-specific code extraction, cross-file reference resolution, incremental diff computation, and memory/markdown ingestion.

Source: mcp/m1nd/m1nd-ingest/src/

Module Map

ModulePurpose
lib.rsIngestor pipeline, IngestAdapter trait, config, stats
walker.rsDirectoryWalker, binary detection, git enrichment
extract/mod.rsExtractor trait, comment stripping, CommentSyntax
extract/python.rsPython: classes, functions, decorators, imports
extract/typescript.rsTypeScript/JS: classes, functions, interfaces, imports
extract/rust_lang.rsRust: structs, enums, impls, traits, functions, mods
extract/go.rsGo: structs, interfaces, functions, packages
extract/java.rsJava: classes, interfaces, methods, packages
extract/generic.rsFallback: file-level node with tag extraction
resolve.rsReferenceResolver, proximity disambiguation
cross_file.rsCross-file edge generation (directory contains, sibling)
diff.rsGraphDiff for incremental updates
json_adapter.rsGeneric JSON-to-graph adapter
memory_adapter.rsMarkdown/memory document adapter
merge.rsGraph merge utilities

Pipeline Overview

flowchart TD
    subgraph "Phase 1: Walk"
        DIR["Directory Root"]
        WALK["DirectoryWalker<br/>walkdir + skip rules"]
        BIN["Binary Detection<br/>NUL in first 8KB"]
        GIT["Git Enrichment<br/>commit counts + timestamps"]
        FILES["DiscoveredFile[]"]
    end

    subgraph "Phase 2: Extract (parallel)"
        RAYON["rayon::par_iter"]
        PY["PythonExtractor"]
        TS["TypeScriptExtractor"]
        RS["RustExtractor"]
        GO["GoExtractor"]
        JAVA["JavaExtractor"]
        GEN["GenericExtractor"]
        RESULTS["ExtractionResult[]"]
    end

    subgraph "Phase 3: Build Graph"
        NODES["Add all nodes<br/>with provenance + timestamps"]
        EDGES["Add resolved edges<br/>with causal strengths"]
        CAUSAL["Causal strength assignment<br/>contains=0.8, imports=0.6, etc."]
    end

    subgraph "Phase 4: Resolve"
        REFS["ReferenceResolver<br/>ref:: → actual nodes"]
        PROX["Proximity Disambiguation<br/>same file > same dir > same project"]
        HINTS["Import Hints<br/>module path → target preference"]
        XFILE["Cross-File Edges<br/>directory contains, siblings"]
    end

    subgraph "Phase 5: Finalize"
        CSR["Build CSR<br/>sort edges, compact arrays"]
        BIDIR["Expand Bidirectional<br/>contains, implements"]
        REV["Build Reverse CSR"]
        PR["PageRank<br/>power iteration"]
    end

    DIR --> WALK --> BIN --> GIT --> FILES
    FILES --> RAYON
    RAYON --> PY & TS & RS & GO & JAVA & GEN
    PY & TS & RS & GO & JAVA & GEN --> RESULTS
    RESULTS --> NODES --> EDGES --> CAUSAL
    CAUSAL --> REFS --> PROX
    PROX --> HINTS --> XFILE
    XFILE --> CSR --> BIDIR --> REV --> PR

Phase 1: Directory Walking

DirectoryWalker uses the walkdir crate to traverse the filesystem. It applies skip rules, detects binary files, and enriches results with git metadata.

Skip Rules

Default skip directories (configured via IngestConfig):

#![allow(unused)]
fn main() {
skip_dirs: vec![
    ".git", "node_modules", "__pycache__", ".venv",
    "target", "dist", "build", ".next", "vendor",
],
skip_files: vec![
    "package-lock.json", "yarn.lock", "Cargo.lock", "poetry.lock",
],
}

Hidden directories (starting with .) are skipped unless they are the root. Symlinks are not followed.

Binary Detection (FM-ING-004)

After discovering a file, the walker reads the first 8KB and checks for NUL bytes (0x00). Any file containing NUL is classified as binary and skipped. This prevents feeding compiled binaries, images, or other non-text files into the language extractors.

Git Enrichment

If the root is inside a git repository, the walker runs git log --format=%at --name-only to extract:

  • Commit count per file: Used to compute change_frequency (1 commit = 0.1, 50+ = 1.0, capped).
  • Most recent commit timestamp: Used for temporal decay scoring.
  • Commit groups: Sets of files that changed together in the same commit. Fed into the CoChangeMatrix after graph finalization.

The result is a WalkResult containing Vec<DiscoveredFile> and Vec<Vec<String>> commit groups.

#![allow(unused)]
fn main() {
pub struct DiscoveredFile {
    pub path: PathBuf,
    pub relative_path: String,
    pub extension: Option<String>,
    pub size_bytes: u64,
    pub last_modified: f64,
    pub commit_count: u32,
    pub last_commit_time: f64,
}
}

Phase 2: Parallel Extraction

Files are distributed across rayon’s thread pool for concurrent extraction. Each file is assigned a language-specific extractor based on its extension:

ExtensionExtractorExtracted Entities
.py, .pyiPythonExtractorClasses, functions, decorators, imports, global assignments
.ts, .tsx, .js, .jsx, .mjs, .cjsTypeScriptExtractorClasses, functions, interfaces, type aliases, imports, exports
.rsRustExtractorStructs, enums, traits, impls, functions, modules, macros
.goGoExtractorStructs, interfaces, functions, methods, packages
.javaJavaExtractorClasses, interfaces, methods, fields, packages
everything elseGenericExtractorFile-level node with tag extraction from content

Extractor Interface

All extractors implement the Extractor trait:

#![allow(unused)]
fn main() {
pub trait Extractor: Send + Sync {
    fn extract(&self, content: &[u8], file_id: &str) -> M1ndResult<ExtractionResult>;
    fn extensions(&self) -> &[&str];
}
}

An ExtractionResult contains:

#![allow(unused)]
fn main() {
pub struct ExtractionResult {
    pub nodes: Vec<ExtractedNode>,
    pub edges: Vec<ExtractedEdge>,
    pub unresolved_refs: Vec<String>,
}
}

Each extracted node carries:

#![allow(unused)]
fn main() {
pub struct ExtractedNode {
    pub id: String,        // e.g. "file::backend/handler.py::ChatHandler"
    pub label: String,     // e.g. "ChatHandler"
    pub node_type: NodeType,
    pub tags: Vec<String>,
    pub line: u32,
    pub end_line: u32,
}
}

Comment and String Stripping

Before extraction, each file’s content passes through strip_comments_and_strings() which removes comments and string literals to prevent false-positive matches from regex extractors. The function preserves import line string content (so from "react" still resolves) but strips string bodies elsewhere.

Comment syntax is per-language:

#![allow(unused)]
fn main() {
pub struct CommentSyntax {
    pub line: &'static str,         // e.g. "//" or "#"
    pub block_open: &'static str,   // e.g. "/*"
    pub block_close: &'static str,  // e.g. "*/"
}
}

Supported: Rust (//, /* */), Python (#, """ """), C-style (//, /* */), Go (//, /* */), Generic (#, none).

Node ID Format

Extracted nodes use a hierarchical ID scheme: file::{relative_path}::{entity_name}. For example:

  • file::backend/handler.py (file node)
  • file::backend/handler.py::ChatHandler (class node)
  • file::backend/handler.py::ChatHandler::handle_message (method node)

Unresolved references use the prefix ref::: ref::Config, ref::react. These are resolved to actual nodes in Phase 4.

Phase 3: Graph Building

After parallel extraction completes, results are collected and processed sequentially (graph mutation is single-threaded).

Node Creation

For each ExtractedNode:

  1. Look up the file timestamp from git enrichment (or filesystem mtime).
  2. Compute change_frequency from git commit count: (commits / 50).clamp(0.1, 1.0). Default 0.3 for non-git repos.
  3. Call graph.add_node() with the external ID, label, node type, tags, timestamp, and change frequency.
  4. Set provenance: source_path, line_start, line_end, namespace="code".
  5. On DuplicateNode error, increment collision counter and continue.

Edge Creation

For each ExtractedEdge (skipping ref:: targets, which are deferred):

  1. Resolve source and target IDs to NodeId.
  2. Assign causal strength by relation type:
RelationCausal StrengthDirection
contains0.8Bidirectional
implements0.7Bidirectional
imports0.6Forward
calls0.5Forward
references0.3Forward
other0.4Forward

contains and implements edges are bidirectional so that both parent-to-child and child-to-parent navigation work.

Safety Guards (FM-ING-002)

Two budget checks run between sequential file processing:

  • Timeout: If start.elapsed() > config.timeout (default 300s), stop processing.
  • Node budget: If nodes >= config.max_nodes (default 500K), stop processing.

Both log a warning and break from the build loop, producing a partial but consistent graph from whatever was processed.

Phase 4: Reference Resolution

ReferenceResolver

Unresolved references (ref::Config, ref::FastAPI, etc.) are resolved to actual graph nodes using the ReferenceResolver.

Multi-value label index (FM-ING-008): The resolver builds a HashMap from labels to lists of matching NodeIds. When multiple nodes share a label (e.g., multiple files define a Config class), proximity disambiguation selects the best match:

ProximityScoreCondition
Same file100Source and target share the same file:: prefix
Same directory50Source and target share the same directory
Same project10Default (both exist in the graph)

Import hint disambiguation: When the extractor sees from foo.bar import Baz, it records an import hint mapping (source_file, "ref::Baz") to the module path foo.bar. The resolver uses this hint to prefer the Baz node under foo/bar/ over a same-named node elsewhere.

Resolution outcome per reference:

  • Resolved: Exactly one match (or best proximity match). Edge created with resolved NodeId.
  • Ambiguous: Multiple matches with equal proximity. Best guess selected, counted in stats.
  • Unresolved: No match found. Counted in stats, no edge created.

Cross-File Edges

After reference resolution, cross_file.rs generates structural edges that span files:

  • Directory contains: dir::backend/ --contains--> file::backend/main.py
  • Sibling edges: Files in the same directory get weak bidirectional edges.

Phase 5: Finalization

Graph.finalize() transforms the mutable graph into its read-optimized CSR form:

  1. Sort edges by source: All pending edges are sorted by source.0 (node index).
  2. Build forward CSR: Compute offsets array, pack targets/weights/relations/etc into parallel arrays.
  3. Expand bidirectional edges: For each bidirectional edge (A, B), ensure both A->B and B->A exist in the CSR.
  4. Build reverse CSR: Sort edges by target, build rev_offsets, rev_sources, rev_edge_idx (mapping back to forward array indices).
  5. Rebuild plasticity arrays: Allocate PlasticityNode for each node with default ceiling.
  6. Compute PageRank: Power iteration with damping 0.85, max 50 iterations, convergence 1e-6.

After finalization, the graph is immutable (except for atomic weight updates by plasticity).

Incremental Ingestion (GraphDiff)

diff.rs enables incremental updates without full re-ingestion.

#![allow(unused)]
fn main() {
pub enum DiffAction {
    AddNode(ExtractedNode),
    RemoveNode(String),
    ModifyNode { external_id, new_label, new_tags, new_last_modified },
    AddEdge(ExtractedEdge),
    RemoveEdge { source_id, target_id, relation },
    ModifyEdgeWeight { source_id, target_id, relation, new_weight },
}
}

GraphDiff::compute() compares old and new extraction results by indexing both into HashMaps by external ID, then classifying each node/edge as added, removed, or modified.

GraphDiff::apply() executes the diff against a live graph. Note: CSR does not support true node/edge removal. “Removed” nodes are tombstoned (zero weight, empty label) rather than physically deleted. A full re-ingest is needed to reclaim space.

When to use incremental vs full:

ScenarioStrategy
Single file changedIncremental diff (fast, ~10ms)
Many files changed (>20%)Full re-ingest (cleaner CSR, correct PageRank)
New codebaseFull ingest
Plasticity state importantFull ingest + plasticity reimport (triple matching)

Memory Adapter

MemoryIngestAdapter converts markdown documents into graph nodes. It implements the IngestAdapter trait with domain "memory".

Supported Formats

Files with extensions .md, .markdown, or .txt are accepted. The adapter walks a directory of memory files and parses each one into:

  • Section nodes (NodeType::Concept): Created from markdown headings (#, ##, etc.).
  • Entry nodes (NodeType::Process): Created from list items under sections.
  • File reference nodes (NodeType::Reference): Created from file paths mentioned in content.

Entry Classification

List items are classified by content patterns:

ClassificationPatternExample
TaskContains “TODO”, “FIXME”, “pending”, “implement”“- TODO: add tests”
DecisionContains “decision:”, “decided:”, “chose”“- Decision: use CSR format”
StateContains “status:”, “state:”, “current:”“- Status: in progress”
EventContains date pattern (YYYY-MM-DD)“- 2026-03-12: deployed”
NoteDefault“- Config lives in settings.py”

Edges

The adapter creates:

  • contains edges from section to child entries.
  • references edges from entries to file reference nodes.
  • follows edges between sequential entries in the same section.

This allows the activation engine to traverse from a concept (“plasticity”) through memory entries to referenced code files, bridging the semantic gap between human notes and source code.

IngestAdapter Trait

The IngestAdapter trait enables domain-specific ingestion beyond code:

#![allow(unused)]
fn main() {
pub trait IngestAdapter: Send + Sync {
    fn domain(&self) -> &str;
    fn ingest(&self, root: &Path) -> M1ndResult<(Graph, IngestStats)>;
}
}

Implemented adapters:

AdapterDomainInput
Ingestor"code"Source code directories
MemoryIngestAdapter"memory"Markdown/text documents
JsonIngestAdapter"generic"Arbitrary JSON with nodes[] and edges[]

The JSON adapter is the escape hatch for importing graphs from external tools. It expects a JSON document with nodes (array of {id, label, type, tags}) and edges (array of {source, target, relation, weight}).

Configuration Reference

#![allow(unused)]
fn main() {
pub struct IngestConfig {
    pub root: PathBuf,
    pub timeout: Duration,          // default: 300s
    pub max_nodes: u64,             // default: 500_000
    pub skip_dirs: Vec<String>,     // default: [".git", "node_modules", ...]
    pub skip_files: Vec<String>,    // default: ["package-lock.json", ...]
    pub parallelism: usize,         // default: 8 (rayon threads)
}
}

Statistics

Every ingest run produces IngestStats:

#![allow(unused)]
fn main() {
pub struct IngestStats {
    pub files_scanned: u64,
    pub files_parsed: u64,
    pub files_skipped_binary: u64,
    pub files_skipped_encoding: u64,
    pub nodes_created: u64,
    pub edges_created: u64,
    pub references_resolved: u64,
    pub references_unresolved: u64,
    pub label_collisions: u64,
    pub elapsed_ms: f64,
    pub commit_groups: Vec<Vec<String>>,
}
}

commit_groups is passed to the CoChangeMatrix in m1nd-core after graph finalization, seeding the temporal co-change model with real git history.

MCP Server (m1nd-mcp)

m1nd-mcp is the transport and session layer. It exposes m1nd-core and m1nd-ingest as 43 MCP tools over JSON-RPC stdio, manages the shared graph lifecycle, handles multi-agent sessions, and provides perspective branching and lock-based change tracking.

Source: mcp/m1nd/m1nd-mcp/src/

Module Map

ModulePurpose
main.rsBinary entry point, config loading, tokio runtime, SIGINT handling
server.rsMcpServer, JSON-RPC transport (framed + line), tool schema registry
session.rsSessionState, engine lifecycle, auto-persist, perspective/lock management
tools.rsTool handler implementations for all 43 MCP tools
engine_ops.rsRead-only engine wrappers for perspective synthesis
protocol.rsJSON-RPC request/response types
perspective/Perspective branching, lock state, watcher events
layer_handlers.rsLayer-based tool dispatch

Transport Layer

Dual Transport Mode

m1nd-mcp accepts two JSON-RPC transport formats on stdin, auto-detected per message:

Framed mode (HTTP-style headers):

Content-Length: 142\r\n
\r\n
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"m1nd.activate","arguments":{"query":"chat","agent_id":"orchestrator"}},"id":1}

Line mode (raw JSON):

{"jsonrpc":"2.0","method":"tools/call","params":{"name":"m1nd.activate","arguments":{"query":"chat","agent_id":"orchestrator"}},"id":1}

Detection is based on the first non-whitespace byte: if it is { or [, the message is treated as line-mode JSON. Otherwise, it is parsed as framed with Content-Length headers. Responses are written in the same mode as the incoming request.

#![allow(unused)]
fn main() {
fn read_request_payload<R: BufRead>(
    reader: &mut R,
) -> std::io::Result<Option<(String, TransportMode)>> {
    loop {
        let buffer = reader.fill_buf()?;
        if buffer.is_empty() { return Ok(None); }
        let first_non_ws = buffer.iter().copied()
            .find(|byte| !byte.is_ascii_whitespace());
        let starts_framed = matches!(first_non_ws, Some(byte) if byte != b'{' && byte != b'[');
        // ...
    }
}
}

This dual-mode support allows m1nd to work with both Claude Code (which uses framed headers) and other MCP clients that send raw line JSON.

Request/Response Format

Requests follow the JSON-RPC 2.0 specification with MCP conventions:

{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": {
        "name": "m1nd.activate",
        "arguments": {
            "query": "chat handler",
            "agent_id": "orchestrator",
            "top_k": 20,
            "dimensions": ["structural", "semantic", "temporal", "causal"],
            "xlr": true
        }
    },
    "id": 1
}

Responses:

{
    "jsonrpc": "2.0",
    "result": {
        "content": [{
            "type": "text",
            "text": "..."
        }]
    },
    "id": 1
}

The tools/list method returns all 43 tool schemas with full inputSchema per MCP spec, enabling auto-discovery by any MCP client.

Server Lifecycle

sequenceDiagram
    participant Main as main.rs
    participant Server as McpServer
    participant Session as SessionState
    participant Tokio as tokio runtime

    Main->>Main: load_config()
    Main->>Server: McpServer::new(config)
    Server->>Session: SessionState::initialize(graph, config, domain)
    Session->>Session: Build all engines (orchestrator, temporal, counterfactual, topology, resonance, plasticity)
    Main->>Server: server.start()
    Main->>Tokio: spawn_blocking(server.serve())

    loop Until EOF or SIGINT
        Tokio->>Server: read JSON-RPC request
        Server->>Server: dispatch to tool handler
        Server->>Session: execute tool logic
        Session->>Session: auto-persist check
        Server->>Tokio: write JSON-RPC response
    end

    Tokio->>Server: server.shutdown()
    Server->>Session: final persist

Configuration

Configuration is resolved in priority order: CLI argument (JSON file path) > environment variables > defaults.

#![allow(unused)]
fn main() {
pub struct McpConfig {
    pub graph_source: PathBuf,          // default: "./graph_snapshot.json"
    pub plasticity_state: PathBuf,      // default: "./plasticity_state.json"
    pub auto_persist_interval: u32,     // default: 50 (queries between persists)
    pub learning_rate: f32,             // default: 0.08
    pub decay_rate: f32,                // default: 0.005
    pub xlr_enabled: bool,             // default: true
    pub max_concurrent_reads: usize,    // default: 32
    pub write_queue_size: usize,        // default: 64
    pub domain: Option<String>,         // default: None ("code")
}
}

Environment variables: M1ND_GRAPH_SOURCE, M1ND_PLASTICITY_STATE, M1ND_XLR_ENABLED.

Startup Sequence

  1. load_config(): Resolve config from CLI args, env vars, or defaults.
  2. McpServer::new(config): Load graph snapshot from disk (or create empty graph). Load plasticity state. Initialize SessionState with all engines.
  3. server.start(): Prepare the server for serving (no-op currently, reserved for future setup).
  4. tokio::task::spawn_blocking(server.serve()): The serve loop does synchronous stdio I/O in a blocking task.
  5. tokio::select! waits for either SIGINT (ctrl_c()) or serve loop completion.

Shutdown

On SIGINT or stdin EOF:

  1. server.shutdown(): Final persist of graph and plasticity state.
  2. Process exits.

The atomic write pattern (temp file + rename) ensures that even if shutdown is interrupted, the previous snapshot remains intact.

Tool Registration and Dispatch

Schema Registry

tool_schemas() returns a JSON array of all 43 tool definitions with full inputSchema objects. Each tool specifies:

  • name: Dot-namespaced (e.g., m1nd.activate)
  • description: Human-readable purpose
  • inputSchema: JSON Schema with properties, required, type, defaults

Example schema entry:

{
    "name": "m1nd.activate",
    "description": "Spreading activation query across the connectome",
    "inputSchema": {
        "type": "object",
        "properties": {
            "query": { "type": "string" },
            "agent_id": { "type": "string" },
            "top_k": { "type": "integer", "default": 20 },
            "dimensions": {
                "type": "array",
                "items": { "type": "string", "enum": ["structural", "semantic", "temporal", "causal"] },
                "default": ["structural", "semantic", "temporal", "causal"]
            },
            "xlr": { "type": "boolean", "default": true }
        },
        "required": ["query", "agent_id"]
    }
}

Tool Categories

The 43 tools are organized into functional groups:

Core Query Tools (13):

ToolPurposeKey Parameters
m1nd.activateSpreading activation queryquery, top_k, dimensions, xlr
m1nd.impactBlast radius analysisnode_id, direction (forward/reverse/both)
m1nd.missingStructural hole detectionquery, min_sibling_activation
m1nd.whyPath explanation between nodessource, target, max_hops
m1nd.warmupTask-based primingtask_description, boost_strength
m1nd.counterfactualNode removal simulationnode_ids, include_cascade
m1nd.predictCo-change predictionchanged_node, top_k, include_velocity
m1nd.fingerprintEquivalence detectiontarget_node
m1nd.driftWeight changes since baselinesince
m1nd.learnHebbian feedbackfeedback (correct/wrong)
m1nd.resonateStanding wave analysisquery, frequencies, num_harmonics
m1nd.seekSeed-level node lookupquery
m1nd.scanFull graph summary(none)

Graph Mutation Tools:

ToolPurpose
m1nd.ingestIngest codebase into graph
m1nd.healthServer diagnostics
m1nd.timelineTemporal event timeline

Perspective Tools (12):

ToolPurpose
m1nd.perspective_startOpen a named perspective branch
m1nd.perspective_closeClose a perspective
m1nd.perspective_listList open perspectives for an agent
m1nd.perspective_inspectView perspective state and cached results
m1nd.perspective_compareDiff two perspectives
m1nd.perspective_branchFork a perspective
m1nd.perspective_suggestGenerate suggestions from perspective context
m1nd.perspective_backUndo last perspective operation
m1nd.perspective_peekRead source file content from within perspective
m1nd.perspective_followFollow links from perspective results
m1nd.perspective_routesView cached activation routes
m1nd.perspective_affinityCross-perspective affinity analysis

Lock Tools (5):

ToolPurpose
m1nd.lock_createCreate a baseline snapshot for change tracking
m1nd.lock_diffDiff current state against lock baseline
m1nd.lock_rebaseUpdate lock baseline to current state
m1nd.lock_releaseRelease a lock
m1nd.lock_watchWatch for changes against lock baseline

Trail Tools (4):

ToolPurpose
m1nd.trail_saveSave current exploration trail
m1nd.trail_listList saved trails
m1nd.trail_resumeResume a saved trail
m1nd.trail_mergeMerge trails

Topology Tools:

ToolPurpose
m1nd.federateCross-graph federation
m1nd.divergeDivergence analysis between graph regions
m1nd.differentialDifferential activation (compare two queries)
m1nd.hypothesizeGenerate hypotheses from graph structure
m1nd.validate_planValidate an implementation plan against graph

Dispatch

All tools require an agent_id parameter. The serve loop matches the tool name from the JSON-RPC request and dispatches to the corresponding handler in tools.rs. The handler extracts parameters from the arguments JSON object, acquires the appropriate lock on SessionState, executes the operation, and returns the result as a JSON-RPC response.

Session Management

SessionState

SessionState is the central state object. It owns the graph, all engines, and all session metadata:

#![allow(unused)]
fn main() {
pub struct SessionState {
    pub graph: SharedGraph,                    // Arc<RwLock<Graph>>
    pub domain: DomainConfig,
    pub orchestrator: QueryOrchestrator,       // activation + XLR + semantic
    pub temporal: TemporalEngine,
    pub counterfactual: CounterfactualEngine,
    pub topology: TopologyAnalyzer,
    pub resonance: ResonanceEngine,
    pub plasticity: PlasticityEngine,

    pub queries_processed: u64,
    pub auto_persist_interval: u32,            // default: 50
    pub start_time: Instant,
    pub last_persist_time: Option<Instant>,
    pub graph_path: PathBuf,
    pub plasticity_path: PathBuf,
    pub sessions: HashMap<String, AgentSession>,

    // Perspective/lock state
    pub graph_generation: u64,
    pub plasticity_generation: u64,
    pub cache_generation: u64,
    pub perspectives: HashMap<(String, String), PerspectiveState>,
    pub locks: HashMap<String, LockState>,
    pub perspective_counter: HashMap<String, u64>,
    pub lock_counter: HashMap<String, u64>,
    pub pending_watcher_events: Vec<WatcherEvent>,
    pub perspective_limits: PerspectiveLimits,
    pub peek_security: PeekSecurityConfig,
    pub ingest_roots: Vec<String>,
}
}

SharedGraph

SharedGraph = Arc<parking_lot::RwLock<Graph>> provides concurrent access:

  • Reads (activation, impact, predict, etc.): Acquire read lock. Multiple concurrent reads allowed.
  • Writes (ingest, learn): Acquire write lock. Exclusive access. Blocks all readers.

parking_lot::RwLock is used instead of std::sync::RwLock for two reasons:

  1. Writer starvation prevention: parking_lot uses a fair queue, so plasticity writes do not starve behind continuous read queries.
  2. Performance: parking_lot’s implementation is faster for the read-heavy, write-rare access pattern of m1nd.

Engine Rebuild

After ingestion replaces the graph, all engines must be rebuilt because they hold indexes derived from the old graph:

#![allow(unused)]
fn main() {
pub fn rebuild_engines(&mut self) -> M1ndResult<()> {
    {
        let graph = self.graph.read();
        self.orchestrator = QueryOrchestrator::build(&graph)?;
        self.temporal = TemporalEngine::build(&graph)?;
        self.plasticity = PlasticityEngine::new(
            &graph, PlasticityConfig::default(),
        );
    }
    self.invalidate_all_perspectives();
    self.mark_all_lock_baselines_stale();
    self.graph_generation += 1;
    self.cache_generation = self.cache_generation.max(self.graph_generation);
    Ok(())
}
}

The rebuild also invalidates all perspective and lock state, bumping generation counters so that stale caches are detected.

Auto-Persist

Every auto_persist_interval queries (default 50), the session persists state to disk:

  1. Graph first: save_graph() writes the CSR graph to JSON via atomic temp-file-then-rename.
  2. Plasticity second: export_state() extracts per-edge SynapticState, then save_plasticity_state() writes to JSON.
  3. If graph save fails, plasticity save is skipped (prevents inconsistent state).
  4. If plasticity save fails after graph succeeds, a warning is logged but the server continues.
#![allow(unused)]
fn main() {
pub fn persist(&mut self) -> M1ndResult<()> {
    let graph = self.graph.read();
    m1nd_core::snapshot::save_graph(&graph, &self.graph_path)?;
    match self.plasticity.export_state(&graph) {
        Ok(states) => {
            if let Err(e) = save_plasticity_state(&states, &self.plasticity_path) {
                eprintln!("[m1nd] WARNING: plasticity persist failed: {}", e);
            }
        }
        Err(e) => eprintln!("[m1nd] WARNING: plasticity export failed: {}", e),
    }
    self.last_persist_time = Some(Instant::now());
    Ok(())
}
}

Multi-Agent Support

Agent Sessions

Each unique agent_id gets an AgentSession:

#![allow(unused)]
fn main() {
pub struct AgentSession {
    pub agent_id: String,
    pub first_seen: Instant,
    pub last_seen: Instant,
    pub query_count: u64,
}
}

Sessions are created on first query and updated on each subsequent query. The last_seen timestamp enables timeout-based cleanup.

Agent Isolation

All agents share one graph (writes are immediately visible), but isolation is provided through:

  • Perspectives: Per-agent branching views with independent route caches. A perspective opened by Agent A is invisible to Agent B.
  • Locks: Per-agent change tracking baselines. Each agent can create independent locks to track changes from their perspective.
  • Query Memory: The plasticity engine’s ring buffer is global (shared learning), but perspective-level route caches are per-agent.

Generation Counters

Three generation counters detect stale state:

CounterBumped ByPurpose
graph_generationIngest, rebuild_enginesDetects stale engine indexes
plasticity_generationLearnDetects stale plasticity state
cache_generationmax(graph_gen, plasticity_gen)Unified staleness for perspective caches

When a perspective’s cached results were computed at a different generation than the current cache_generation, the perspective is marked stale and results are recomputed on next access.

Perspective System

Perspectives are per-agent named branches that cache activation results and enable comparative analysis.

Lifecycle

stateDiagram-v2
    [*] --> Open: perspective_start
    Open --> Active: first query (activate/impact/etc)
    Active --> Active: additional queries
    Active --> Branched: perspective_branch
    Active --> Stale: graph mutation (ingest/learn)
    Stale --> Active: re-query (auto-refresh)
    Active --> [*]: perspective_close
    Open --> [*]: perspective_close

Perspective IDs

Generated as persp_{agent_prefix}_{counter:03}. Each agent has an independent monotonic counter. Example: Agent “jimi” creates perspectives persp_jimi_001, persp_jimi_002, etc.

Resource Limits

PerspectiveLimits caps resource usage:

  • Maximum open perspectives per agent
  • Maximum total perspectives across all agents
  • Maximum locks per agent

Peek Security

perspective_peek allows reading source file content from within a perspective context. Security restrictions:

  • Files must be within an ingest_roots allow-list (populated during ingest).
  • Path traversal (..) is blocked.
  • Symlinks outside the allow-list are rejected.

Lock System

Locks capture a baseline snapshot of graph state for change tracking.

Lock Lifecycle

stateDiagram-v2
    [*] --> Created: lock_create
    Created --> Active: baseline captured
    Active --> Diffed: lock_diff (non-destructive)
    Diffed --> Active: continue tracking
    Active --> Rebased: lock_rebase (new baseline)
    Rebased --> Active: continue tracking
    Active --> Stale: graph mutation
    Stale --> Rebased: lock_rebase
    Active --> [*]: lock_release

Operations

  • lock_create: Captures current graph state (generation counter + weight snapshot for tracked edges).
  • lock_diff: Compares current state against baseline. Reports weight changes, new/removed edges. Non-destructive.
  • lock_rebase: Updates baseline to current state. Clears staleness flag.
  • lock_release: Frees the lock.
  • lock_watch: Returns pending watcher events (changes that occurred since last check).

When a graph mutation occurs (ingest, learn), all locks are marked baseline_stale = true. lock_diff reports this staleness and suggests lock_rebase.

Engine Operations (Perspective Synthesis)

engine_ops.rs provides read-only wrappers around engine operations for use within perspective synthesis. These wrappers operate under a SynthesisBudget:

  • Max calls: 8 engine calls per synthesis operation.
  • Wall-clock timeout: 500ms total.

This prevents perspective synthesis from monopolizing the server. Each wrapper (activate_readonly, impact_readonly, etc.) checks budget before executing and returns a budget-exhausted error if limits are exceeded.

Concurrency Model

flowchart TD
    STDIN["stdin (JSON-RPC)"]
    TOKIO["tokio::task::spawn_blocking"]
    SERVE["McpServer.serve()<br/>(synchronous loop)"]
    RW["SharedGraph<br/>Arc&lt;parking_lot::RwLock&lt;Graph&gt;&gt;"]
    READ["Read Lock<br/>(activate, impact, predict, ...)"]
    WRITE["Write Lock<br/>(ingest, graph swap)"]
    PERSIST["Auto-Persist<br/>(write lock on graph)"]

    STDIN --> TOKIO
    TOKIO --> SERVE
    SERVE --> READ
    SERVE --> WRITE
    SERVE --> PERSIST
    READ --> RW
    WRITE --> RW
    PERSIST --> RW

The serve loop is single-threaded (synchronous stdio I/O), but graph access is concurrent-safe through SharedGraph. Within a single request:

  • Read operations acquire a read lock (shared, non-blocking with other reads).
  • Write operations acquire a write lock (exclusive, blocks all other access).
  • Plasticity weight updates use atomic CAS (no lock needed for individual weight writes).

The tokio runtime is used solely for the select! between SIGINT and serve loop completion. All actual tool processing happens synchronously within spawn_blocking.

Spreading Activation

Spreading activation is the core query mechanism in m1nd. Instead of searching for text matches, m1nd fires a signal into a weighted graph and observes where the energy propagates. The resulting activation pattern tells you what is structurally, semantically, temporally, and causally related to your query.

The neuroscience origin

The concept comes from cognitive psychology. In 1975, Allan Collins and Elizabeth Loftus proposed that human semantic memory is organized as a network of interconnected concepts. When you think of “doctor,” activation spreads along weighted connections to “nurse,” “hospital,” “stethoscope” – but not to “bicycle.” The strength of the connection determines how much activation passes through. Distant or weak connections receive less energy. The pattern of activated nodes is the meaning.

m1nd applies this model to code. Files, functions, classes, and modules become nodes. Import relationships, call chains, type references, and co-change history become weighted edges. Querying “authentication” does not find the string “authentication” – it fires signal into nodes that match and watches energy flow outward through the graph’s structure.

Four dimensions of activation

Most spreading activation systems operate on a single graph. m1nd runs four independent activation passes and fuses the results. Each dimension captures a different kind of relationship.

D1: Structural

Graph distance, edge types, and edge weights. This is classical spreading activation – signal decays as it hops along edges. A file that imports another file is one hop away. A function that calls through two intermediate modules is three hops away. The signal strength at each node tells you how structurally close it is to the query.

The structural dimension uses m1nd’s CSR (Compressed Sparse Row) adjacency matrix with forward and reverse indices. PageRank scores computed at ingest time provide a static importance baseline.

D2: Semantic

Token overlap and naming patterns. This dimension scores nodes based on how their identifiers (file names, function names, class names) relate to the query text. m1nd uses a TF-IDF weighted trigram index built at ingest time. The trigrams for “authentication” overlap with “auth_handler,” “authenticate_user,” and “auth_middleware” – without requiring exact string matching.

Co-occurrence embeddings from short random walks on the graph add a second signal: nodes that tend to appear near each other in the graph structure score higher, even if their names share no characters.

D3: Temporal

Recency and change frequency. Files modified recently score higher than files untouched for months. Files that change often (high velocity) score higher than stable ones. The temporal dimension uses exponential decay with a 7-day half-life:

recency = exp(-0.693 * age_seconds / (168 * 3600))
score   = recency * 0.6 + frequency * 0.4

This ensures that recently active parts of the codebase surface in activation results, even if they are structurally distant.

D4: Causal

Forward and backward causal chain traversal. Edges with causal_strength > 0 (derived from call chains, error propagation paths, and stacktrace mappings) form a causal subgraph. Signal propagates forward along causal edges with full strength, and backward with a 0.7 discount factor. This surfaces nodes that cause or are caused by the query target.

The activation wavefront algorithm

m1nd implements two propagation strategies and a hybrid selector that chooses between them at runtime.

WavefrontEngine (BFS)

Breadth-first, depth-parallel propagation. All active nodes at the current depth fire simultaneously. Signal accumulates into the next depth’s buffer via a scatter-max operation: when multiple sources send signal to the same target, only the strongest arrival survives.

for each depth 0..max_depth:
    for each node in current frontier:
        if activation[node] < threshold: skip
        for each outgoing edge (node -> target):
            signal = activation[node] * edge_weight * decay
            if inhibitory: signal = -signal * inhibitory_factor
            if signal > activation[target]:
                activation[target] = signal    // scatter-max
                add target to next frontier
    frontier = next_frontier

This processes all nodes at the same distance in one pass. Optimal when many seeds fire simultaneously or the graph has high average degree.

HeapEngine (priority queue)

Max-heap propagation. The strongest signal fires first. Early-terminates when the heap top drops below threshold. Uses a Bloom filter for fast visited checks (probabilistic, with FPR ~ 1% at typical graph sizes).

Optimal for sparse queries: few seeds, low graph density. The seed-to-node ratio and average degree determine the crossover point.

HybridEngine (auto-select)

The HybridEngine chooses at runtime:

#![allow(unused)]
fn main() {
fn prefer_heap(graph: &Graph, seed_count: usize) -> bool {
    let seed_ratio = seed_count as f64 / graph.num_nodes().max(1) as f64;
    seed_ratio < 0.001 && graph.avg_degree() < 8.0
}
}

If fewer than 0.1% of nodes are seeds and the average degree is below 8, the HeapEngine wins. Otherwise, the WavefrontEngine is faster because it avoids heap overhead for dense propagation.

Seed node selection

Before activation can begin, the query text must be converted into seed nodes. The SeedFinder performs a multi-strategy match against all node labels:

StrategyRelevance scoreExample
Exact label match1.0“auth” matches node “auth”
Prefix match0.9“auth” matches “auth_handler”
Substring match0.8“auth” matches “pre_auth_check”
Tag match0.85“auth” matches node tagged “authentication”
Fuzzy trigram0.7 * sim“authenticaton” (typo) matches “authentication”

Trigram similarity uses cosine distance between character n-gram frequency vectors. A match threshold of 0.3 catches most typos without generating noise.

For enhanced matching, find_seeds_semantic runs a two-phase process: basic seed finding produces candidates, then the semantic engine re-ranks them with a 0.6/0.4 blend of basic and semantic scores. Maximum 200 seeds are returned (capped by MAX_SEEDS).

Decay and damping

Signal attenuates at every hop:

signal_at_target = signal_at_source * edge_weight * decay_factor

The default decay factor is 0.55 per hop (from DecayFactor::DEFAULT). After 3 hops, the signal has decayed to 0.55^3 = 16.6% of its original strength. After 5 hops (the default max_depth), it is at 5%. The max depth is hard-capped at 20 to prevent runaway propagation.

Additional controls:

  • Threshold (default 0.04): signal below this value is discarded. This prunes the wavefront and prevents ghost activations.
  • Saturation cap (default 1.0): seeds cannot inject more than this amount of activation. Prevents a single high-PageRank node from dominating.
  • Inhibitory factor (default 0.5): inhibitory edges (negative relationships like “replaces,” “deprecates”) apply capped proportional suppression rather than simple negation.

How dimensions are combined

After all four dimensions produce their scored node lists, the merge_dimensions function fuses them into a single ranking.

Weighted sum

Each dimension has a base weight:

#![allow(unused)]
fn main() {
pub const DIMENSION_WEIGHTS: [f32; 4] = [0.35, 0.25, 0.15, 0.25];
//                                        D1     D2     D3     D4
//                                    Struct  Seman  Temp  Causal
}

If a dimension produces no results (e.g., no temporal data available), its weight is redistributed proportionally to the active dimensions. This adaptive redistribution prevents zero-result dimensions from diluting the signal.

Resonance bonus

Nodes that appear in multiple dimensions receive a multiplicative bonus:

#![allow(unused)]
fn main() {
pub const RESONANCE_BONUS_3DIM: f32 = 1.3;  // 3 dimensions agree
pub const RESONANCE_BONUS_4DIM: f32 = 1.5;  // all 4 dimensions agree
}

A node that scores above 0.01 in all four dimensions receives a 1.5x boost. This is a powerful signal: structural proximity, naming similarity, recent change activity, and causal relationship all point to the same node. These are the nodes the agent should examine first.

The check order matters. m1nd checks dim_count >= 4 before dim_count >= 3. The original Python implementation had a dead elif branch that never triggered the 4-dimension bonus – this was fixed during the Rust port (FM-ACT-001).

PageRank boost

After dimension fusion, each node receives a small PageRank boost:

#![allow(unused)]
fn main() {
const PAGERANK_BOOST: f32 = 0.1;
node.activation += node.pagerank * PAGERANK_BOOST;
}

This ensures that structurally important nodes (high PageRank) surface slightly higher in results, all else being equal. The factor is intentionally small (10% of PageRank) to avoid overwhelming the activation signal.

Visual example

Consider a simplified graph of a backend application:

graph LR
    main["main.py<br/>PR: 0.15"]
    auth["auth.py<br/>PR: 0.12"]
    session["session.py<br/>PR: 0.08"]
    middleware["middleware.py<br/>PR: 0.10"]
    user["user_model.py<br/>PR: 0.06"]
    db["database.py<br/>PR: 0.09"]
    routes["routes.py<br/>PR: 0.11"]

    main -->|imports| auth
    main -->|imports| routes
    routes -->|imports| auth
    routes -->|imports| middleware
    auth -->|imports| session
    auth -->|imports| user
    auth -->|imports| db
    session -->|imports| db
    middleware -->|imports| session
    user -->|imports| db

    style auth fill:#ff6b6b,stroke:#333,color:#fff
    style session fill:#ffa07a,stroke:#333
    style middleware fill:#ffa07a,stroke:#333
    style user fill:#ffd700,stroke:#333
    style db fill:#ffd700,stroke:#333
    style routes fill:#ffa07a,stroke:#333
    style main fill:#ffd700,stroke:#333

Query: "authentication"

Step 1: Seed selection. auth.py matches with relevance 0.9 (prefix match). session.py matches with relevance 0.85 (tag “auth” in its tags). These become seeds with activation 0.9 and 0.85 respectively.

Step 2: Structural propagation (D1). From auth.py (0.9), signal flows to:

  • session.py: 0.9 * 1.0 * 0.55 = 0.495
  • user_model.py: 0.9 * 1.0 * 0.55 = 0.495
  • database.py: 0.9 * 1.0 * 0.55 = 0.495

From session.py (already at 0.85 from seed, plus 0.495 from structural – scatter-max keeps the higher):

  • database.py: 0.85 * 1.0 * 0.55 = 0.467 (less than existing 0.495, discarded)

Reverse propagation (via reverse CSR):

  • routes.py imports auth.py, so routes.py receives: 0.9 * 1.0 * 0.55 = 0.495
  • main.py imports auth.py, so main.py receives: 0.9 * 1.0 * 0.55 = 0.495
  • middleware.py imports session.py, so middleware.py receives: 0.85 * 1.0 * 0.55 = 0.467

Step 3: Merge. After running all four dimensions, the final ranking (with resonance bonuses for multi-dimension hits) produces:

NodeD1D2D3D4DimsBonusFinal
auth.py0.900.880.700.6541.5x0.88
session.py0.850.400.500.4541.5x0.64
user_model.py0.500.300.100.2041.5x0.40
middleware.py0.470.150.600.3041.5x0.39
database.py0.500.050.200.1031.3x0.28

The result tells the agent: start with auth.py, then examine session.py and user_model.py. The four-dimension resonance bonus pushes middleware.py ahead of database.py even though their structural scores are similar – because middleware was recently modified (temporal) and sits on a causal chain (causal).

What this means in practice

An agent calling m1nd.activate("authentication") gets back a ranked list of the 20 most relevant nodes in 31ms. It does not need to know file names, directory structure, or grep patterns. The activation pattern encodes structural proximity, naming affinity, temporal relevance, and causal relationships – fused into a single score.

Over time, as the agent provides feedback via m1nd.learn, the edge weights shift through Hebbian plasticity (see Hebbian Plasticity). The paths that led to useful results strengthen. The paths that led to noise weaken. The same query tomorrow will produce better results.

Hebbian Plasticity

m1nd’s graph learns. Every query changes the edge weights. Paths that lead to useful results get stronger. Paths that lead to noise get weaker. Over time, the graph evolves to match how your team thinks about your codebase. No other code intelligence tool does this.

The neuroscience principle

In 1949, Donald Hebb proposed a theory of synaptic learning: “When an axon of cell A is near enough to excite cell B and repeatedly or persistently takes part in firing it, some growth process or metabolic change takes place in one or both cells such that A’s efficiency, as one of the cells firing B, is increased.”

The popular summary: neurons that fire together wire together.

The converse is equally important: neurons that fire independently weaken their connections. This bidirectional learning – strengthening co-active pathways, weakening inactive ones – is the foundation of how biological neural networks adapt to experience.

m1nd applies this principle to code graphs. Nodes are modules. Edges are relationships. “Firing” means being activated by a spreading activation query. When an agent confirms that a result was useful, the edges connecting those activated nodes strengthen. When the agent marks results as wrong, those paths weaken. The graph remembers what worked.

The five-step learning cycle

Every query that passes through m1nd’s PlasticityEngine triggers a five-step update cycle. This runs automatically – no explicit training phase required.

Step 1: Hebbian strengthening

For every edge where both the source and target were activated in the query results, the weight increases:

delta_w = learning_rate * activation_source * activation_target
new_weight = min(current_weight + delta_w, weight_cap)

The default learning rate is 0.08 (from LearningRate::DEFAULT). The weight cap is 3.0 – no edge can grow stronger than 3x its original weight. This prevents runaway positive feedback.

From the source code:

#![allow(unused)]
fn main() {
// Hebbian: delta_w = lr * act_src * act_tgt
let delta = lr * src_act.get() * tgt_act;
let new_weight = (current + delta).min(cap);
}

The product activation_source * activation_target means that strongly co-activated pairs get the largest boost. A pair where both nodes scored 0.8 gets 0.08 * 0.8 * 0.8 = 0.051 added to their edge weight. A pair where one scored 0.2 gets only 0.08 * 0.8 * 0.2 = 0.013. This is faithful to Hebb’s rule: the strength of the update is proportional to the correlation of the firing.

Step 2: Synaptic decay

Edges whose source nodes were not activated in this query decay slightly:

new_weight = max(current_weight * (1 - decay_rate), weight_floor)

The default decay rate is 0.005 per query (0.5%). The weight floor is 0.05 – edges never decay below 5% of their original weight. This ensures that even unused paths retain some connectivity, preventing the graph from fragmenting.

#![allow(unused)]
fn main() {
let decay_factor = 1.0 - self.config.decay_rate.get(); // 0.995
let new_weight = (current * decay_factor).max(floor);
}

The asymmetry is intentional. Strengthening applies a fixed delta (additive). Decay applies a multiplicative factor. This means strong edges resist decay (a weight-3.0 edge loses 0.015 per query) while weak edges decay faster in relative terms (a weight-0.1 edge loses 0.0005 per query). Frequently activated paths grow monotonically. Rarely activated paths slowly fade but never disappear.

Step 3: Long-Term Potentiation / Long-Term Depression

After an edge has been strengthened 5 consecutive times (the LTP threshold), it receives a one-time +0.15 bonus:

#![allow(unused)]
fn main() {
if !graph.edge_plasticity.ltp_applied[j]
    && graph.edge_plasticity.strengthen_count[j] >= self.config.ltp_threshold
{
    let new_weight = (current + self.config.ltp_bonus.get()).min(cap);
    graph.edge_plasticity.ltp_applied[j] = true;
}
}

Conversely, after an edge has been weakened 5 consecutive times (the LTD threshold), it receives a one-time -0.15 penalty:

#![allow(unused)]
fn main() {
if !graph.edge_plasticity.ltd_applied[j]
    && graph.edge_plasticity.weaken_count[j] >= self.config.ltd_threshold
{
    let new_weight = (current - self.config.ltd_penalty.get()).max(floor);
    graph.edge_plasticity.ltd_applied[j] = true;
}
}

These thresholds model biological LTP/LTD – the transition from short-term to long-term memory. Five consecutive activations is a signal of sustained relevance, not a fluke. The one-time bonus/penalty is permanent: it does not reset, and it does not apply again for the same edge. This prevents unbounded weight inflation from repeated queries.

Step 4: Homeostatic normalization

After strengthening and LTP/LTD, the total incoming weight for each node is checked against a ceiling of 5.0:

#![allow(unused)]
fn main() {
if total_incoming > ceiling {
    let scale = ceiling / total_incoming;
    for each incoming edge:
        new_weight = current * scale;
}
}

This is homeostatic plasticity – a biological mechanism that prevents individual neurons from becoming over-stimulated. In m1nd, it prevents hub nodes (like config.py or main.py) from accumulating so much incoming weight that they dominate every activation query regardless of the actual query content.

The normalization is proportional: all incoming edges are scaled by the same factor. This preserves relative strengths while enforcing an absolute ceiling. A node with 10 incoming edges at weight 1.0 each (total 10.0) would have all edges scaled to 0.5, bringing the total to 5.0.

Step 5: Query memory recording

The query, its seeds, and its activated nodes are recorded in a bounded ring buffer (capacity: 1000 queries). This memory serves two purposes:

  1. Priming signal: future queries that share seeds with past queries get a boost from nodes that frequently appeared in those past results. This implements a form of associative memory – “things I looked at near authentication tend to be relevant when I look at authentication again.”

  2. Seed bigrams: pairs of seeds that co-occur across multiple queries are tracked. This supports the m1nd.warmup tool, which uses query memory to pre-activate frequently queried paths.

How m1nd.learn works

The automatic plasticity cycle runs on every m1nd.activate call. But agents can also provide explicit feedback via m1nd.learn:

Positive feedback: m1nd.learn(feedback="correct", node_ids=[...])

When an agent confirms that specific nodes were useful:

  1. The edges connecting those nodes are strengthened with an amplified Hebbian update (the activation values are set to 1.0 for the confirmed nodes, producing maximum delta_w).
  2. The strengthen counters increment, moving edges closer to the LTP threshold.
  3. Query memory records the confirmed nodes with high weight, boosting them in future priming signals.

Negative feedback: m1nd.learn(feedback="wrong", node_ids=[...])

When an agent marks results as irrelevant:

  1. The edges connecting those nodes receive decay as if they were inactive, even though they were activated.
  2. The weaken counters increment, moving edges closer to the LTD threshold.
  3. Query memory records the rejection, reducing the priming signal for those nodes.

This feedback loop is what makes m1nd adaptive. A team that mostly works on the payment system will gradually strengthen all paths around payment-related modules. An agent investigating authentication will produce different results than an agent investigating billing – even on the same codebase – because their feedback histories have shaped different edge weight landscapes.

Plasticity state persistence

The learned weights are valuable. Losing them means losing the graph’s adaptation to your workflow. m1nd persists plasticity state in two ways:

Per-edge state (SynapticState)

Each edge’s plasticity state is captured as a serializable record:

#![allow(unused)]
fn main() {
pub struct SynapticState {
    pub source_label: String,
    pub target_label: String,
    pub relation: String,
    pub original_weight: f32,
    pub current_weight: f32,
    pub strengthen_count: u16,
    pub weaken_count: u16,
    pub ltp_applied: bool,
    pub ltd_applied: bool,
}
}

This is exported via PlasticityEngine::export_state() and persisted to M1ND_PLASTICITY_STATE (a JSON file). The export includes a NaN firewall (FM-PL-001): any non-finite weight falls back to the original weight. The write is atomic (temp file + rename, FM-PL-008) to prevent corruption on crash.

Importing state

When m1nd restarts, import_state restores learned weights. Edge identity matching uses (source_label, target_label, relation) triples – not numeric indices – because re-ingesting the codebase may produce different node numbering. This means plasticity survives codebase re-ingestion: if auth.py -> session.py was strengthened, that strengthening persists even if auth.py gets a different NodeId after re-ingest.

Weights are clamped to [weight_floor, weight_cap] on import. Invalid JSON triggers a schema validation error (FM-PL-007) rather than corrupting the graph.

Persistence frequency

The graph auto-persists every 50 queries and on server shutdown. This is a balance between durability (don’t lose too much learning) and disk I/O (don’t write on every query).

The drift tool

After persistence, the natural question is: how much has the graph changed? The m1nd.drift tool answers this.

m1nd.drift compares the current edge weights against their original (ingest-time) baselines and reports:

  • Total edges changed: how many edges have weights different from their original values.
  • Average weight change: the mean absolute delta across all modified edges.
  • Top strengthened edges: the edges that have grown the most relative to their baseline.
  • Top weakened edges: the edges that have decayed the most.
  • LTP/LTD counts: how many edges have crossed the long-term potentiation or depression thresholds.

This is designed for session recovery. When an agent starts a new session, m1nd.drift tells it what has changed since the graph was last loaded. The agent can see that “paths around the payment module strengthened significantly since yesterday” and adjust its investigation accordingly.

Session 1:
  ingest -> activate("auth") -> agent uses results -> learn(correct)
  → 740 edges strengthened, 12,340 edges decayed slightly

Session 2:
  drift(since=session_1) -> auth paths now 15% stronger on average
  activate("auth") -> better results, faster convergence to useful nodes

Session N:
  the graph has adapted to how your team thinks about auth

How this makes the graph adapt

The combination of automatic Hebbian updates, explicit feedback, LTP/LTD thresholds, and homeostatic normalization creates a self-tuning system:

  1. Short-term adaptation (within a session): edges on frequently queried paths strengthen immediately. The next query about the same topic converges faster.

  2. Long-term memory (across sessions): edges that cross the LTP threshold receive a permanent bonus. Persistent investigation patterns are encoded in the graph structure.

  3. Forgetting (controlled decay): paths that are never queried slowly fade toward the weight floor. This prevents the graph from becoming saturated with historical patterns that no longer reflect the codebase’s structure.

  4. Stability (homeostatic normalization): no node can accumulate unbounded incoming weight. Hub nodes stay important but do not become black holes that absorb all activation energy.

The result is a graph that starts generic (all edges at their ingest-time weights, reflecting code structure) and gradually becomes specific (edges weighted by how you use the codebase). Two teams working on the same repository will develop different plasticity landscapes. This is a feature: the graph models the team’s mental model, not just the code’s static structure.

Constants reference

ParameterDefaultPurpose
DEFAULT_LEARNING_RATE0.08Hebbian delta_w scaling
DEFAULT_DECAY_RATE0.005Per-query inactive edge decay
LTP_THRESHOLD5Consecutive strengthens for long-term bonus
LTD_THRESHOLD5Consecutive weakens for long-term penalty
LTP_BONUS0.15One-time weight bonus at LTP threshold
LTD_PENALTY0.15One-time weight penalty at LTD threshold
HOMEOSTATIC_CEILING5.0Max total incoming weight per node
WEIGHT_FLOOR0.05Minimum edge weight (never decays below)
WEIGHT_CAP3.0Maximum edge weight (never strengthens above)
DEFAULT_MEMORY_CAPACITY1000Ring buffer size for query memory
CAS_RETRY_LIMIT64Atomic weight update retries

XLR Noise Cancellation

Every code graph is noisy. Utility modules, shared constants, logging wrappers – they connect to everything, and they show up in every activation query whether they are relevant or not. m1nd solves this with a technique borrowed from professional audio engineering: differential signal processing with inverted channels. The same principle that makes balanced XLR cables immune to electromagnetic interference makes m1nd’s activation queries immune to structural noise.

The origin: balanced audio cables

In a recording studio, audio signals travel over long cables. The cable acts as an antenna, picking up electromagnetic interference (EMI) from power supplies, lighting, and radio transmissions. An unbalanced cable delivers a noisy signal: the audio you want plus the interference you do not.

A balanced XLR cable solves this by carrying two copies of the audio signal: one normal (hot, pin 2) and one inverted (cold, pin 3). Both signals travel the same physical path, so they pick up the same interference. At the receiving end, the cold signal is flipped back and added to the hot signal. The audio doubles in strength. The interference – identical on both channels – cancels out.

Hot channel:  signal + noise
Cold channel: -signal + noise

At receiver:  hot - cold = (signal + noise) - (-signal + noise) = 2 * signal

The noise vanishes. The signal survives.

The problem in code graphs

When you ask m1nd “what is related to the payment system?”, structural spreading activation fires from payment-related seed nodes and propagates outward. The result includes genuinely relevant modules (billing, invoicing, refunds) but also noisy modules that connect to everything:

  • config.py – imported by 40+ files
  • logger.py – called from every module
  • database.py – the persistence layer for everything
  • utils.py – helper functions used everywhere
  • __init__.py – package marker files

These modules score high in structural activation not because they are relevant to payments, but because they are structurally central. They are the electromagnetic interference of code graphs: noise that correlates with structure, not with the query.

Text search (grep, ripgrep) cannot solve this. It finds files that contain the word “payment,” which misses files that handle payments but never use that string. Static analysis cannot solve it either – the call graph from payment modules genuinely includes config.py and logger.py.

The problem is not that these edges are wrong. They are structurally correct. The problem is that they carry noise mixed in with signal, and the noise is stronger.

How m1nd implements differential processing

m1nd’s AdaptiveXlrEngine implements a six-stage pipeline that mirrors the physics of balanced XLR cables.

Stage 1: Select anti-seeds

The cold channel needs its own starting points – nodes that represent “not the query.” m1nd calls these anti-seeds. Good anti-seeds are structurally similar to the seeds (similar degree, similar graph position) but semantically distant (low neighborhood overlap).

The selection criteria:

#![allow(unused)]
fn main() {
// Degree ratio filter: anti-seed must have similar connectivity
if avg_seed_degree > 0.0 {
    let ratio = degree / avg_seed_degree;
    if ratio < 0.3 { continue; }  // Too peripheral
}

// Jaccard similarity: anti-seed must have different neighbors
let jaccard = intersection as f32 / union_size as f32;
if jaccard > 0.2 { continue; }  // Too similar to seeds
}

A node with similar degree but different neighbors is the ideal anti-seed. It will activate the same noisy hub nodes (because it has similar connectivity) but different domain-specific nodes (because it lives in a different part of the graph).

By default, 3 anti-seeds are selected.

Stage 2: Compute seed immunity

Nodes close to the seeds – within 2 BFS hops – are marked as immune to cold-channel cancellation. This prevents the anti-seed signal from cancelling legitimate results near the query target.

#![allow(unused)]
fn main() {
// BFS from seeds, mark everything within immunity_hops as immune
while let Some((node, d)) = queue.pop_front() {
    if d >= self.params.immunity_hops { continue; }
    for each neighbor:
        immune[neighbor] = true;
        queue.push_back((neighbor, d + 1));
}
}

Immunity is the key insight that prevents over-cancellation. Without it, the cold channel could erase results that are genuinely close to the query. With it, only distant noise gets cancelled.

Stage 3: Propagate hot signal

Spectral pulses propagate from the seed nodes at frequency F_HOT = 1.0. Each pulse carries an amplitude, a phase, and a frequency. The signal decays along edges and attenuates at inhibitory edges (by INHIBITORY_COLD_ATTENUATION = 0.5).

#![allow(unused)]
fn main() {
let pulse = SpectralPulse {
    node: seed,
    amplitude: seed_activation,
    phase: 0.0,
    frequency: F_HOT,  // 1.0
    hops: 0,
    prev_node: seed,
};
}

Propagation is budget-limited (default 50,000 total pulses across hot and cold channels) to prevent runaway computation on dense graphs.

Stage 4: Propagate cold signal

The same propagation runs from the anti-seed nodes at frequency F_COLD = 3.7. The cold frequency is intentionally different from the hot frequency. This spectral separation allows m1nd to distinguish which contributions came from which channel.

#![allow(unused)]
fn main() {
let cold_freq = PosF32::new(F_COLD).unwrap();  // 3.7
let anti_seed_pairs: Vec<(NodeId, FiniteF32)> = anti_seeds
    .iter()
    .map(|&n| (n, FiniteF32::ONE))
    .collect();
let cold_pulses = self.propagate_spectral(
    graph, &anti_seed_pairs, cold_freq, config, half_budget
)?;
}

Stage 5: Adaptive differential

At each node, the hot and cold amplitudes are combined. Immune nodes receive the full hot signal with no cold cancellation. Non-immune nodes get the differential:

#![allow(unused)]
fn main() {
let effective_cold = if immune { 0.0 } else { cold_amp[i] };
let raw = hot - effective_cold;
}

The raw differential is then modulated by local graph density. Nodes with degree near the graph average get density 1.0. High-degree nodes (hubs) get density > 1.0, amplifying the differential effect. Low-degree nodes (leaves) get density < 1.0, attenuating it. This is density-adaptive strength – a hub with both hot and cold signal has its differential amplified because hubs are the primary source of noise.

#![allow(unused)]
fn main() {
let density = if avg_deg > 0.0 {
    (out_deg / avg_deg).max(DENSITY_FLOOR).min(DENSITY_CAP)
} else {
    1.0
};
}

Density is clamped to [0.3, 2.0] to prevent extreme values from distorting results.

Stage 6: Sigmoid gating

The density-modulated differential passes through a sigmoid gate:

#![allow(unused)]
fn main() {
pub fn sigmoid_gate(net_signal: FiniteF32) -> FiniteF32 {
    let x = net_signal.get() * SIGMOID_STEEPNESS;  // * 6.0
    let clamped = x.max(-20.0).min(20.0);
    let result = 1.0 / (1.0 + (-clamped).exp());
    FiniteF32::new(result)
}
}

The sigmoid converts the raw differential (which can be any value) into a smooth activation in [0, 1]. The steepness factor of 6.0 creates a sharp transition: small positive differentials map close to 0.5, strong positive differentials map close to 1.0, and negative differentials (more cold than hot) map close to 0.0 and are effectively suppressed.

graph LR
    subgraph "XLR Pipeline"
        S["Seeds<br/>F=1.0 (hot)"] --> HP["Hot<br/>propagation"]
        AS["Anti-seeds<br/>F=3.7 (cold)"] --> CP["Cold<br/>propagation"]
        HP --> DIFF["Differential<br/>hot - cold"]
        CP --> DIFF
        DIFF --> DEN["Density<br/>modulation"]
        DEN --> SIG["Sigmoid<br/>gating"]
        SIG --> OUT["Clean<br/>activation"]
    end

    subgraph "Immunity"
        IMM["2-hop BFS<br/>from seeds"] -.->|"immune = true"| DIFF
    end

    style S fill:#ff6b6b,stroke:#333,color:#fff
    style AS fill:#4dabf7,stroke:#333,color:#fff
    style OUT fill:#51cf66,stroke:#333,color:#fff
    style IMM fill:#ffd43b,stroke:#333

Over-cancellation fallback

If the cold channel is too aggressive – cancelling all hot signal and leaving zero results – m1nd triggers a fallback (FM-XLR-010):

#![allow(unused)]
fn main() {
let fallback = all_zero && !hot_pulses.is_empty();
if fallback {
    // Return hot-only (no cancellation)
    activations.clear();
    for i in 0..n {
        if hot_amp[i] > 0.01 {
            activations.push((NodeId::new(i as u32), FiniteF32::new(hot_amp[i])));
        }
    }
}
}

This ensures that XLR noise cancellation never makes results worse than plain activation. It can either improve them (by removing noise) or fall back to the unfiltered result. The xlr_fallback_used flag in the activation result tells the caller that this happened.

Before and after

Without XLR

Query: “payment processing”

RankNodeActivationRelevant?
1config.py0.82No – imports everywhere
2payment_handler.py0.78Yes
3database.py0.75No – generic persistence
4logger.py0.72No – called from everything
5billing.py0.70Yes
6utils.py0.68No – shared utilities
7invoice.py0.65Yes
8middleware.py0.63No – request pipeline
9refund.py0.60Yes
10routes.py0.58No – URL dispatch

Signal-to-noise ratio: 4 relevant out of 10.

With XLR

The anti-seeds (e.g., nodes from the user management subsystem) produce cold signal that reaches the same hub nodes – config.py, database.py, logger.py, utils.py, middleware.py, routes.py. These hubs receive both hot and cold signal. The differential cancels them.

Payment-specific nodes (payment_handler.py, billing.py, invoice.py, refund.py) receive hot signal but little cold signal, because the anti-seeds live in a different part of the graph. Their differential stays high.

RankNodeHotColdDifferentialRelevant?
1payment_handler.py0.780.050.73Yes
2billing.py0.700.080.62Yes
3invoice.py0.650.060.59Yes
4refund.py0.600.040.56Yes
5payment_models.py0.550.030.52Yes
6stripe_adapter.py0.500.020.48Yes
7config.py0.820.790.03No (cancelled)
8database.py0.750.710.04No (cancelled)

Signal-to-noise ratio: 6 relevant out of 8.

The hub nodes did not disappear entirely – they still have small positive differentials. But they dropped from ranks 1, 3, 4, 6 to ranks 7, 8. The payment-specific nodes rose to the top.

Why the spectral model matters

The use of different frequencies for hot (1.0) and cold (3.7) channels enables a richer cancellation model than simple subtraction. The spectral_overlap function measures how much the hot and cold frequency distributions overlap at each node:

#![allow(unused)]
fn main() {
// Overlap = sum(min(hot_buckets, cold_buckets)) / sum(hot_buckets)
}

Nodes where hot and cold signals arrive at similar frequencies have high spectral overlap – these are the noise nodes that respond to any query. Nodes where the hot signal dominates a different frequency band than the cold signal have low spectral overlap – these are the signal nodes that respond specifically to this query.

This is directly analogous to common-mode rejection in electrical engineering: the interference is the component that appears identically on both channels, and the signal is the component that differs.

Constants reference

ParameterValuePurpose
F_HOT1.0Hot channel frequency
F_COLD3.7Cold channel frequency
SPECTRAL_BANDWIDTH0.8Gaussian kernel width for overlap
IMMUNITY_HOPS2BFS depth for seed immunity
SIGMOID_STEEPNESS6.0Sharpness of activation gate
SPECTRAL_BUCKETS20Resolution of frequency overlap
DENSITY_FLOOR0.3Minimum density modulation
DENSITY_CAP2.0Maximum density modulation
INHIBITORY_COLD_ATTENUATION0.5Cold signal reduction at inhibitory edges
Default anti-seeds3Number of cold-channel origins
Default pulse budget50,000Total pulse limit (hot + cold)

Structural Holes

Most developer tools find things that exist. m1nd finds things that should exist but do not. A missing error handler. A test file that was never written. A validation function that every sibling module has except one. These are structural holes – gaps in the code graph that reveal omissions, risks, and design inconsistencies.

The network theory origin

The concept of structural holes comes from sociologist Ronald Burt’s 1992 book Structural Holes: The Social Structure of Competition. Burt observed that in social networks, the most valuable positions are not the most connected ones – they are the ones that bridge otherwise disconnected groups. The gap between two groups is a structural hole. The person who spans it controls information flow.

Burt was studying people and organizations, but the insight transfers directly to code. In a codebase graph, a structural hole is a place where a connection should exist based on the surrounding structure but does not. The module that imports five out of six modules in a cluster but skips the sixth. The service that handles create, read, update, but not delete. The directory where every file has a test except one.

These gaps are invisible to text search. You cannot grep for something that is absent. You cannot find a missing test by scanning the test directory – you have to know what should be there and check whether it is there. This requires structural reasoning over the graph, not pattern matching over text.

How m1nd detects structural holes

m1nd detects structural holes through a combination of activation context and neighborhood analysis. The detection is embedded in the query pipeline and can also be invoked directly via the m1nd.missing tool.

The algorithm

The core insight is: a node surrounded by activated neighbors but not activated itself is probably missing something.

After a spreading activation query produces its ranked results, the structural hole detector scans every node in the graph:

#![allow(unused)]
fn main() {
pub fn detect_structural_holes(
    &self,
    graph: &Graph,
    activation: &ActivationResult,
    min_sibling_activation: FiniteF32,
) -> M1ndResult<Vec<StructuralHole>> {
    // Build activation lookup
    let mut act_map = vec![0.0f32; n];
    for a in &activation.activated {
        act_map[a.node.as_usize()] = a.activation.get();
    }

    for each node i:
        // Skip if already activated (it is in the results)
        if act_map[i] > 0.01: continue

        // Count activated neighbors
        for each neighbor of i:
            if act_map[neighbor] > min_sibling_activation:
                activated_neighbors += 1
                neighbor_act_sum += act_map[neighbor]

        // If 2+ neighbors are activated but this node is not: structural hole
        if activated_neighbors >= 2:
            holes.push(StructuralHole {
                node: i,
                sibling_avg_activation: neighbor_act_sum / activated_neighbors,
                reason: format!("{} activated neighbors (avg={:.2}) but node inactive",
                    activated_neighbors, avg),
            })
}

The threshold of 2 activated neighbors prevents false positives from nodes that happen to touch one activated node. The min_sibling_activation parameter (default 0.3) filters out weakly activated neighbors. Results are sorted by the average activation of the surrounding neighbors – the higher the average, the more conspicuous the absence.

Why this works

Consider a query about “authentication.” The activation pattern covers auth.py, session.py, middleware.py, user_model.py, and their neighbors. Now suppose auth_test.py does not exist. The test files for every other module in the auth cluster exist and are activated (because they have edges to the modules they test). auth_test.py is absent from the graph entirely. But if it did exist, it would be surrounded by activated nodes. Its absence is detectable by the gap it leaves.

In the more subtle case, auth_test.py exists but lacks tests for the password reset flow. The test file node exists and is activated, but password_reset_handler.py is activated while auth_test.py’s test coverage edges do not include it. The neighborhood analysis detects this: password_reset_handler.py’s siblings all have test edges, but this one does not.

The m1nd.missing tool

The m1nd.missing tool wraps structural hole detection in a purpose-built interface. Instead of requiring the caller to run an activation query first, m1nd.missing accepts a natural-language query, runs internal activation, and returns only the holes.

The tool exposes the detection through the MCP protocol. Behind the scenes, it:

  1. Finds seed nodes matching the query text.
  2. Runs full four-dimension spreading activation (structural, semantic, temporal, causal).
  3. Analyzes the activation pattern for structural holes using the neighborhood algorithm.
  4. Returns the top 10 holes sorted by surrounding activation strength.

Each hole includes:

  • node_id: the node at the center of the gap.
  • label: human-readable identifier.
  • node_type: file, function, class, etc.
  • reason: a description of why this is a hole (e.g., “4 activated neighbors (avg=0.72) but node inactive”).
  • sibling_avg_activation: quantifies how strongly the surrounding nodes were activated.

Real examples

Missing error handler

Query: “error handling in the payment flow”

Activation lights up: payment_handler.py, billing.py, refund.py, payment_errors.py, retry_logic.py.

Structural hole detected: webhook_handler.py has 3 activated neighbors (payment_handler.py, billing.py, payment_errors.py) but is itself not activated. Investigation reveals: the webhook handler processes Stripe callbacks but has no error handling – raw exceptions propagate to the caller. Every other module in the payment cluster catches and wraps errors via payment_errors.py. The webhook handler does not.

StructuralHole {
    node: "file::webhook_handler.py",
    sibling_avg_activation: 0.71,
    reason: "3 activated neighbors (avg=0.71) but node inactive"
}

Missing test file

Query: “authentication testing”

Activation lights up: test_auth.py, test_session.py, test_middleware.py, auth.py, session.py.

Structural hole detected: password_reset.py has 4 activated neighbors but zero activation itself. The module handles password reset logic. It imports from auth.py and session.py (both activated). It is imported by routes.py (activated). But there is no test_password_reset.py in the graph, and password_reset.py has no test-relationship edges. Every sibling module in the auth cluster has a corresponding test file. This one does not.

StructuralHole {
    node: "file::password_reset.py",
    sibling_avg_activation: 0.68,
    reason: "4 activated neighbors (avg=0.68) but node inactive"
}

Missing validation

Query: “input validation for API endpoints”

Activation lights up: validate_user_input.py, validate_payment_input.py, validate_search_params.py, schema_validator.py.

Structural hole detected: admin_routes.py has 3 activated neighbors (the validation modules it shares with other route handlers) but is not activated itself. Investigation reveals: user routes, payment routes, and search routes all import and call validation functions. Admin routes do not. The admin API accepts raw input without validation – a security risk hidden in a structural gap.

StructuralHole {
    node: "file::admin_routes.py",
    sibling_avg_activation: 0.65,
    reason: "3 activated neighbors (avg=0.65) but node inactive"
}

Why no other tool can do this

Text search (grep, ripgrep)

Text search finds strings. It cannot find the absence of a string. You cannot grep for “the test file that should exist but does not.” You would need to know in advance what you are looking for, which defeats the purpose.

Static analysis (AST, linters)

Linters can enforce specific rules (“every function must have a docstring”) but cannot detect structural patterns across the graph (“every module in this cluster has a test file except this one”). They operate on individual files, not on the relationships between files.

Embedding search finds documents similar to a query. It cannot detect the gap between documents. If test_password_reset.py does not exist, there is no document to embed. The absence is invisible.

Code coverage tools

Coverage tools tell you which lines of code are executed during tests. They can identify untested code within a file. They cannot identify missing files – a test file that was never created has zero coverage, indistinguishable from a file that exists but is not run.

m1nd’s advantage

m1nd operates on the graph structure, not on file contents. It reasons about the topology of relationships: which nodes connect to which other nodes, and where those connections are unexpectedly absent. The spreading activation query establishes a context (“what is related to authentication?”), and the structural hole detector finds gaps within that context.

This is a fundamentally different capability. It is not better text search, or better static analysis, or better coverage. It is structural reasoning about the shape of the code graph – something that requires a graph in the first place, and an activation pattern to establish context.

Connection to other concepts

Structural hole detection works best when combined with other m1nd capabilities:

  • Spreading activation (see Spreading Activation) provides the activation context that defines “which neighbors count.” Without activation, every node would be checked against every other node – too noisy to be useful.

  • Hebbian plasticity (see Hebbian Plasticity) sharpens the detection over time. As edges on well-tested paths strengthen and edges on untested paths weaken, the contrast between activated and inactive nodes increases. Holes become more conspicuous.

  • XLR noise cancellation (see XLR Noise Cancellation) removes hub-node noise from the activation pattern before hole detection runs. Without XLR, config.py being activated might cause false-positive holes in unrelated modules that happen to import config. With XLR, the activation pattern is cleaner, and the holes that surface are more meaningful.

Interpreting results

A structural hole is not necessarily a bug. It is a signal that something deviates from the surrounding pattern. Possible interpretations:

Hole typeWhat it meansAction
Missing test fileA module has no tests while its siblings doWrite tests or mark as intentionally untested
Missing error handlingA module does not use the error patterns its siblings useAdd error handling or document why it is unnecessary
Missing validationAn endpoint lacks input validation that peer endpoints haveAdd validation – likely a security gap
Missing importA module does not import a shared utility that all siblings importCheck if the module implements the functionality differently
Missing documentationA module has no doc-edges while siblings doWrite documentation or accept the gap
Intentional isolationA module is deliberately decoupled from a clusterNo action – but the hole confirms the isolation is real

The sibling_avg_activation score helps prioritize. A hole where surrounding nodes have an average activation of 0.85 is more suspicious than one at 0.35. The former means the node is deeply embedded in a strongly activated cluster and conspicuously absent. The latter means the surrounding nodes were only weakly related to the query – the “hole” may just be a node in a different domain.

API Reference Overview

m1nd exposes 43 MCP tools over JSON-RPC 2.0 via stdio. Every tool requires agent_id as a parameter. Tools are organized into seven groups.

Tool Index

Core Activation (3 tools)

ToolDescription
m1nd.activateSpreading activation query across the connectome
m1nd.warmupTask-based warmup and priming
m1nd.resonateResonance analysis: harmonics, sympathetic pairs, and resonant frequencies

Analysis (7 tools)

ToolDescription
m1nd.impactImpact radius / blast analysis for a node
m1nd.predictCo-change prediction for a modified node
m1nd.counterfactualWhat-if node removal simulation
m1nd.fingerprintActivation fingerprint and equivalence detection
m1nd.hypothesizeGraph-based hypothesis testing against structural claims
m1nd.differentialFocused structural diff between two graph snapshots
m1nd.divergeStructural drift between a baseline and current graph state

Memory & Learning (7 tools)

ToolDescription
m1nd.learnExplicit feedback-based edge adjustment
m1nd.driftWeight and structural drift analysis
m1nd.whyPath explanation between two nodes
m1nd.trail.savePersist current investigation state
m1nd.trail.resumeRestore a saved investigation
m1nd.trail.listList saved investigation trails
m1nd.trail.mergeCombine two or more investigation trails

Exploration (6 tools)

ToolDescription
m1nd.seekIntent-aware semantic code search
m1nd.scanPattern-aware structural code analysis
m1nd.missingDetect structural holes and missing connections
m1nd.traceMap runtime errors to structural root causes
m1nd.timelineGit-based temporal history for a node
m1nd.federateMulti-repository federated graph ingestion

Perspectives (12 tools)

ToolDescription
m1nd.perspective.startEnter a perspective: navigable route surface from a query
m1nd.perspective.routesBrowse the current route set with pagination
m1nd.perspective.inspectExpand a route with metrics, provenance, and affinity
m1nd.perspective.peekExtract a code/doc slice from a route target
m1nd.perspective.followFollow a route: move focus to target, synthesize new routes
m1nd.perspective.suggestGet the next best move suggestion
m1nd.perspective.affinityDiscover probable connections a route target might have
m1nd.perspective.branchFork navigation state into a new branch
m1nd.perspective.backNavigate back to previous focus
m1nd.perspective.compareCompare two perspectives on shared/unique nodes
m1nd.perspective.listList all perspectives for an agent
m1nd.perspective.closeClose a perspective and release associated locks

Lifecycle & Locks (8 tools)

ToolDescription
m1nd.ingestIngest or re-ingest a codebase, descriptor, or memory corpus
m1nd.healthServer health and statistics
m1nd.validate_planValidate a modification plan against the code graph
m1nd.lock.createPin a subgraph region and capture a baseline
m1nd.lock.watchSet a watcher strategy on a lock
m1nd.lock.diffCompute what changed in a locked region since baseline
m1nd.lock.rebaseRe-capture lock baseline from current graph
m1nd.lock.releaseRelease a lock and free its resources

Common Parameters

Every tool requires the agent_id parameter:

ParameterTypeRequiredDescription
agent_idstringYesIdentifier for the calling agent. Used for session tracking, perspective ownership, lock ownership, and multi-agent coordination.

Agent IDs are free-form strings. Convention: use the agent’s name or role identifier (e.g. "orchestrator", "auditor-1", "builder-api").

JSON-RPC Request Format

m1nd uses the MCP protocol over JSON-RPC 2.0 via stdio. All tool calls use the tools/call method.

Request structure

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "m1nd.activate",
    "arguments": {
      "agent_id": "orchestrator",
      "query": "session management"
    }
  }
}

Successful response

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{ ... pretty-printed JSON result ... }"
      }
    ]
  }
}

The text field contains the tool-specific output as a pretty-printed JSON string. Parse it to get the structured result.

Error response (tool execution error)

Tool execution errors are returned as MCP isError content, not as JSON-RPC errors:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Error: Node not found: file::nonexistent.py"
      }
    ],
    "isError": true
  }
}

Error response (protocol error)

JSON-RPC protocol errors use standard error codes:

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32601,
    "message": "Method not found: bad_method"
  }
}

Error Codes

CodeMeaning
-32700Parse error – invalid JSON
-32601Method not found – unknown JSON-RPC method
-32603Internal error – server-level failure

Tool-specific errors (node not found, perspective not found, lock ownership violation, etc.) are returned via isError: true in the content, not as JSON-RPC errors.

Transport

m1nd supports two stdio transport modes, auto-detected per message:

  • Framed: Content-Length: N\r\n\r\n{json} – standard MCP/LSP framing. Used by Claude Code, Cursor, and most MCP clients.
  • Line: {json}\n – one JSON object per line. Used by simple scripts and testing.

The server auto-detects which mode each incoming message uses and responds in the same mode.

Tool Name Normalization

Tool names accept both dots and underscores as separators. The server normalizes _ to . before dispatch:

  • m1nd.activate and m1nd_activate both work
  • m1nd.perspective.start and m1nd_perspective_start both work
  • m1nd.lock.create and m1nd_lock_create both work

Protocol Handshake

Before calling tools, MCP clients perform a handshake:

{"jsonrpc":"2.0","id":0,"method":"initialize","params":{}}

Response:

{
  "jsonrpc": "2.0",
  "id": 0,
  "result": {
    "protocolVersion": "2024-11-05",
    "serverInfo": { "name": "m1nd-mcp", "version": "0.1.0" },
    "capabilities": { "tools": {} }
  }
}

Then list available tools:

{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}

This returns the full schema for all 43 tools with inputSchema for each.

Activation Tools

Three tools for querying the connectome through spreading activation, task-based priming, and resonance analysis.


m1nd.activate

Spreading activation query across the connectome. The primary search tool – propagates signal from seed nodes through the graph across four dimensions (structural, semantic, temporal, causal), with XLR noise cancellation and optional ghost edge / structural hole detection.

Parameters

ParameterTypeRequiredDefaultDescription
querystringYesSearch query for spreading activation. Matched against node labels, tags, and provenance to find seed nodes.
agent_idstringYesCalling agent identifier.
top_kintegerNo20Number of top activated nodes to return.
dimensionsstring[]No["structural", "semantic", "temporal", "causal"]Activation dimensions to include. Each dimension contributes independently to the final activation score. Values: "structural", "semantic", "temporal", "causal".
xlrbooleanNotrueEnable XLR noise cancellation. Filters low-confidence activations to reduce false positives.
include_ghost_edgesbooleanNotrueInclude ghost edge detection. Ghost edges are probable but unconfirmed connections inferred from activation patterns.
include_structural_holesbooleanNofalseInclude structural hole detection. Identifies nodes that should be connected but are not.

Example Request

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "m1nd.activate",
    "arguments": {
      "agent_id": "orchestrator",
      "query": "session pool management",
      "top_k": 5,
      "include_ghost_edges": true
    }
  }
}

Example Response

{
  "query": "session pool management",
  "seeds": [
    { "node_id": "file::pool.py", "label": "pool.py", "relevance": 0.95 }
  ],
  "activated": [
    {
      "node_id": "file::pool.py",
      "label": "pool.py",
      "type": "file",
      "activation": 0.89,
      "dimensions": { "structural": 0.92, "semantic": 0.95, "temporal": 0.78, "causal": 0.71 },
      "pagerank": 0.635,
      "tags": ["session", "pool"],
      "provenance": {
        "source_path": "backend/pool.py",
        "line_start": 1,
        "line_end": 245,
        "canonical": true
      }
    },
    {
      "node_id": "file::pool.py::class::ConnectionPool",
      "label": "ConnectionPool",
      "type": "class",
      "activation": 0.84,
      "dimensions": { "structural": 0.88, "semantic": 0.91, "temporal": 0.72, "causal": 0.65 },
      "pagerank": 0.412,
      "tags": ["pool", "session"],
      "provenance": {
        "source_path": "backend/pool.py",
        "line_start": 15,
        "line_end": 180,
        "canonical": true
      }
    }
  ],
  "ghost_edges": [
    {
      "source": "pool.py",
      "target": "recovery.py",
      "shared_dimensions": ["semantic", "causal"],
      "strength": 0.34
    }
  ],
  "structural_holes": [],
  "plasticity": {
    "edges_strengthened": 12,
    "edges_decayed": 3,
    "ltp_events": 1,
    "priming_nodes": 5
  },
  "elapsed_ms": 31.2
}

When to Use

  • Primary search – the default way to ask “what in the codebase relates to X?”
  • Exploration – when you know a topic but not the specific files
  • Context building – before working on a feature, activate its topic to find all related code
  • Gap detection – enable include_structural_holes to find missing connections

Side Effects

Activate has plasticity side effects: it strengthens edges between activated nodes and decays inactive edges. This makes the graph learn from usage patterns over time.

  • m1nd.warmup – activate + prime for a specific task
  • m1nd.seek – intent-aware search (finds code by purpose, not just keywords)
  • m1nd.perspective.start – wraps activate into a navigable perspective
  • m1nd.learn – explicitly provide feedback on activation results

m1nd.warmup

Task-based warmup and priming. Activates the graph around a task description and applies a temporary boost to relevant nodes, preparing the graph for focused work. The boost decays naturally over time.

Parameters

ParameterTypeRequiredDefaultDescription
task_descriptionstringYesDescription of the task to warm up for. Natural language.
agent_idstringYesCalling agent identifier.
boost_strengthnumberNo0.15Priming boost strength applied to relevant nodes. Range: 0.0 to 1.0. Higher values make the primed nodes more dominant in subsequent queries.

Example Request

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "m1nd.warmup",
    "arguments": {
      "agent_id": "orchestrator",
      "task_description": "Refactor the messaging routing module to support group chats",
      "boost_strength": 0.2
    }
  }
}

Example Response

{
  "task": "Refactor the messaging routing module to support group chats",
  "primed_nodes": 23,
  "top_primed": [
    { "node_id": "file::messaging_routes.py", "label": "messaging_routes.py", "boost": 0.2 },
    { "node_id": "file::messaging.py", "label": "messaging.py", "boost": 0.18 },
    { "node_id": "file::messaging_models.py", "label": "messaging_models.py", "boost": 0.15 },
    { "node_id": "file::handler.py", "label": "handler.py", "boost": 0.12 }
  ],
  "elapsed_ms": 18.5
}

When to Use

  • Session start – warm up before a focused work session to bias the graph toward relevant code
  • Context switch – when changing tasks, warm up the new topic
  • Before complex queries – warmup biases subsequent activate, impact, and why queries toward the warmed-up region

Side Effects

Applies temporary priming boosts to node activations. These boosts decay naturally and are NOT persisted across server restarts.

  • m1nd.activate – raw activation query without the priming boost
  • m1nd.trail.resume – restores a full investigation context including activation boosts

m1nd.resonate

Resonance analysis: standing waves, harmonics, sympathetic pairs, and resonant frequencies in the graph. Identifies nodes that form natural clusters of mutual reinforcement – the “harmonics” of the connectome.

Parameters

ParameterTypeRequiredDefaultDescription
querystringNoSearch query to find seed nodes for resonance analysis. Provide either query or node_id (or neither for global resonance).
node_idstringNoSpecific node identifier to use as seed. Alternative to query.
agent_idstringYesCalling agent identifier.
top_kintegerNo20Number of top resonance results to return.

Example Request

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "m1nd.resonate",
    "arguments": {
      "agent_id": "orchestrator",
      "query": "authentication flow",
      "top_k": 10
    }
  }
}

Example Response

{
  "seed": "authentication flow",
  "harmonics": [
    {
      "node_id": "file::auth_discovery.py",
      "label": "auth_discovery.py",
      "amplitude": 0.92,
      "harmonic_order": 1
    },
    {
      "node_id": "file::middleware.py",
      "label": "middleware.py",
      "amplitude": 0.71,
      "harmonic_order": 2
    },
    {
      "node_id": "file::principal_registry.py",
      "label": "principal_registry.py",
      "amplitude": 0.68,
      "harmonic_order": 2
    }
  ],
  "sympathetic_pairs": [
    { "a": "auth_discovery.py", "b": "principal_registry.py", "coupling": 0.84 }
  ],
  "elapsed_ms": 45.0
}

When to Use

  • Deep structural analysis – find natural clusters of mutually reinforcing code
  • Pattern discovery – identify which modules form coherent subsystems
  • Architecture review – see which modules resonate together (and which do not)
  • Refactoring – resonance groups suggest natural module boundaries

Side Effects

Read-only. No plasticity side effects.

Analysis Tools

Seven tools for impact analysis, prediction, counterfactual simulation, fingerprinting, hypothesis testing, structural diffing, and drift detection.


m1nd.impact

Impact radius / blast analysis for a node. Propagates signal outward from a source node to determine which other nodes would be affected by a change. Supports forward (downstream), reverse (upstream), and bidirectional analysis.

Parameters

ParameterTypeRequiredDefaultDescription
node_idstringYesTarget node identifier. Can be an external_id (file::backend/config.py) or a node label.
agent_idstringYesCalling agent identifier.
directionstringNo"forward"Propagation direction. Values: "forward" (what does this affect?), "reverse" (what affects this?), "both".
include_causal_chainsbooleanNotrueInclude causal chain detection. Shows the specific paths through which impact propagates.

Example Request

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "m1nd.impact",
    "arguments": {
      "agent_id": "orchestrator",
      "node_id": "file::handler.py",
      "direction": "forward",
      "include_causal_chains": true
    }
  }
}

Example Response

{
  "source": "file::handler.py",
  "source_label": "handler.py",
  "direction": "forward",
  "blast_radius": [
    { "node_id": "file::chat_routes.py", "label": "chat_routes.py", "type": "file", "signal_strength": 0.91, "hop_distance": 1 },
    { "node_id": "file::ws_relay.py", "label": "ws_relay.py", "type": "file", "signal_strength": 0.78, "hop_distance": 1 },
    { "node_id": "file::parser.py", "label": "parser.py", "type": "file", "signal_strength": 0.65, "hop_distance": 2 }
  ],
  "total_energy": 4271.0,
  "max_hops_reached": 3,
  "causal_chains": [
    {
      "path": ["handler.py", "chat_routes.py", "main.py"],
      "relations": ["imported_by", "registered_in"],
      "cumulative_strength": 0.82
    }
  ]
}

When to Use

  • Before modifying code – understand the blast radius before touching a file
  • Risk assessment – high total_energy = high-risk change
  • Scope validation – verify that a planned change does not leak beyond expected boundaries
  • Reverse analysis – find all upstream dependencies that could cause a bug in a given module

m1nd.predict

Co-change prediction for a modified node. Given a node that was just changed, predicts which other nodes are most likely to need changes too, ranked by confidence. Uses historical co-change patterns, structural proximity, and velocity scoring.

Parameters

ParameterTypeRequiredDefaultDescription
changed_nodestringYesNode identifier that was changed.
agent_idstringYesCalling agent identifier.
top_kintegerNo10Number of top predictions to return.
include_velocitybooleanNotrueInclude velocity scoring. Velocity considers how recently and frequently nodes co-changed.

Example Request

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "m1nd.predict",
    "arguments": {
      "agent_id": "orchestrator",
      "changed_node": "file::pool.py",
      "top_k": 5
    }
  }
}

Example Response

{
  "changed_node": "file::pool.py",
  "predictions": [
    { "node_id": "file::worker.py", "label": "worker.py", "confidence": 0.89, "velocity": 0.72, "reason": "high co-change frequency + structural coupling" },
    { "node_id": "file::process_manager.py", "label": "process_manager.py", "confidence": 0.76, "velocity": 0.65, "reason": "imports pool" },
    { "node_id": "file::tests/test_pool.py", "label": "test_pool.py", "confidence": 0.71, "velocity": 0.80, "reason": "test file" },
    { "node_id": "file::worker.py", "label": "worker.py", "confidence": 0.54, "velocity": 0.41, "reason": "2-hop dependency" },
    { "node_id": "file::config.py", "label": "config.py", "confidence": 0.32, "velocity": 0.28, "reason": "shared configuration" }
  ],
  "elapsed_ms": 8.3
}

When to Use

  • After modifying a module – find what else needs updating
  • Before committing – verify you have not missed a co-change partner
  • Code review – check if a PR is missing changes to coupled modules

m1nd.counterfactual

What-if node removal simulation. Simulates removing one or more nodes from the graph and reports the cascade: orphaned nodes, lost activation energy, and the resulting blast radius. Non-destructive – the graph is not modified.

Parameters

ParameterTypeRequiredDefaultDescription
node_idsstring[]YesNode identifiers to simulate removal of.
agent_idstringYesCalling agent identifier.
include_cascadebooleanNotrueInclude cascade analysis. Shows multi-hop propagation of the removal.

Example Request

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "m1nd.counterfactual",
    "arguments": {
      "agent_id": "orchestrator",
      "node_ids": ["file::worker.py"],
      "include_cascade": true
    }
  }
}

Example Response

{
  "removed_nodes": ["file::worker.py"],
  "cascade": [
    { "depth": 1, "affected": 23 },
    { "depth": 2, "affected": 456 },
    { "depth": 3, "affected": 3710 }
  ],
  "total_affected": 4189,
  "orphaned_count": 0,
  "pct_activation_lost": 0.41,
  "elapsed_ms": 3.1
}

When to Use

  • Before deleting/rewriting modules – understand the full cascade before removing code
  • Dependency audit – find modules whose removal would be catastrophic
  • Architecture planning – evaluate the cost of removing a subsystem

m1nd.fingerprint

Activation fingerprint and equivalence detection. Computes a structural fingerprint for a target node (or the entire graph) and finds functionally equivalent or duplicate nodes. Uses probe queries to generate activation patterns and compares them via cosine similarity.

Parameters

ParameterTypeRequiredDefaultDescription
target_nodestringNoTarget node to find equivalents for. If omitted, performs global fingerprinting.
agent_idstringYesCalling agent identifier.
similarity_thresholdnumberNo0.85Cosine similarity threshold for equivalence. Range: 0.0 to 1.0. Lower values find more (but weaker) matches.
probe_queriesstring[]NoOptional probe queries for fingerprinting. Auto-generated from the node’s neighborhood if omitted.

Example Request

{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tools/call",
  "params": {
    "name": "m1nd.fingerprint",
    "arguments": {
      "agent_id": "orchestrator",
      "target_node": "file::pool.py",
      "similarity_threshold": 0.7
    }
  }
}

Example Response

{
  "target": "file::pool.py",
  "fingerprint": [0.92, 0.45, 0.78, 0.33, 0.67],
  "equivalents": [
    { "node_id": "file::worker.py", "label": "worker.py", "similarity": 0.88, "reason": "similar pool lifecycle pattern" },
    { "node_id": "file::fast_pool.py", "label": "fast_pool.py", "similarity": 0.74, "reason": "shared structural role" }
  ],
  "elapsed_ms": 55.2
}

When to Use

  • Duplicate detection – find code doing the same thing in different places
  • Consolidation audit – identify candidates for unification
  • Post-build review – verify new code does not duplicate existing functionality
  • m1nd.resonate – finds harmonically coupled nodes (complementary, not duplicate)
  • m1nd.activate – simpler search without equivalence scoring

m1nd.hypothesize

Graph-based hypothesis testing. Takes a natural language claim about the codebase, parses it into a structural query pattern, and returns evidence for and against the claim with a Bayesian confidence score.

Supported claim patterns (auto-detected from natural language): NEVER_CALLS, ALWAYS_BEFORE, DEPENDS_ON, NO_DEPENDENCY, COUPLING, ISOLATED, GATEWAY, CIRCULAR.

Parameters

ParameterTypeRequiredDefaultDescription
claimstringYesNatural language claim about the codebase. Examples: "handler never validates session tokens", "all external calls go through the router", "auditor is independent of messaging".
agent_idstringYesCalling agent identifier.
max_hopsintegerNo5Maximum BFS hops for evidence search.
include_ghost_edgesbooleanNotrueInclude ghost edges as weak evidence. Ghost edges count as lower-weight supporting evidence.
include_partial_flowbooleanNotrueInclude partial flow when full path not found. Shows how far the search reached.
path_budgetintegerNo1000Budget cap for all-paths enumeration. Limits computation on dense graphs.

Example Request

{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "tools/call",
  "params": {
    "name": "m1nd.hypothesize",
    "arguments": {
      "agent_id": "orchestrator",
      "claim": "worker depends on messaging at runtime"
    }
  }
}

Example Response

{
  "claim": "worker depends on messaging at runtime",
  "claim_type": "depends_on",
  "subject_nodes": ["file::worker.py"],
  "object_nodes": ["file::messaging.py"],
  "verdict": "likely_true",
  "confidence": 0.72,
  "supporting_evidence": [
    {
      "type": "path_found",
      "description": "2-hop path via process_manager.cancel",
      "likelihood_factor": 3.5,
      "nodes": ["file::worker.py", "file::process_manager.py", "file::messaging.py"],
      "relations": ["calls", "imports"],
      "path_weight": 0.68
    }
  ],
  "contradicting_evidence": [],
  "paths_explored": 25015,
  "elapsed_ms": 58.0
}

Verdict Values

VerdictConfidence RangeMeaning
"likely_true"> 0.8Strong structural evidence supports the claim
"likely_false"< 0.2Strong structural evidence contradicts the claim
"inconclusive"0.2 – 0.8Evidence exists both for and against

When to Use

  • Architecture validation – test claims about module boundaries and dependencies
  • Bug investigation – test whether a suspected dependency exists
  • Code review – verify architectural invariants are maintained
  • Security audit – test isolation claims (e.g. “auth module is isolated from user input”)
  • m1nd.why – finds the path between two specific nodes
  • m1nd.impact – measures downstream impact rather than testing a claim
  • m1nd.scan – structural analysis with predefined patterns

m1nd.differential

Focused structural diff between two graph snapshots. Compares edges, weights, nodes, and coupling between snapshot A and snapshot B, optionally narrowed by a focus question.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier.
snapshot_astringYesPath to snapshot A, or "current" for the in-memory graph.
snapshot_bstringYesPath to snapshot B, or "current" for the in-memory graph.
questionstringNoFocus filter question. Narrows the diff output. Examples: "what new coupling was introduced?", "what modules became isolated?".
focus_nodesstring[]No[]Limit diff to neighborhood of specific nodes.

Example Request

{
  "jsonrpc": "2.0",
  "id": 6,
  "method": "tools/call",
  "params": {
    "name": "m1nd.differential",
    "arguments": {
      "agent_id": "orchestrator",
      "snapshot_a": "/path/to/before.json",
      "snapshot_b": "current",
      "question": "what new coupling was introduced?"
    }
  }
}

Example Response

{
  "snapshot_a": "/path/to/before.json",
  "snapshot_b": "current",
  "new_edges": [
    { "source": "messaging_routes.py", "target": "handler.py", "relation": "calls", "weight": 0.45 }
  ],
  "removed_edges": [],
  "weight_changes": [
    { "source": "pool.py", "target": "worker.py", "relation": "imports", "old_weight": 0.5, "new_weight": 0.78, "delta": 0.28 }
  ],
  "new_nodes": ["file::messaging_bridge.py"],
  "removed_nodes": [],
  "coupling_deltas": [
    { "community_a": "messaging", "community_b": "handler", "old_coupling": 0.2, "new_coupling": 0.65, "delta": 0.45 }
  ],
  "summary": "1 new edge, 1 new node, 1 weight change, 1 coupling increase",
  "elapsed_ms": 120.5
}

When to Use

  • Pre/post refactor comparison – snapshot before, refactor, then diff against current
  • PR review – compare graph before and after a branch’s changes
  • Architecture drift monitoring – periodic snapshot comparison
  • m1nd.diverge – higher-level drift analysis with anomaly detection
  • m1nd.lock.diff – diff within a locked region (no snapshot file needed)

m1nd.diverge

Structural drift detection between a baseline and the current graph state. Higher-level than differential – includes anomaly detection (test deficits, velocity spikes, new coupling), coupling matrix changes, and a Jaccard-based structural drift score.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier.
baselinestringYesBaseline reference. Values: ISO date ("2026-03-01"), git ref (SHA or tag), or "last_session" to use the saved GraphFingerprint.
scopestringNoFile path glob to limit scope. Example: "backend/orchestrator*". None = all nodes.
include_coupling_changesbooleanNotrueInclude coupling matrix delta between communities.
include_anomaliesbooleanNotrueDetect anomalies: test deficits, velocity spikes, new coupling, isolation.

Example Request

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "m1nd.diverge",
    "arguments": {
      "agent_id": "orchestrator",
      "baseline": "2026-03-01",
      "scope": "backend/orchestrator*"
    }
  }
}

Example Response

{
  "baseline": "2026-03-01",
  "baseline_commit": "a1b2c3d",
  "scope": "backend/orchestrator*",
  "structural_drift": 0.23,
  "new_nodes": ["file::orchestrator_runtime_guard.py"],
  "removed_nodes": [],
  "modified_nodes": [
    { "file": "orchestrator_v2.py", "delta": "+450/-30", "growth_ratio": 1.85 }
  ],
  "coupling_changes": [
    { "pair": ["orchestrator_v2.py", "orchestrator_routes.py"], "was": 0.4, "now": 0.72, "direction": "strengthened" }
  ],
  "anomalies": [
    { "type": "test_deficit", "file": "orchestrator_runtime_guard.py", "detail": "New file with 0 test files", "severity": "warning" },
    { "type": "velocity_spike", "file": "orchestrator_v2.py", "detail": "450 lines added in 12 days", "severity": "info" }
  ],
  "summary": "23% structural drift. 1 new file (untested). orchestrator coupling strengthened.",
  "elapsed_ms": 85.0
}

Anomaly Types

TypeDescription
test_deficitNew or modified file with no corresponding test file
velocity_spikeUnusually high churn rate
new_couplingPreviously independent modules are now coupled
isolationModule that was connected became isolated

When to Use

  • Session startm1nd.drift shows weight-level changes; m1nd.diverge shows structural-level changes
  • Sprint retrospective – how much did the architecture change this sprint?
  • Quality gate – flag files with test deficits before merging

Memory & Learning Tools

Seven tools for feedback-based learning, drift analysis, path explanation, and investigation trail management.


m1nd.learn

Explicit feedback-based edge adjustment. After using activate or other query tools, call learn to tell m1nd whether the results were correct, wrong, or partial. This applies Hebbian learning to strengthen or weaken edges between the query seeds and the reported nodes.

Parameters

ParameterTypeRequiredDefaultDescription
querystringYesThe original query this feedback relates to. Must match the query used in the activation.
agent_idstringYesCalling agent identifier.
feedbackstringYesFeedback type. Values: "correct" (strengthen edges), "wrong" (weaken edges), "partial" (strengthen confirmed nodes only).
node_idsstring[]YesNode identifiers to apply feedback to. For "correct", these are the relevant results. For "wrong", these are the irrelevant ones.
strengthnumberNo0.2Feedback strength for edge adjustment. Range: 0.0 to 1.0. Higher = stronger plasticity effect.

Example Request

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "m1nd.learn",
    "arguments": {
      "agent_id": "orchestrator",
      "query": "session pool management",
      "feedback": "correct",
      "node_ids": ["file::pool.py", "file::worker.py"],
      "strength": 0.3
    }
  }
}

Example Response

{
  "query": "session pool management",
  "feedback": "correct",
  "edges_adjusted": 8,
  "nodes_affected": 2,
  "plasticity_delta": 0.3,
  "elapsed_ms": 2.1
}

When to Use

  • After every activate where results were used – always provide feedback to improve future results
  • After investigation – report which activated nodes were actually relevant
  • Continuous improvement – the graph learns from your feedback over time

Side Effects

Modifies edge weights in the graph. Changes are persisted on the next auto-persist cycle (every 50 queries) and on shutdown.

  • m1nd.activate – the query tool whose results you are providing feedback on
  • m1nd.drift – see cumulative weight changes from learning

m1nd.drift

Weight and structural drift analysis. Compares the current graph state against a baseline (typically "last_session") to show what changed – new edges, removed edges, and weight drift. Useful for context recovery at session start.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier.
sincestringNo"last_session"Baseline reference point. Values: "last_session" (saved state from previous session), or a timestamp.
include_weight_driftbooleanNotrueInclude edge weight drift analysis. Shows which edges strengthened or weakened.

Example Request

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "m1nd.drift",
    "arguments": {
      "agent_id": "orchestrator",
      "since": "last_session",
      "include_weight_drift": true
    }
  }
}

Example Response

{
  "since": "last_session",
  "node_count_delta": 15,
  "edge_count_delta": 42,
  "new_nodes": ["file::forem_publisher.py", "file::forem_routes.py"],
  "removed_nodes": [],
  "top_weight_drifts": [
    { "edge": "pool.py -> worker.py", "old": 0.45, "new": 0.72, "delta": 0.27 },
    { "edge": "handler.py -> parser.py", "old": 0.60, "new": 0.48, "delta": -0.12 }
  ],
  "elapsed_ms": 12.0
}

When to Use

  • Session start – first tool to call after m1nd.health to recover context
  • After ingest – see what the new ingest changed
  • After extended learning – track cumulative drift from feedback
  • m1nd.diverge – higher-level structural drift with anomaly detection
  • m1nd.health – basic server health (call before drift)

m1nd.why

Path explanation between two nodes. Finds and explains the relationship paths connecting a source node to a target node. Returns all paths up to max_hops, ranked by cumulative edge strength.

Parameters

ParameterTypeRequiredDefaultDescription
sourcestringYesSource node identifier.
targetstringYesTarget node identifier.
agent_idstringYesCalling agent identifier.
max_hopsintegerNo6Maximum hops in path search. Higher values find more indirect paths but take longer.

Example Request

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "m1nd.why",
    "arguments": {
      "agent_id": "orchestrator",
      "source": "file::worker.py",
      "target": "file::messaging.py",
      "max_hops": 4
    }
  }
}

Example Response

{
  "source": "file::worker.py",
  "target": "file::messaging.py",
  "paths": [
    {
      "nodes": ["worker.py", "process_manager.py", "messaging.py"],
      "relations": ["calls::cancel", "imports"],
      "cumulative_strength": 0.68,
      "hops": 2
    },
    {
      "nodes": ["worker.py", "worker.py", "handler.py", "messaging.py"],
      "relations": ["imported_by", "calls", "imports"],
      "cumulative_strength": 0.31,
      "hops": 3
    }
  ],
  "total_paths_found": 2,
  "elapsed_ms": 15.0
}

When to Use

  • Understanding dependencies – “why are these two modules connected?”
  • Tracing influence – find the relationship chain between distant modules
  • Bug investigation – understand how a change in A could affect B

m1nd.trail.save

Persist the current investigation state – nodes visited, hypotheses formed, conclusions reached, and open questions. Captures activation boosts for later restoration.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier.
labelstringYesHuman-readable label for this investigation.
hypothesesobject[]No[]Hypotheses formed during investigation. Each object has: statement (string, required), confidence (number, default 0.5), supporting_nodes (string[]), contradicting_nodes (string[]).
conclusionsobject[]No[]Conclusions reached. Each object has: statement (string, required), confidence (number, default 0.5), from_hypotheses (string[]), supporting_nodes (string[]).
open_questionsstring[]No[]Open questions remaining for future investigation.
tagsstring[]No[]Tags for organization and search.
summarystringNoOptional summary. Auto-generated if omitted.
visited_nodesobject[]No[]Explicitly list visited nodes with annotations. Each object has: node_external_id (string, required), annotation (string, optional), relevance (number, default 0.5). If omitted, captured from active perspective state.
activation_boostsobjectNo{}Map of node_external_id to boost weight [0.0, 1.0]. Re-injected on resume.

Example Request

{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tools/call",
  "params": {
    "name": "m1nd.trail.save",
    "arguments": {
      "agent_id": "orchestrator",
      "label": "auth-leak-investigation",
      "hypotheses": [
        {
          "statement": "Auth tokens leak through session pool",
          "confidence": 0.7,
          "supporting_nodes": ["file::pool.py", "file::auth_discovery.py"]
        },
        {
          "statement": "Rate limiter missing from auth chain",
          "confidence": 0.9,
          "supporting_nodes": ["file::middleware.py"]
        }
      ],
      "open_questions": ["Does the rate limiter apply to WebSocket connections?"],
      "tags": ["security", "auth", "session"],
      "activation_boosts": {
        "file::pool.py": 0.8,
        "file::auth_discovery.py": 0.6
      }
    }
  }
}

Example Response

{
  "trail_id": "trail_jimi_001_a1b2c3",
  "label": "auth-leak-investigation",
  "agent_id": "orchestrator",
  "nodes_saved": 47,
  "hypotheses_saved": 2,
  "conclusions_saved": 0,
  "open_questions_saved": 1,
  "graph_generation_at_creation": 42,
  "created_at_ms": 1710300000000
}

When to Use

  • End of investigation session – save your work before ending a session
  • Before context compaction – checkpoint your investigation state
  • Cross-session continuity – resume exactly where you left off

m1nd.trail.resume

Restore a saved investigation. Re-injects activation boosts into the graph, validates that saved nodes still exist, detects staleness, and optionally downgrades hypotheses whose supporting nodes are missing.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier.
trail_idstringYesTrail ID to resume (from trail.save or trail.list).
forcebooleanNofalseResume even if trail is stale (>50% missing nodes). Default behavior: refuse to resume stale trails.

Example Request

{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "tools/call",
  "params": {
    "name": "m1nd.trail.resume",
    "arguments": {
      "agent_id": "orchestrator",
      "trail_id": "trail_jimi_001_a1b2c3"
    }
  }
}

Example Response

{
  "trail_id": "trail_jimi_001_a1b2c3",
  "label": "auth-leak-investigation",
  "stale": false,
  "generations_behind": 3,
  "missing_nodes": [],
  "nodes_reactivated": 47,
  "hypotheses_downgraded": [],
  "trail": {
    "trail_id": "trail_jimi_001_a1b2c3",
    "agent_id": "orchestrator",
    "label": "auth-leak-investigation",
    "status": "active",
    "created_at_ms": 1710300000000,
    "last_modified_ms": 1710300000000,
    "node_count": 47,
    "hypothesis_count": 2,
    "conclusion_count": 0,
    "open_question_count": 1,
    "tags": ["security", "auth", "session"],
    "summary": "Investigating auth token leaks through session pool"
  },
  "elapsed_ms": 22.5
}

When to Use

  • Session start – restore a previous investigation
  • Cross-agent handoff – agent B resumes agent A’s trail
  • After re-ingest – check if investigation nodes survived the graph update

m1nd.trail.list

List saved investigation trails with optional filters. Returns compact summaries suitable for selecting a trail to resume.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier.
filter_agent_idstringNoFilter to a specific agent’s trails. None = all agents.
filter_statusstringNoFilter by status: "active", "saved", "archived", "stale", "merged".
filter_tagsstring[]No[]Filter by tags (any match).

Example Request

{
  "jsonrpc": "2.0",
  "id": 6,
  "method": "tools/call",
  "params": {
    "name": "m1nd.trail.list",
    "arguments": {
      "agent_id": "orchestrator",
      "filter_status": "saved",
      "filter_tags": ["security"]
    }
  }
}

Example Response

{
  "trails": [
    {
      "trail_id": "trail_jimi_001_a1b2c3",
      "agent_id": "orchestrator",
      "label": "auth-leak-investigation",
      "status": "saved",
      "created_at_ms": 1710300000000,
      "last_modified_ms": 1710300000000,
      "node_count": 47,
      "hypothesis_count": 2,
      "conclusion_count": 0,
      "open_question_count": 1,
      "tags": ["security", "auth", "session"]
    }
  ],
  "total_count": 1
}

When to Use

  • Session start – see what investigations are available to resume
  • Multi-agent coordination – see trails from other agents
  • Cleanup – find stale or merged trails

m1nd.trail.merge

Combine two or more investigation trails. Merges visited nodes, hypotheses, and conclusions. Uses confidence+recency scoring for conflict resolution. Discovers cross-connections between independently explored areas.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier.
trail_idsstring[]YesTwo or more trail IDs to merge.
labelstringNoLabel for the merged trail. Auto-generated if omitted.

Example Request

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "m1nd.trail.merge",
    "arguments": {
      "agent_id": "orchestrator",
      "trail_ids": ["trail_jimi_001_a1b2c3", "trail_analyst_002_d4e5f6"],
      "label": "combined-auth-investigation"
    }
  }
}

Example Response

{
  "merged_trail_id": "trail_jimi_003_g7h8i9",
  "label": "combined-auth-investigation",
  "source_trails": ["trail_jimi_001_a1b2c3", "trail_analyst_002_d4e5f6"],
  "nodes_merged": 83,
  "hypotheses_merged": 5,
  "conflicts": [
    {
      "hypothesis_a": "Session pool leaks tokens",
      "hypothesis_b": "Session pool tokens are properly scoped",
      "resolution": "resolved",
      "winner": "Session pool leaks tokens",
      "score_delta": 0.35
    }
  ],
  "connections_discovered": [
    {
      "type": "bridge_edge",
      "detail": "auth_discovery.py connects the auth trail to the session trail",
      "from_node": "file::auth_discovery.py",
      "to_node": "file::pool.py",
      "weight": 0.72
    }
  ],
  "elapsed_ms": 45.0
}

Conflict Resolution

When merging hypotheses that contradict each other:

  • confidence+recency scoring determines the winner
  • If the score delta is too small, the conflict is marked "unresolved" for human review
  • Source trails are set to "merged" status after a successful merge

When to Use

  • Multi-agent investigation – combine findings from parallel agents
  • Investigation continuation – merge an old investigation with new findings
  • Consolidation – clean up related but separate investigation threads

Exploration Tools

Six tools for intent-aware search, pattern-based scanning, structural hole detection, stacktrace analysis, temporal history, and multi-repository federation.


m1nd.seek

Intent-aware semantic code search. Finds code by purpose, not text pattern. Combines keyword matching, graph activation (PageRank), and trigram similarity for ranking.

Unlike activate (which propagates signal through edges), seek scores every node independently against the query, making it better for finding specific code when you know what it does but not where it lives.

Parameters

ParameterTypeRequiredDefaultDescription
querystringYesNatural language description of what the agent is looking for. Example: "code that validates user credentials".
agent_idstringYesCalling agent identifier.
top_kintegerNo20Maximum results to return.
scopestringNoFile path prefix to limit search scope. Example: "backend/". None = entire graph.
node_typesstring[]No[]Filter by node type: "function", "class", "struct", "module", "file". Empty = all types.
min_scorenumberNo0.1Minimum combined score threshold. Range: 0.0 to 1.0.
graph_rerankbooleanNotrueWhether to run graph re-ranking (PageRank weighting) on candidates. Disable for pure text matching.

Scoring Formula (V1)

combined = keyword_match * 0.6 + graph_activation * 0.3 + trigram * 0.1

V2 upgrade path will replace keyword matching with real embeddings (fastembed-rs + jina-embeddings-v2-base-code).

Example Request

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "m1nd.seek",
    "arguments": {
      "agent_id": "orchestrator",
      "query": "code that validates user credentials",
      "top_k": 5,
      "node_types": ["function", "class"]
    }
  }
}

Example Response

{
  "query": "code that validates user credentials",
  "results": [
    {
      "node_id": "file::auth_discovery.py::fn::validate_credentials",
      "label": "validate_credentials",
      "type": "function",
      "score": 0.87,
      "score_breakdown": {
        "embedding_similarity": 0.92,
        "graph_activation": 0.78,
        "temporal_recency": 0.65
      },
      "intent_summary": "Validates user credentials against provider store",
      "file_path": "backend/auth_discovery.py",
      "line_start": 45,
      "line_end": 82,
      "connections": [
        { "node_id": "file::principal_registry.py", "label": "principal_registry.py", "relation": "calls" }
      ]
    }
  ],
  "total_candidates_scanned": 9767,
  "embeddings_used": false,
  "elapsed_ms": 25.0
}

When to Use

  • “Find the code that does X” – when you know the purpose, not the filename
  • Codebase onboarding – exploring unfamiliar code by intent
  • Pre-modification search – find all code related to a feature before changing it
  • m1nd.activate – graph-propagation search (better for exploring neighborhoods)
  • m1nd.scan – pattern-based structural analysis (finds anti-patterns, not features)

m1nd.scan

Pattern-aware structural code analysis with graph-validated findings. Detects structural issues using predefined patterns, then validates each finding against the graph to filter false positives. Works across file boundaries.

Parameters

ParameterTypeRequiredDefaultDescription
patternstringYesPattern ID or custom pattern string. Built-in patterns: "error_handling", "resource_cleanup", "api_surface", "state_mutation", "concurrency", "auth_boundary", "test_coverage", "dependency_injection".
agent_idstringYesCalling agent identifier.
scopestringNoFile path prefix to limit scan scope.
severity_minnumberNo0.3Minimum severity threshold. Range: 0.0 to 1.0.
graph_validatebooleanNotrueValidate findings against graph edges (cross-file analysis). Disable for raw pattern matching only.
limitintegerNo50Maximum findings to return.

Example Request

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "m1nd.scan",
    "arguments": {
      "agent_id": "orchestrator",
      "pattern": "error_handling",
      "scope": "backend/",
      "severity_min": 0.5
    }
  }
}

Example Response

{
  "pattern": "error_handling",
  "findings": [
    {
      "pattern": "error_handling",
      "status": "confirmed",
      "severity": 0.78,
      "node_id": "file::worker.py::fn::spawn_agent",
      "label": "spawn_agent",
      "file_path": "backend/worker.py",
      "line": 89,
      "message": "Bare except clause catches all exceptions including KeyboardInterrupt",
      "graph_context": [
        { "node_id": "file::process_manager.py", "label": "process_manager.py", "relation": "calls" }
      ]
    }
  ],
  "files_scanned": 77,
  "total_matches_raw": 23,
  "total_matches_validated": 8,
  "elapsed_ms": 150.0
}

Finding Status Values

StatusMeaning
"confirmed"Graph validation confirms the issue is real
"mitigated"The issue exists but is handled by a related module
"false_positive"Graph context shows the pattern match is not actually an issue

When to Use

  • Code quality audit – scan for anti-patterns across the codebase
  • Security review – use "auth_boundary" to find auth bypass paths
  • Pre-deploy check – scan for "error_handling" and "resource_cleanup" issues
  • Test gaps – use "test_coverage" to find untested code

m1nd.missing

Detect structural holes and missing connections. Given a topic query, finds areas where the graph suggests something should exist but does not. Identifies absent abstractions, missing connections, and incomplete patterns.

Parameters

ParameterTypeRequiredDefaultDescription
querystringYesSearch query to find structural holes around.
agent_idstringYesCalling agent identifier.
min_sibling_activationnumberNo0.3Minimum sibling activation threshold. Siblings with activation below this are not considered for hole detection.

Example Request

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "m1nd.missing",
    "arguments": {
      "agent_id": "orchestrator",
      "query": "database connection pooling",
      "min_sibling_activation": 0.3
    }
  }
}

Example Response

{
  "query": "database connection pooling",
  "holes": [
    {
      "node_id": "structural_hole_1",
      "label": "connection lifecycle",
      "type": "structural_hole",
      "reason": "No dedicated connection pool abstraction -- 4 adjacent modules manage connections independently"
    },
    {
      "node_id": "structural_hole_2",
      "label": "pool metrics",
      "type": "structural_hole",
      "reason": "No pool health monitoring -- 3 adjacent modules expose metrics but pool does not"
    }
  ],
  "total_holes": 9,
  "elapsed_ms": 67.0
}

When to Use

  • Gap analysis – “what am I missing?” before implementing a feature
  • Pre-spec – identify areas that need design before building
  • Architecture review – find missing abstractions or connections
  • Feature completeness – after building, check for structural holes around the feature
  • m1nd.activate – activate with include_structural_holes: true for inline hole detection
  • m1nd.hypothesize – test a specific claim about missing structure

m1nd.trace

Map runtime errors to structural root causes via stacktrace analysis. Parses the stacktrace, maps frames to graph nodes, and scores each node’s suspiciousness using trace depth, modification recency, and centrality. Also finds co-change suspects (files modified around the same time as the top suspect).

Parameters

ParameterTypeRequiredDefaultDescription
error_textstringYesFull error output (stacktrace + error message).
agent_idstringYesCalling agent identifier.
languagestringNoLanguage hint: "python", "rust", "typescript", "javascript", "go". Auto-detected if omitted.
window_hoursnumberNo24.0Temporal window (hours) for co-change suspect scan.
top_kintegerNo10Max suspects to return.

Example Request

{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tools/call",
  "params": {
    "name": "m1nd.trace",
    "arguments": {
      "agent_id": "orchestrator",
      "error_text": "Traceback (most recent call last):\n  File \"backend/handler.py\", line 234, in handle_message\n  File \"backend/pool.py\", line 89, in acquire\n  File \"backend/worker.py\", line 156, in submit\nTimeoutError: pool exhausted",
      "language": "python",
      "top_k": 5
    }
  }
}

Example Response

{
  "language_detected": "python",
  "error_type": "TimeoutError",
  "error_message": "pool exhausted",
  "frames_parsed": 3,
  "frames_mapped": 3,
  "suspects": [
    {
      "node_id": "file::worker.py::fn::submit",
      "label": "submit",
      "type": "function",
      "suspiciousness": 0.91,
      "signals": { "trace_depth_score": 1.0, "recency_score": 0.85, "centrality_score": 0.88 },
      "file_path": "backend/worker.py",
      "line_start": 150,
      "line_end": 175,
      "related_callers": ["pool.py::acquire"]
    },
    {
      "node_id": "file::pool.py::fn::acquire",
      "label": "acquire",
      "type": "function",
      "suspiciousness": 0.78,
      "signals": { "trace_depth_score": 0.67, "recency_score": 0.72, "centrality_score": 0.65 },
      "file_path": "backend/pool.py",
      "line_start": 80,
      "line_end": 110,
      "related_callers": ["handler.py::handle_message"]
    }
  ],
  "co_change_suspects": [
    { "node_id": "file::config.py", "label": "config.py", "modified_at": 1710295000.0, "reason": "Modified within 2h of top suspect" }
  ],
  "causal_chain": ["worker.py::submit", "pool.py::acquire", "handler.py::handle_message"],
  "fix_scope": {
    "files_to_inspect": ["backend/worker.py", "backend/pool.py", "backend/config.py"],
    "estimated_blast_radius": 23,
    "risk_level": "medium"
  },
  "unmapped_frames": [],
  "elapsed_ms": 3.5
}

Suspiciousness Scoring

SignalWeightDescription
trace_depth_scoreHigh1.0 = deepest frame (most specific); decays linearly
recency_scoreMediumExponential decay from last modification time
centrality_scoreMediumNormalized PageRank centrality

When to Use

  • Bug investigation – paste an error and get ranked suspects
  • Root cause analysis – the causal chain shows the error propagation path
  • Fix scopingfix_scope tells you which files to inspect and the risk level

m1nd.timeline

Git-based temporal history for a node. Returns the change history, co-change partners, velocity, stability score, and churn data for a specific file or module.

Parameters

ParameterTypeRequiredDefaultDescription
nodestringYesNode external_id. Example: "file::backend/handler.py".
agent_idstringYesCalling agent identifier.
depthstringNo"30d"Time depth. Values: "7d", "30d", "90d", "all".
include_co_changesbooleanNotrueInclude co-changed files with coupling scores.
include_churnbooleanNotrueInclude lines added/deleted churn data.
top_kintegerNo10Max co-change partners to return.

Example Request

{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "tools/call",
  "params": {
    "name": "m1nd.timeline",
    "arguments": {
      "agent_id": "orchestrator",
      "node": "file::backend/handler.py",
      "depth": "30d",
      "top_k": 5
    }
  }
}

Example Response

{
  "node": "file::backend/handler.py",
  "depth": "30d",
  "changes": [
    {
      "date": "2026-03-10",
      "commit": "a1b2c3d",
      "author": "cosmophonix",
      "delta": "+45/-12",
      "co_changed": ["parser.py", "chat_routes.py"]
    },
    {
      "date": "2026-03-05",
      "commit": "e4f5g6h",
      "author": "cosmophonix",
      "delta": "+120/-30",
      "co_changed": ["pool.py", "worker.py"]
    }
  ],
  "co_changed_with": [
    { "file": "parser.py", "times": 8, "coupling_degree": 0.72 },
    { "file": "chat_routes.py", "times": 6, "coupling_degree": 0.55 },
    { "file": "pool.py", "times": 4, "coupling_degree": 0.38 }
  ],
  "velocity": "accelerating",
  "stability_score": 0.35,
  "pattern": "churning",
  "total_churn": { "lines_added": 580, "lines_deleted": 120 },
  "commit_count_in_window": 12,
  "elapsed_ms": 45.0
}

Velocity Values

ValueMeaning
"accelerating"Change frequency is increasing
"decelerating"Change frequency is decreasing
"stable"Consistent change rate

Pattern Values

ValueMeaning
"expanding"Growing (net positive churn)
"shrinking"Reducing (net negative churn)
"churning"High add+delete with little net growth
"dormant"Few or no changes in the window
"stable"Small, consistent changes

When to Use

  • Hotspot detection – find files that change too frequently (stability_score < 0.3)
  • Co-change discovery – find files that always change together (coupling_degree > 0.6)
  • Refactoring signals – churning files may need redesign
  • m1nd.diverge – structural drift across the whole codebase
  • m1nd.predict – predict co-changes for a given modification

m1nd.federate

Ingest multiple repositories into a unified federated graph with automatic cross-repo edge detection. After federation, all existing query tools (activate, impact, why, etc.) traverse cross-repo edges automatically.

Node IDs in the federated graph use {repo_name}::file::path format.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier.
reposobject[]YesList of repositories to federate. Each object has: name (string, required – namespace prefix), path (string, required – absolute path), adapter (string, default "code").
detect_cross_repo_edgesbooleanNotrueAuto-detect cross-repo edges: config, API, import, type, deployment, MCP contract.
incrementalbooleanNofalseOnly re-ingest repos that changed since last federation.

Cross-Repo Edge Types

Edge TypeDescription
shared_configTwo repos reference the same configuration key
api_contractOne repo’s API client matches another’s API server
package_depDirect package dependency
shared_typeSame type/interface definition used across repos
deployment_depDeployment configuration dependency
mcp_contractMCP tool consumer/provider relationship

Example Request

{
  "jsonrpc": "2.0",
  "id": 6,
  "method": "tools/call",
  "params": {
    "name": "m1nd.federate",
    "arguments": {
      "agent_id": "orchestrator",
      "repos": [
        { "name": "backend", "path": "/project/backend" },
        { "name": "frontend", "path": "/project/frontend" },
        { "name": "mcp-server", "path": "/project/mcp-server", "adapter": "code" }
      ],
      "detect_cross_repo_edges": true,
      "incremental": false
    }
  }
}

Example Response

{
  "repos_ingested": [
    { "name": "backend", "path": "/project/backend", "node_count": 9767, "edge_count": 26557, "from_cache": false, "ingest_ms": 910.0 },
    { "name": "frontend", "path": "/project/frontend", "node_count": 1200, "edge_count": 3500, "from_cache": false, "ingest_ms": 320.0 },
    { "name": "mcp-server", "path": "/project/mcp-server", "node_count": 250, "edge_count": 680, "from_cache": false, "ingest_ms": 85.0 }
  ],
  "total_nodes": 11217,
  "total_edges": 30737,
  "cross_repo_edges": [
    {
      "source_repo": "frontend",
      "target_repo": "backend",
      "source_node": "frontend::file::src/lib/apiConfig.ts",
      "target_node": "backend::file::main.py",
      "edge_type": "api_contract",
      "relation": "calls_api",
      "weight": 0.85,
      "causal_strength": 0.72
    },
    {
      "source_repo": "mcp-server",
      "target_repo": "backend",
      "source_node": "mcp-server::file::src/mcp-server.js",
      "target_node": "backend::file::main.py",
      "edge_type": "mcp_contract",
      "relation": "calls_api",
      "weight": 0.78,
      "causal_strength": 0.65
    }
  ],
  "cross_repo_edge_count": 18203,
  "incremental": false,
  "skipped_repos": [],
  "elapsed_ms": 1315.0
}

When to Use

  • Multi-repo projects – analyze dependencies across frontend, backend, and infrastructure repos
  • Monorepo decomposition – understand how packages depend on each other
  • API impact analysis – find which repos are affected by an API change
  • m1nd.ingest – single-repo ingestion
  • m1nd.impact – blast radius analysis (works across federated repos)
  • m1nd.why – path explanation (traverses cross-repo edges)

Perspective Tools

Twelve tools for stateful, navigable exploration of the code graph. Perspectives are m1nd’s primary interface for guided codebase exploration – they synthesize routes (weighted navigation suggestions) from a query, then let you follow, inspect, branch, and compare routes.

Core Concepts

Perspective: A stateful exploration session. Created from a query, it synthesizes a set of routes from the current graph state. The agent navigates by following routes, which moves the focus and generates new routes.

Route: A suggested direction of exploration. Each route points to a target node and carries a score, a family classification, and a path preview.

Focus: The current node the perspective is centered on. Following a route moves the focus. back restores the previous focus.

Mode: Either "anchored" (routes stay related to an anchor node) or "local" (routes synthesized purely from the current focus). Anchored mode degrades to local if navigation moves more than 8 hops from the anchor.

Route Set Version: A version counter for the current route set. Clients must pass this in subsequent calls for staleness detection. If the graph changes between calls, the version changes and stale requests are rejected.

Lens: Optional filtering configuration for perspectives (dimension weights, node type filters, etc.).


m1nd.perspective.start

Enter a perspective: creates a navigable route surface from a query. Returns the initial set of routes.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier.
querystringYesSeed query for route synthesis.
anchor_nodestringNoAnchor to a specific node. If provided, activates anchored mode where all routes maintain relevance to this node.
lensobjectNoStarting lens configuration. Controls dimension weights, node type filters, etc.

Example Request

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "m1nd.perspective.start",
    "arguments": {
      "agent_id": "orchestrator",
      "query": "session management and connection pooling",
      "anchor_node": "file::pool.py"
    }
  }
}

Example Response

{
  "perspective_id": "persp_jimi_001",
  "mode": "anchored",
  "anchor_node": "file::pool.py",
  "focus_node": "file::pool.py",
  "routes": [
    {
      "route_id": "R_a1b2c3",
      "index": 1,
      "target_node": "file::worker.py",
      "target_label": "worker.py",
      "family": "structural_neighbor",
      "score": 0.89,
      "path_preview": ["pool.py", "worker.py"],
      "reason": "High structural coupling with anchor"
    },
    {
      "route_id": "R_d4e5f6",
      "index": 2,
      "target_node": "file::process_manager.py",
      "target_label": "process_manager.py",
      "family": "causal_downstream",
      "score": 0.76,
      "path_preview": ["pool.py", "process_manager.py"],
      "reason": "Causal dependency via imports"
    }
  ],
  "total_routes": 12,
  "page": 1,
  "total_pages": 2,
  "route_set_version": 100,
  "cache_generation": 42,
  "suggested": "inspect R_a1b2c3 for structural details"
}

When to Use

  • Guided exploration – when you want to explore a topic interactively
  • Codebase navigation – start from a known file and discover related code
  • Investigation – anchor to a suspicious file and follow routes to find related issues

m1nd.perspective.routes

Browse the current route set with pagination.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier.
perspective_idstringYesPerspective to browse.
pageintegerNo1Page number (1-based).
page_sizeintegerNo6Routes per page. Clamped to [1, 10].
route_set_versionintegerNoVersion from previous response for staleness check.

Example Request

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "m1nd.perspective.routes",
    "arguments": {
      "agent_id": "orchestrator",
      "perspective_id": "persp_jimi_001",
      "page": 2,
      "page_size": 6,
      "route_set_version": 100
    }
  }
}

Example Response

{
  "perspective_id": "persp_jimi_001",
  "mode": "anchored",
  "mode_effective": "anchored",
  "anchor": "file::pool.py",
  "focus": "file::pool.py",
  "lens_summary": "all dimensions, no type filter",
  "page": 2,
  "total_pages": 2,
  "total_routes": 12,
  "route_set_version": 100,
  "cache_generation": 42,
  "routes": [
    {
      "route_id": "R_g7h8i9",
      "index": 7,
      "target_node": "file::config.py",
      "target_label": "config.py",
      "family": "configuration",
      "score": 0.42,
      "path_preview": ["pool.py", "config.py"],
      "reason": "Shared configuration key"
    }
  ],
  "suggested": null,
  "page_size_clamped": false
}

When to Use

  • Pagination – browse all available routes when the first page was not enough
  • Full route survey – see the complete landscape before deciding which route to follow

m1nd.perspective.inspect

Expand a route with fuller path, metrics, provenance, affinity candidates, and score breakdown. Does not change the perspective state – purely informational.

Specify the route by either route_id (stable, content-addressed) or route_index (1-based position on the current page). Exactly one must be provided.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier.
perspective_idstringYesPerspective containing the route.
route_idstringNoStable content-addressed route ID.
route_indexintegerNo1-based page-local position.
route_set_versionintegerYesRoute set version for staleness check.

Example Request

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "m1nd.perspective.inspect",
    "arguments": {
      "agent_id": "orchestrator",
      "perspective_id": "persp_jimi_001",
      "route_id": "R_a1b2c3",
      "route_set_version": 100
    }
  }
}

Example Response

{
  "route_id": "R_a1b2c3",
  "route_index": 1,
  "family": "structural_neighbor",
  "target_node": "file::worker.py",
  "target_label": "worker.py",
  "target_type": "file",
  "path_preview": ["pool.py", "worker.py", "worker.py"],
  "family_explanation": "Direct structural neighbor via import chain",
  "score": 0.89,
  "score_breakdown": {
    "local_activation": 0.92,
    "path_coherence": 0.88,
    "novelty": 0.75,
    "anchor_relevance": 0.95,
    "continuity": 0.82
  },
  "provenance": {
    "source_path": "backend/worker.py",
    "line_start": 1,
    "line_end": 312,
    "namespace": null,
    "provenance_stale": false
  },
  "peek_available": true,
  "affinity_candidates": [
    { "node_id": "file::fast_pool.py", "label": "fast_pool.py", "strength": 0.72, "reason": "Similar pool pattern" }
  ],
  "response_chars": 450
}

When to Use

  • Before following – understand what a route leads to before committing
  • Score analysis – see why a route was ranked high or low
  • Provenance check – verify the source file exists and is not stale

m1nd.perspective.peek

Extract a small relevant code or documentation slice from a route’s target node. Reads the actual source file and returns the relevant excerpt. Security-checked: only reads files within the ingested graph scope.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier.
perspective_idstringYesPerspective containing the route.
route_idstringNoStable content-addressed route ID.
route_indexintegerNo1-based page-local position.
route_set_versionintegerYesRoute set version for staleness check.

Example Request

{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tools/call",
  "params": {
    "name": "m1nd.perspective.peek",
    "arguments": {
      "agent_id": "orchestrator",
      "perspective_id": "persp_jimi_001",
      "route_index": 1,
      "route_set_version": 100
    }
  }
}

Example Response

{
  "route_id": "R_a1b2c3",
  "route_index": 1,
  "target_node": "file::worker.py",
  "content": {
    "excerpt": "class WorkerPool:\n    \"\"\"Manages a pool of CLI subprocess workers.\"\"\"\n    def __init__(self, max_workers=8):\n        self.pool = {}\n        self.max_workers = max_workers\n        ...",
    "file_path": "backend/worker.py",
    "line_start": 15,
    "line_end": 45,
    "truncated": true
  }
}

When to Use

  • Quick preview – see the actual code at a route target without leaving m1nd
  • Decision making – peek at code to decide whether to follow a route
  • Investigation – read suspicious code without opening a file

m1nd.perspective.follow

Follow a route: move focus to the target node and synthesize new routes from the new position. This is the primary navigation action in a perspective. Updates the navigation history for back.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier.
perspective_idstringYesPerspective to navigate within.
route_idstringNoStable content-addressed route ID.
route_indexintegerNo1-based page-local position.
route_set_versionintegerYesRoute set version for staleness check.

Example Request

{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "tools/call",
  "params": {
    "name": "m1nd.perspective.follow",
    "arguments": {
      "agent_id": "orchestrator",
      "perspective_id": "persp_jimi_001",
      "route_id": "R_a1b2c3",
      "route_set_version": 100
    }
  }
}

Example Response

{
  "perspective_id": "persp_jimi_001",
  "previous_focus": "file::pool.py",
  "new_focus": "file::worker.py",
  "mode": "anchored",
  "mode_effective": "anchored",
  "routes": [
    {
      "route_id": "R_j1k2l3",
      "index": 1,
      "target_node": "file::process_manager.py",
      "target_label": "process_manager.py",
      "family": "causal_downstream",
      "score": 0.85,
      "path_preview": ["worker.py", "process_manager.py"],
      "reason": "Direct caller of worker pool submit"
    },
    {
      "route_id": "R_m4n5o6",
      "index": 2,
      "target_node": "file::worker.py",
      "target_label": "worker.py",
      "family": "structural_neighbor",
      "score": 0.71,
      "path_preview": ["worker.py", "worker.py"],
      "reason": "Co-manages subprocess lifecycle"
    }
  ],
  "total_routes": 8,
  "page": 1,
  "total_pages": 2,
  "route_set_version": 101,
  "cache_generation": 42,
  "suggested": "inspect R_j1k2l3 -- high causal relevance"
}

When to Use

  • Navigation – this is the primary way to move through the graph
  • Guided exploration – follow high-scoring routes to discover related code
  • Investigation – follow causal chains to trace a bug

m1nd.perspective.suggest

Get the next best move suggestion based on navigation history. Analyzes the routes, the current focus, and the history of followed routes to recommend what to do next.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier.
perspective_idstringYesPerspective to get a suggestion for.
route_set_versionintegerYesRoute set version for staleness check.

Example Request

{
  "jsonrpc": "2.0",
  "id": 6,
  "method": "tools/call",
  "params": {
    "name": "m1nd.perspective.suggest",
    "arguments": {
      "agent_id": "orchestrator",
      "perspective_id": "persp_jimi_001",
      "route_set_version": 101
    }
  }
}

Example Response

{
  "perspective_id": "persp_jimi_001",
  "suggestion": {
    "action": "follow",
    "route_id": "R_j1k2l3",
    "reason": "High-scoring causal route not yet explored. process_manager.py is a hub node connecting to 47 downstream modules.",
    "confidence": 0.82
  }
}

When to Use

  • Undecided – when you are not sure which route to follow
  • Systematic exploration – let m1nd guide you through the most informative path
  • Investigation – follow the suggestion trail to efficient root cause discovery

m1nd.perspective.affinity

Discover probable connections a route target might have. Returns affinity candidates – nodes that are not directly connected to the target but show structural affinity (similar neighborhoods, shared patterns).

Epistemic notice: these are probable connections, not verified graph edges.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier.
perspective_idstringYesPerspective containing the route.
route_idstringNoStable content-addressed route ID.
route_indexintegerNo1-based page-local position.
route_set_versionintegerYesRoute set version for staleness check.

Example Request

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "m1nd.perspective.affinity",
    "arguments": {
      "agent_id": "orchestrator",
      "perspective_id": "persp_jimi_001",
      "route_id": "R_j1k2l3",
      "route_set_version": 101
    }
  }
}

Example Response

{
  "route_id": "R_j1k2l3",
  "target_node": "file::process_manager.py",
  "notice": "Probable connections, not verified edges.",
  "candidates": [
    { "node_id": "file::recovery.py", "label": "recovery.py", "strength": 0.68, "reason": "Similar lifecycle management pattern" },
    { "node_id": "file::autonomous_daemon.py", "label": "autonomous_daemon.py", "strength": 0.52, "reason": "Shared subprocess management trait" }
  ]
}

When to Use

  • Discovery – find non-obvious connections that the graph does not yet have
  • Architecture analysis – identify modules that should be connected but are not
  • Ghost edge validation – affinity candidates often become confirmed edges after investigation

m1nd.perspective.branch

Fork the current navigation state into a new perspective branch. The branch starts at the same focus with the same route set, but future navigation in the branch is independent. Useful for exploring alternatives without losing your place.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier.
perspective_idstringYesPerspective to branch from.
branch_namestringNoOptional branch name. Auto-generated if not provided.

Example Request

{
  "jsonrpc": "2.0",
  "id": 8,
  "method": "tools/call",
  "params": {
    "name": "m1nd.perspective.branch",
    "arguments": {
      "agent_id": "orchestrator",
      "perspective_id": "persp_jimi_001",
      "branch_name": "auth-path"
    }
  }
}

Example Response

{
  "perspective_id": "persp_jimi_001",
  "branch_perspective_id": "persp_jimi_002",
  "branch_name": "auth-path",
  "branched_from_focus": "file::worker.py"
}

When to Use

  • Alternative exploration – explore two different paths from the same point
  • Comparative analysis – branch, follow different routes, then compare
  • Safe investigation – branch before following a risky route

m1nd.perspective.back

Navigate back to the previous focus, restoring the checkpoint state. Like browser back navigation. Pops the latest navigation event from the history stack.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier.
perspective_idstringYesPerspective to navigate back in.

Example Request

{
  "jsonrpc": "2.0",
  "id": 9,
  "method": "tools/call",
  "params": {
    "name": "m1nd.perspective.back",
    "arguments": {
      "agent_id": "orchestrator",
      "perspective_id": "persp_jimi_001"
    }
  }
}

Example Response

{
  "perspective_id": "persp_jimi_001",
  "restored_focus": "file::pool.py",
  "restored_mode": "anchored",
  "routes": [
    {
      "route_id": "R_a1b2c3",
      "index": 1,
      "target_node": "file::worker.py",
      "target_label": "worker.py",
      "family": "structural_neighbor",
      "score": 0.89,
      "path_preview": ["pool.py", "worker.py"],
      "reason": "High structural coupling with anchor"
    }
  ],
  "total_routes": 12,
  "page": 1,
  "total_pages": 2,
  "route_set_version": 102,
  "cache_generation": 42
}

When to Use

  • Undo navigation – go back after following a wrong route
  • Breadth-first exploration – follow a route, come back, follow another

m1nd.perspective.compare

Compare two perspectives on shared/unique nodes and dimension deltas. Both perspectives must belong to the same agent (V1 restriction).

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier.
perspective_id_astringYesFirst perspective ID.
perspective_id_bstringYesSecond perspective ID.
dimensionsstring[]No[]Dimensions to compare on. Empty = all dimensions.

Example Request

{
  "jsonrpc": "2.0",
  "id": 10,
  "method": "tools/call",
  "params": {
    "name": "m1nd.perspective.compare",
    "arguments": {
      "agent_id": "orchestrator",
      "perspective_id_a": "persp_jimi_001",
      "perspective_id_b": "persp_jimi_002"
    }
  }
}

Example Response

{
  "perspective_id_a": "persp_jimi_001",
  "perspective_id_b": "persp_jimi_002",
  "shared_nodes": ["worker.py", "process_manager.py", "pool.py"],
  "unique_to_a": ["worker.py", "config.py"],
  "unique_to_b": ["auth_discovery.py", "middleware.py"],
  "dimension_deltas": [
    { "dimension": "structural", "score_a": 0.82, "score_b": 0.75, "delta": 0.07 },
    { "dimension": "semantic", "score_a": 0.65, "score_b": 0.88, "delta": -0.23 },
    { "dimension": "causal", "score_a": 0.71, "score_b": 0.45, "delta": 0.26 }
  ],
  "response_chars": 380
}

When to Use

  • Branch comparison – compare a branch perspective with the original
  • Multi-approach analysis – see how two different starting queries reach different parts of the graph
  • Investigation correlation – find shared nodes between two independent investigations

m1nd.perspective.list

List all perspectives for an agent. Returns compact summaries with status, focus, route count, and memory usage.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier.

Example Request

{
  "jsonrpc": "2.0",
  "id": 11,
  "method": "tools/call",
  "params": {
    "name": "m1nd.perspective.list",
    "arguments": {
      "agent_id": "orchestrator"
    }
  }
}

Example Response

{
  "agent_id": "orchestrator",
  "perspectives": [
    {
      "perspective_id": "persp_jimi_001",
      "mode": "anchored",
      "focus_node": "file::worker.py",
      "route_count": 12,
      "nav_event_count": 3,
      "stale": false,
      "created_at_ms": 1710300000000,
      "last_accessed_ms": 1710300500000
    },
    {
      "perspective_id": "persp_jimi_002",
      "mode": "anchored",
      "focus_node": "file::auth_discovery.py",
      "route_count": 8,
      "nav_event_count": 1,
      "stale": false,
      "created_at_ms": 1710300200000,
      "last_accessed_ms": 1710300400000
    }
  ],
  "total_memory_bytes": 24576
}

When to Use

  • Session overview – see all active perspectives
  • Memory management – check total memory usage and close stale perspectives
  • Multi-perspective work – switch between perspectives

m1nd.perspective.close

Close a perspective and release associated locks. Frees memory and stops route caching for this perspective. Cascade-releases any locks that were associated with this perspective.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier.
perspective_idstringYesPerspective to close.

Example Request

{
  "jsonrpc": "2.0",
  "id": 12,
  "method": "tools/call",
  "params": {
    "name": "m1nd.perspective.close",
    "arguments": {
      "agent_id": "orchestrator",
      "perspective_id": "persp_jimi_002"
    }
  }
}

Example Response

{
  "perspective_id": "persp_jimi_002",
  "closed": true,
  "locks_released": ["lock_jimi_003"]
}

When to Use

  • Cleanup – close perspectives after investigation is complete
  • Memory pressure – close unused perspectives to free memory
  • Session end – close all perspectives before ending a session

Full Workflow Example

A complete perspective exploration session, from start to close:

1. START a perspective from a query
   m1nd.perspective.start(query="authentication and session security")
   -> persp_jimi_001, 12 routes, anchored to auth_discovery.py

2. BROWSE routes (page 1 already returned by start)
   Routes: R01 auth_discovery.py, R02 middleware.py, R03 principal_registry.py, ...

3. INSPECT a high-scoring route before following
   m1nd.perspective.inspect(route_id="R01", route_set_version=100)
   -> score_breakdown, provenance, affinity_candidates

4. FOLLOW the route to navigate
   m1nd.perspective.follow(route_id="R01", route_set_version=100)
   -> new focus: auth_discovery.py, 8 new routes, version=101

5. ASK for a suggestion on what to do next
   m1nd.perspective.suggest(route_set_version=101)
   -> "follow R_x: principal_registry.py, high causal relevance"

6. BRANCH before exploring a risky path
   m1nd.perspective.branch(branch_name="session-path")
   -> new persp_jimi_002, same focus

7. FOLLOW different routes in each branch
   Branch 1: follow toward principal_registry.py
   Branch 2: follow toward pool.py

8. COMPARE the two branches
   m1nd.perspective.compare(perspective_id_a="persp_jimi_001", perspective_id_b="persp_jimi_002")
   -> shared: [auth_discovery.py], unique_to_a: [principal_registry.py], unique_to_b: [pool.py]

9. BACK to undo the last follow in branch 1
   m1nd.perspective.back()
   -> restored focus: auth_discovery.py

10. CLOSE both perspectives when done
    m1nd.perspective.close(perspective_id="persp_jimi_001")
    m1nd.perspective.close(perspective_id="persp_jimi_002")

This workflow demonstrates the full exploration cycle: start with a query, navigate through the graph by following routes, branch to explore alternatives, compare branches, and close when done.

Lifecycle & Lock Tools

Eight tools for graph ingestion, health monitoring, plan validation, and subgraph locking with change detection.


m1nd.ingest

Ingest or re-ingest a codebase, descriptor, or memory corpus into the graph. This is the primary way to load data into m1nd. Supports three adapters (code, json, memory) and two modes (replace, merge).

Parameters

ParameterTypeRequiredDefaultDescription
pathstringYesFilesystem path to the source root or memory corpus.
agent_idstringYesCalling agent identifier.
incrementalbooleanNofalseIncremental ingest (code adapter only). Only re-processes files that changed since the last ingest.
adapterstringNo"code"Adapter to use for parsing. Values: "code" (source code – Python, Rust, TypeScript, etc.), "json" (graph snapshot JSON), "memory" (markdown memory corpus).
modestringNo"replace"How to handle the existing graph. Values: "replace" (clear and rebuild), "merge" (add new nodes/edges into existing graph).
namespacestringNoOptional namespace tag for non-code nodes. Used by memory and json adapters to prefix node external_ids.

Example Request

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "m1nd.ingest",
    "arguments": {
      "agent_id": "orchestrator",
      "path": "/path/to/your/project",
      "adapter": "code",
      "mode": "replace",
      "incremental": false
    }
  }
}

Example Response

{
  "files_processed": 335,
  "nodes_created": 9767,
  "edges_created": 26557,
  "languages": { "python": 335 },
  "elapsed_ms": 910.0
}

Adapters

AdapterInputNode TypesEdge Types
codeSource code directoryfile, class, function, struct, moduleimports, calls, registers, configures, tests, inherits
jsonGraph snapshot JSON(preserved from snapshot)(preserved from snapshot)
memoryMarkdown filesdocument, concept, entityreferences, relates_to

Mode Behavior

ModeBehavior
replaceClears the existing graph, ingests fresh, finalizes (PageRank + CSR). All perspectives and locks are invalidated.
mergeAdds new nodes and edges into the existing graph. Existing nodes are updated if they share the same external_id. Graph is re-finalized after merge.

When to Use

  • Session start – ingest the codebase if the graph is empty or stale
  • After code changes – re-ingest incrementally to update the graph
  • Multi-source – merge a memory corpus into a code graph for cross-domain queries
  • Federation preparation – use m1nd.federate instead for multi-repo ingestion

Side Effects

  • replace mode: clears all graph state, invalidates all perspectives and locks, marks lock baselines as stale
  • merge mode: adds to graph, increments graph generation, triggers watcher events on affected locks

m1nd.health

Server health and statistics. Returns node/edge counts, query count, uptime, memory usage, plasticity state, and active sessions.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier.

Example Request

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "m1nd.health",
    "arguments": {
      "agent_id": "orchestrator"
    }
  }
}

Example Response

{
  "status": "healthy",
  "node_count": 9767,
  "edge_count": 26557,
  "queries_processed": 142,
  "uptime_seconds": 3600.5,
  "memory_usage_bytes": 52428800,
  "plasticity_state": "active",
  "last_persist_time": "2026-03-13T10:30:00Z",
  "active_sessions": [
    { "agent_id": "orchestrator", "last_active": "2026-03-13T11:25:00Z" }
  ]
}

When to Use

  • Session start – first tool to call; verify the server is alive and the graph is loaded
  • Monitoring – periodic health checks in long sessions
  • Debugging – check memory usage and query counts

m1nd.validate_plan

Validate a proposed modification plan against the code graph. Detects gaps (affected files missing from the plan), risk level, test coverage, and suggested additions. Designed to be called before implementing a plan.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier.
actionsobject[]YesOrdered list of planned actions. Each object has: action_type (string, required – "modify", "create", "delete", "rename", "test"), file_path (string, required – relative path), description (string, optional), depends_on (string[], optional – other file_paths this action depends on).
include_test_impactbooleanNotrueAnalyze test coverage for modified files.
include_risk_scorebooleanNotrueCompute composite risk score.

Example Request

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "m1nd.validate_plan",
    "arguments": {
      "agent_id": "orchestrator",
      "actions": [
        { "action_type": "modify", "file_path": "backend/pool.py", "description": "Add connection timeout" },
        { "action_type": "modify", "file_path": "backend/worker.py", "description": "Update pool acquire to use timeout" },
        { "action_type": "test", "file_path": "backend/tests/test_pool.py" }
      ]
    }
  }
}

Example Response

{
  "actions_analyzed": 3,
  "actions_resolved": 3,
  "actions_unresolved": 0,
  "gaps": [
    {
      "file_path": "backend/config.py",
      "node_id": "file::config.py",
      "reason": "imported by modified file pool.py -- timeout config likely needed here",
      "severity": "warning",
      "signal_strength": 0.72
    },
    {
      "file_path": "backend/process_manager.py",
      "node_id": "file::process_manager.py",
      "reason": "in blast radius of worker.py -- calls worker.submit",
      "severity": "info",
      "signal_strength": 0.45
    }
  ],
  "risk_score": 0.52,
  "risk_level": "medium",
  "test_coverage": {
    "modified_files": 2,
    "tested_files": 1,
    "untested_files": ["backend/worker.py"],
    "coverage_ratio": 0.5
  },
  "suggested_additions": [
    { "action_type": "modify", "file_path": "backend/config.py", "reason": "Timeout configuration likely needed" },
    { "action_type": "test", "file_path": "backend/tests/test_worker.py", "reason": "Modified file has no test action in plan" }
  ],
  "blast_radius_total": 47,
  "elapsed_ms": 35.0
}

Risk Levels

LevelScore RangeMeaning
"low"< 0.3Small, well-tested change
"medium"0.3 – 0.6Moderate scope, some gaps
"high"0.6 – 0.8Large scope or missing tests
"critical">= 0.8Very high risk – review carefully

When to Use

  • Before implementing – validate your plan catches all affected files
  • PR review – validate that a PR’s changes are complete
  • Planning – estimate risk and scope before committing to a plan
  • Quality gate – reject plans with risk_score > threshold

m1nd.lock.create

Pin a subgraph region and capture a baseline snapshot for change monitoring. Locks are used to track what changes in a region of the graph while you work. The baseline is compared against the current state when you call lock.diff.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier. Lock is owned by this agent.
scopestringYesScope type. Values: "node" (single nodes only), "subgraph" (BFS expansion from roots), "query_neighborhood" (nodes matching a query), "path" (ordered node list).
root_nodesstring[]YesRoot nodes for the lock scope. Non-empty. Matched by external_id (exact), then label, then substring.
radiusintegerNoBFS radius for subgraph scope. Range: 1 to 4. Required for subgraph scope.
querystringNoQuery string for query_neighborhood scope.
path_nodesstring[]NoOrdered node list for path scope.

Scope Types

ScopeDescription
nodeLock only the specified root nodes
subgraphBFS expansion from root nodes up to radius hops
query_neighborhoodNodes matching a query activation
pathAn ordered list of nodes forming a path

Example Request

{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tools/call",
  "params": {
    "name": "m1nd.lock.create",
    "arguments": {
      "agent_id": "orchestrator",
      "scope": "subgraph",
      "root_nodes": ["file::handler.py"],
      "radius": 2
    }
  }
}

Example Response

{
  "lock_id": "lock_jimi_001",
  "scope": "subgraph",
  "baseline_nodes": 1639,
  "baseline_edges": 707,
  "graph_generation": 42,
  "created_at_ms": 1710300000000
}

Limits

  • Max locks per agent: configurable (default: 10)
  • Max baseline nodes: configurable (default: 5000)
  • Max baseline edges: configurable (default: 10000)
  • Total memory budget shared with perspectives

When to Use

  • Change monitoring – lock a region before making changes, then diff to see what changed
  • Multi-agent coordination – lock regions to detect when other agents’ changes affect your work
  • Regression detection – lock a stable region and watch for unexpected changes

m1nd.lock.watch

Set a watcher strategy on a lock. Watchers determine when the lock automatically detects changes. Without a watcher, you must manually call lock.diff.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier. Must own the lock.
lock_idstringYesLock to set the watcher on.
strategystringYesWatcher strategy. Values: "manual" (no automatic detection), "on_ingest" (detect after every ingest), "on_learn" (detect after every learn call). Note: "periodic" is not supported in V1.

Example Request

{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "tools/call",
  "params": {
    "name": "m1nd.lock.watch",
    "arguments": {
      "agent_id": "orchestrator",
      "lock_id": "lock_jimi_001",
      "strategy": "on_ingest"
    }
  }
}

Example Response

{
  "lock_id": "lock_jimi_001",
  "strategy": "on_ingest",
  "previous_strategy": null
}

When to Use

  • Automatic monitoring – set on_ingest to detect changes after every code re-ingest
  • Learning feedback – set on_learn to detect when learning shifts edge weights in your region
  • Manual control – set manual to disable automatic detection

m1nd.lock.diff

Compute what changed in a locked region since the baseline was captured. Returns new/removed nodes, new/removed edges, weight changes, and watcher event counts. Fast-path: if the graph generation has not changed, returns immediately with no_changes: true.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier. Must own the lock.
lock_idstringYesLock to diff.

Example Request

{
  "jsonrpc": "2.0",
  "id": 6,
  "method": "tools/call",
  "params": {
    "name": "m1nd.lock.diff",
    "arguments": {
      "agent_id": "orchestrator",
      "lock_id": "lock_jimi_001"
    }
  }
}

Example Response (changes detected)

{
  "diff": {
    "lock_id": "lock_jimi_001",
    "no_changes": false,
    "new_nodes": ["file::handler.py::fn::new_method"],
    "removed_nodes": [],
    "new_edges": ["handler.py|new_method->parser.py|calls"],
    "removed_edges": [],
    "boundary_edges_added": [],
    "boundary_edges_removed": [],
    "weight_changes": [
      { "edge_key": "handler.py|parser.py|imports", "old_weight": 0.5, "new_weight": 0.72 }
    ],
    "baseline_stale": false,
    "elapsed_ms": 0.08
  },
  "watcher_events_drained": 2,
  "rebase_suggested": null
}

Example Response (no changes)

{
  "diff": {
    "lock_id": "lock_jimi_001",
    "no_changes": true,
    "new_nodes": [],
    "removed_nodes": [],
    "new_edges": [],
    "removed_edges": [],
    "boundary_edges_added": [],
    "boundary_edges_removed": [],
    "weight_changes": [],
    "baseline_stale": false,
    "elapsed_ms": 0.001
  },
  "watcher_events_drained": 0,
  "rebase_suggested": null
}

When to Use

  • After ingest – check if your locked region was affected
  • After learning – check if feedback shifted weights in your region
  • Periodic check – poll for changes during long sessions
  • Before committing – verify no unexpected changes in the region

m1nd.lock.rebase

Re-capture the lock baseline from the current graph without releasing the lock. Use this after calling lock.diff and acknowledging the changes – the new baseline becomes the reference for future diffs.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier. Must own the lock.
lock_idstringYesLock to rebase.

Example Request

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "m1nd.lock.rebase",
    "arguments": {
      "agent_id": "orchestrator",
      "lock_id": "lock_jimi_001"
    }
  }
}

Example Response

{
  "lock_id": "lock_jimi_001",
  "previous_generation": 42,
  "new_generation": 45,
  "baseline_nodes": 1645,
  "baseline_edges": 712,
  "watcher_preserved": true
}

When to Use

  • After acknowledging changes – rebase after reviewing a diff to reset the baseline
  • After stale warning – when lock.diff returns baseline_stale: true, rebase to fix it
  • Periodic refresh – rebase periodically in long sessions to keep baselines current

m1nd.lock.release

Release a lock and free its resources. Removes the lock state, cleans up pending watcher events, and frees memory. Irreversible.

Parameters

ParameterTypeRequiredDefaultDescription
agent_idstringYesCalling agent identifier. Must own the lock.
lock_idstringYesLock to release.

Example Request

{
  "jsonrpc": "2.0",
  "id": 8,
  "method": "tools/call",
  "params": {
    "name": "m1nd.lock.release",
    "arguments": {
      "agent_id": "orchestrator",
      "lock_id": "lock_jimi_001"
    }
  }
}

Example Response

{
  "lock_id": "lock_jimi_001",
  "released": true
}

When to Use

  • Done monitoring – release when you no longer need change detection
  • Memory pressure – locks consume memory proportional to baseline size
  • Session end – release all locks before ending a session
  • Automatic: locks are also cascade-released when their associated perspective is closed via perspective.close

Quick Start

Five minutes from zero to your first query.

Prerequisites

  • Rust toolchain: 1.75+ (install via rustup)
  • A codebase: Any project with Python, Rust, TypeScript/JavaScript, Go, or Java files. Other languages use a generic fallback extractor.

Installation

git clone https://github.com/cosmophonix/m1nd.git
cd m1nd
cargo build --release

The binary is at ./target/release/m1nd-mcp (~8MB). Copy it wherever you want:

cp target/release/m1nd-mcp /usr/local/bin/

From crates.io

cargo install m1nd-mcp

Verify the build

m1nd-mcp --help

The binary should start and wait for JSON-RPC input on stdin. Press Ctrl+C to exit.

Configuration

m1nd is an MCP server that communicates over stdio using JSON-RPC. You configure it in your AI client’s MCP settings.

Claude Code

Add to your Claude Code MCP configuration (.mcp.json in your project root, or ~/.claude/mcp.json globally):

{
  "mcpServers": {
    "m1nd": {
      "command": "/path/to/m1nd-mcp",
      "env": {
        "M1ND_GRAPH_SOURCE": "/tmp/m1nd-graph.json",
        "M1ND_PLASTICITY_STATE": "/tmp/m1nd-plasticity.json"
      }
    }
  }
}

Cursor

In Cursor settings, navigate to MCP Servers and add:

{
  "m1nd": {
    "command": "/path/to/m1nd-mcp",
    "env": {
      "M1ND_GRAPH_SOURCE": "/tmp/m1nd-graph.json",
      "M1ND_PLASTICITY_STATE": "/tmp/m1nd-plasticity.json"
    }
  }
}

Windsurf / Cline / Roo Code / Continue

Any MCP-compatible client works. The pattern is the same: point the client at the m1nd-mcp binary and optionally set the environment variables for persistence.

Environment Variables

VariablePurposeDefault
M1ND_GRAPH_SOURCEPath to persist graph state between sessionsIn-memory only (lost on exit)
M1ND_PLASTICITY_STATEPath to persist learned edge weightsIn-memory only (lost on exit)

Recommendation: Always set both variables. Without persistence, every restart discards learned weights and you lose the graph’s accumulated intelligence.

Advanced Configuration

The server accepts these configuration parameters (set via environment):

ParameterDefaultPurpose
learning_rate0.08How aggressively the graph learns from feedback
decay_rate0.005Rate of edge weight decay over time
xlr_enabledtrueEnable XLR noise cancellation
auto_persist_interval50Persist state every N queries
max_concurrent_reads32Maximum concurrent read operations
domain“code”Domain preset: code, music, memory, or generic

First Run: Ingest a Project

Once your MCP client is configured and m1nd is running, ingest your codebase. This is the foundation for everything else.

If you are using m1nd through an MCP client like Claude Code, the client sends the JSON-RPC calls for you when you invoke the tools. The raw JSON-RPC is shown here for clarity.

Step 1: Ingest

// Ingest your project
{
  "method": "tools/call",
  "params": {
    "name": "m1nd.ingest",
    "arguments": {
      "path": "/path/to/your/project",
      "agent_id": "dev"
    }
  }
}

Expected response (times will vary by project size):

{
  "files_processed": 335,
  "nodes_created": 9767,
  "edges_created": 26557,
  "languages": {"python": 335},
  "elapsed_ms": 910
}

What happened: m1nd parsed every file, extracted structural elements (modules, classes, functions, imports), resolved references between them, built a compressed graph, and computed PageRank centrality for every node.

Step 2: Verify with Health Check

{
  "method": "tools/call",
  "params": {
    "name": "m1nd.health",
    "arguments": {
      "agent_id": "dev"
    }
  }
}

Expected response:

{
  "status": "ok",
  "nodes": 9767,
  "edges": 26557,
  "finalized": true,
  "plasticity_records": 0,
  "agents_seen": ["dev"],
  "queries_served": 1,
  "uptime_seconds": 12
}

If you see "nodes": 0, re-check the path you passed to ingest.

Step 3: Your First Query

{
  "method": "tools/call",
  "params": {
    "name": "m1nd.activate",
    "arguments": {
      "query": "authentication",
      "agent_id": "dev",
      "top_k": 5
    }
  }
}

Expected response:

{
  "activated": [
    {
      "node_id": "file::auth.py",
      "score": 0.89,
      "dimension_scores": {
        "structural": 0.92,
        "semantic": 0.95,
        "temporal": 0.78,
        "causal": 0.71
      }
    },
    {"node_id": "file::middleware.py", "score": 0.72},
    {"node_id": "file::session.py", "score": 0.61}
  ],
  "ghost_edges": [
    {"from": "file::auth.py", "to": "file::rate_limiter.py", "confidence": 0.34}
  ]
}

What happened: m1nd fired a signal into the graph from nodes matching “authentication” and let it propagate across structural, semantic, temporal, and causal dimensions. Noise was cancelled via XLR differential processing. The results are ranked by multi-dimensional activation score, not just text matching.

What Next

You now have a working m1nd instance. From here:

  • First Query Tutorial: Step-by-step walkthrough of the full activate-learn-activate cycle, counterfactual simulation, and structural hole detection.
  • Multi-Agent Tutorial: How to use m1nd with multiple agents sharing one graph.
  • FAQ: Common questions and answers.

Troubleshooting

“No graph snapshot found, starting fresh” – This is normal on first run. The graph is empty until you call ingest.

Ingest returns 0 files – Check that the path is correct and contains supported source files. m1nd currently has extractors for Python (.py), Rust (.rs), TypeScript/JavaScript (.ts/.js/.tsx/.jsx), Go (.go), and Java (.java). Other files use a generic fallback extractor.

MCP client does not see m1nd tools – Verify the binary path is correct and the binary is executable (chmod +x m1nd-mcp). Check your client’s MCP server logs for connection errors.

Permission denied – Ensure the M1ND_GRAPH_SOURCE and M1ND_PLASTICITY_STATE paths are writable.

First Query: The Full Cycle

This tutorial walks you through the core m1nd workflow: ingest, activate, learn, and observe the graph getting smarter. Then we explore structural holes and counterfactual simulation.

Prerequisites: You have completed the Quick Start and have m1nd running with a codebase ingested.

All examples use the JSON-RPC wire format. If you are working through an MCP client (Claude Code, Cursor, etc.), the client sends these calls for you when you invoke the tools by name.

Step 1: Ingest Your Codebase

If you have not already ingested, do it now:

{
  "method": "tools/call",
  "params": {
    "name": "m1nd.ingest",
    "arguments": {
      "path": "/your/project",
      "agent_id": "dev"
    }
  }
}

Response:

{
  "files_processed": 335,
  "nodes_created": 9767,
  "edges_created": 26557,
  "languages": {"python": 335},
  "elapsed_ms": 910
}

The graph now contains structural nodes (files, classes, functions) and edges (imports, calls, inheritance, co-change patterns). PageRank has been computed, giving each node a centrality score.

Step 2: First Activation

Ask the graph about session pool management:

{
  "method": "tools/call",
  "params": {
    "name": "m1nd.activate",
    "arguments": {
      "query": "session pool management",
      "agent_id": "dev",
      "top_k": 5
    }
  }
}

Response:

{
  "activated": [
    {
      "node_id": "file::pool.py",
      "score": 0.89,
      "dimension_scores": {
        "structural": 0.92,
        "semantic": 0.95,
        "temporal": 0.78,
        "causal": 0.71
      }
    },
    {"node_id": "file::pool.py::class::ConnectionPool", "score": 0.84},
    {"node_id": "file::worker.py", "score": 0.61},
    {"node_id": "file::pool.py::fn::acquire", "score": 0.58},
    {"node_id": "file::process_manager.py", "score": 0.45}
  ],
  "ghost_edges": [
    {
      "from": "file::pool.py",
      "to": "file::recovery.py",
      "confidence": 0.34
    }
  ]
}

Reading the results:

  • score: Combined 4-dimensional activation score (0.0 to 1.0)
  • dimension_scores: Breakdown by structural (graph distance, PageRank), semantic (token overlap), temporal (co-change history), and causal (suspiciousness)
  • ghost_edges: Connections the graph inferred but that are not explicit in code. Here, pool.py and recovery.py are structurally unconnected but co-activate together – a hidden dependency worth investigating.

Note the scores. We will come back to this query after teaching the graph.

Step 3: Teach the Graph (Hebbian Learning)

The top two results (pool.py and the ConnectionPool class) were exactly what we needed. Tell the graph:

{
  "method": "tools/call",
  "params": {
    "name": "m1nd.learn",
    "arguments": {
      "query": "session pool management",
      "agent_id": "dev",
      "feedback": "correct",
      "node_ids": [
        "file::pool.py",
        "file::pool.py::class::ConnectionPool"
      ],
      "strength": 0.2
    }
  }
}

Response:

{
  "edges_strengthened": 740,
  "edges_weakened": 0,
  "plasticity_records": 740,
  "learning_type": "hebbian_ltp"
}

What happened: Hebbian Long-Term Potentiation (LTP) strengthened 740 edges along paths connecting the confirmed-useful nodes. “Neurons that fire together wire together.” The next time anyone queries this region of the graph, those paths carry more signal.

Now suppose worker.py (score 0.61) was not actually relevant. Mark it wrong:

{
  "method": "tools/call",
  "params": {
    "name": "m1nd.learn",
    "arguments": {
      "query": "session pool management",
      "agent_id": "dev",
      "feedback": "wrong",
      "node_ids": ["file::worker.py"],
      "strength": 0.2
    }
  }
}

Response:

{
  "edges_strengthened": 0,
  "edges_weakened": 312,
  "plasticity_records": 1052,
  "learning_type": "hebbian_ltd"
}

Long-Term Depression (LTD) weakened 312 edges leading to worker.py from this query region. The graph now knows: for session pool queries, worker.py is noise.

Step 4: Activate Again – See the Improvement

Run the exact same query:

{
  "method": "tools/call",
  "params": {
    "name": "m1nd.activate",
    "arguments": {
      "query": "session pool management",
      "agent_id": "dev",
      "top_k": 5
    }
  }
}

Expected changes:

{
  "activated": [
    {"node_id": "file::pool.py", "score": 0.93},
    {"node_id": "file::pool.py::class::ConnectionPool", "score": 0.88},
    {"node_id": "file::pool.py::fn::acquire", "score": 0.65},
    {"node_id": "file::process_manager.py", "score": 0.47},
    {"node_id": "file::recovery.py", "score": 0.39}
  ]
}

Compare with Step 2:

NodeBeforeAfterChange
pool.py0.890.93+0.04 (strengthened)
ConnectionPool class0.840.88+0.04 (strengthened)
worker.py0.61droppedPushed below top-5 (weakened)
recovery.pyghost only0.39Promoted from ghost to main results

The graph learned. worker.py fell out of the top results. recovery.py, previously only a ghost edge, got promoted because the strengthened paths through pool.py now carry more signal to its neighborhood.

This is the core value proposition of m1nd: every interaction makes the graph smarter. No other code intelligence tool does this.

Step 5: Structural Hole Detection

Ask the graph what is missing around a topic:

{
  "method": "tools/call",
  "params": {
    "name": "m1nd.missing",
    "arguments": {
      "query": "database connection pooling",
      "agent_id": "dev"
    }
  }
}

Response:

{
  "holes": [
    {
      "region": "connection lifecycle",
      "adjacent_nodes": 4,
      "description": "No dedicated connection pool abstraction"
    },
    {
      "region": "pool metrics",
      "adjacent_nodes": 3,
      "description": "No pool health monitoring"
    },
    {
      "region": "graceful drain",
      "adjacent_nodes": 2,
      "description": "No connection drain on shutdown"
    }
  ],
  "total_holes": 9
}

What happened: m1nd activated the “database connection pooling” region of the graph and looked for gaps – areas where the graph’s structure predicts a node should exist but none does. These are structural holes: places where other codebases of similar shape would have components but yours does not.

This is not a linter or rule-based checker. It is topology-based gap detection. The graph’s shape implies these components should exist, based on the relationships between the nodes that do exist.

Step 6: Counterfactual Simulation

Before deleting or rewriting a module, simulate the consequences:

{
  "method": "tools/call",
  "params": {
    "name": "m1nd.counterfactual",
    "arguments": {
      "node_ids": ["file::worker.py"],
      "agent_id": "dev"
    }
  }
}

Response:

{
  "cascade": [
    {"depth": 1, "affected": 23},
    {"depth": 2, "affected": 456},
    {"depth": 3, "affected": 3710}
  ],
  "total_affected": 4189,
  "orphaned_count": 0,
  "pct_activation_lost": 0.41
}

Reading the results:

  • Depth 1: 23 nodes directly depend on worker.py
  • Depth 2: 456 more nodes depend on those 23
  • Depth 3: 3,710 more – a cascade explosion
  • Total: 4,189 nodes affected out of ~9,767 (42.9% of the graph)
  • Activation lost: 41% of the graph’s total activation capacity would be disrupted

Compare this with removing config.py:

{
  "method": "tools/call",
  "params": {
    "name": "m1nd.counterfactual",
    "arguments": {
      "node_ids": ["file::config.py"],
      "agent_id": "dev"
    }
  }
}

Response:

{
  "cascade": [
    {"depth": 1, "affected": 89},
    {"depth": 2, "affected": 1234},
    {"depth": 3, "affected": 1208}
  ],
  "total_affected": 2531,
  "orphaned_count": 3,
  "pct_activation_lost": 0.28
}

Despite config.py having more direct dependents (89 vs 23), its total cascade is smaller (2,531 vs 4,189). worker.py sits at a structural chokepoint where downstream nodes have more transitive dependencies. This insight is impossible to get from grep or import analysis alone – it requires full graph traversal.

Step 7: Hypothesis Testing (Bonus)

Test a structural claim against the graph:

{
  "method": "tools/call",
  "params": {
    "name": "m1nd.hypothesize",
    "arguments": {
      "claim": "worker depends on messaging at runtime",
      "agent_id": "dev"
    }
  }
}

Response:

{
  "verdict": "likely_true",
  "confidence": 0.72,
  "paths_explored": 25015,
  "evidence": [
    {
      "path": [
        "file::worker.py",
        "file::process_manager.py::fn::cancel",
        "file::messaging.py"
      ],
      "hops": 2
    }
  ],
  "note": "2-hop dependency via cancel function -- invisible to grep"
}

The hypothesis engine explored 25,015 paths in 58ms and found a 2-hop dependency that no text search could reveal: the worker pool reaches the WhatsApp manager through a cancel function in the process manager. This is the kind of hidden coupling that causes production incidents.

Summary: The Learning Loop

The core m1nd workflow is a feedback loop:

ingest  -->  activate  -->  use results  -->  learn  -->  activate again
  |                                              |
  |              (graph gets smarter)            |
  +----------------------------------------------+

Every learn call shifts edge weights. Every subsequent activate benefits from accumulated learning. Over sessions, the graph adapts to how your team thinks about your codebase.

Additional tools layer on top of this foundation:

ToolWhen to Use
missingBefore designing new features – find what your codebase lacks
counterfactualBefore deleting or rewriting – simulate the blast radius
hypothesizeWhen debugging – test assumptions about hidden dependencies
impactBefore modifying a file – understand the blast radius
predictAfter modifying a file – which other files probably need changes too
traceWhen an error occurs – map stacktraces to structural root causes

Next: Multi-Agent Tutorial – how multiple agents share one graph.

Multi-Agent Usage

m1nd is designed for multi-agent systems. One m1nd instance serves many agents simultaneously. This tutorial covers agent identity, concurrent access, perspective isolation, trail sharing, and a real-world example of how a production system uses m1nd with a fleet of agents.

How It Works

m1nd runs as a single process with a single shared graph. Multiple MCP clients connect to the same instance (or multiple instances reading from the same persisted state). Every tool call includes an agent_id parameter that identifies the caller.

  Agent A (Claude Code)  ----+
                              |
  Agent B (Cursor)       ----+----> m1nd-mcp (single graph)
                              |
  Agent C (custom agent) ----+

The graph is shared. Learning by one agent benefits all agents. Perspectives are isolated per agent. Trails can be shared across agents.

Agent ID Conventions

Every m1nd tool requires an agent_id parameter. This is a free-form string, but consistent naming matters:

agent_id: "orchestrator"          -- orchestrator
agent_id: "auditor-1"   -- security hardening agent
agent_id: "builder-api"     -- API building agent
agent_id: "analyzer-core"  -- performance analysis agent

Recommended convention: {archetype}-{task} for short-lived task agents, simple names for persistent agents.

Rules:

  • Agent IDs are case-sensitive
  • Use lowercase with hyphens
  • m1nd tracks all agent IDs it has seen (visible in health output)
  • Agent ID determines perspective ownership and trail ownership

Shared Graph, Individual Learning

When Agent A calls learn with feedback, the edge weight changes are visible to all agents immediately:

// Agent A: "pool.py was useful for my auth investigation"
{
  "method": "tools/call",
  "params": {
    "name": "m1nd.learn",
    "arguments": {
      "query": "authentication flow",
      "agent_id": "agent-a",
      "feedback": "correct",
      "node_ids": ["file::pool.py"]
    }
  }
}
// -> 740 edges strengthened
// Agent B: immediately benefits from stronger pool.py edges
{
  "method": "tools/call",
  "params": {
    "name": "m1nd.activate",
    "arguments": {
      "query": "session management",
      "agent_id": "agent-b",
      "top_k": 5
    }
  }
}
// -> pool.py scores higher than it would have before Agent A's feedback

This is by design. The graph represents collective intelligence about the codebase. Every agent’s learning contributes to the whole.

Perspective Isolation

Perspectives are m1nd’s navigation system – a stateful exploration session anchored to a node, with a route surface, breadcrumb history, and focus tracking.

Perspectives are isolated per agent. Agent A’s perspectives are not visible to Agent B, and navigation in one perspective does not affect another.

Agent A: Start a Perspective

{
  "method": "tools/call",
  "params": {
    "name": "m1nd.perspective.start",
    "arguments": {
      "agent_id": "agent-a",
      "query": "authentication middleware",
      "anchor_node": "file::middleware.py"
    }
  }
}

Response:

{
  "perspective_id": "persp-a1b2c3",
  "focus": "file::middleware.py",
  "routes": [
    {"route_id": "r-001", "target": "file::auth.py", "label": "imports", "score": 0.92},
    {"route_id": "r-002", "target": "file::session.py", "label": "calls", "score": 0.78}
  ],
  "route_set_version": 1
}

Agent B: Has Its Own Perspectives

{
  "method": "tools/call",
  "params": {
    "name": "m1nd.perspective.start",
    "arguments": {
      "agent_id": "agent-b",
      "query": "worker pool scaling"
    }
  }
}

Agent B gets a completely independent perspective. Its routes, focus, and history are separate from Agent A’s.

Listing Perspectives

Each agent only sees its own:

// Agent A sees only its perspectives
{
  "method": "tools/call",
  "params": {
    "name": "m1nd.perspective.list",
    "arguments": {"agent_id": "agent-a"}
  }
}
// -> [{"perspective_id": "persp-a1b2c3", "focus": "file::middleware.py", ...}]

// Agent B sees only its perspectives
{
  "method": "tools/call",
  "params": {
    "name": "m1nd.perspective.list",
    "arguments": {"agent_id": "agent-b"}
  }
}
// -> [{"perspective_id": "persp-d4e5f6", "focus": "file::worker.py", ...}]

Comparing Perspectives Across Agents

You can explicitly compare two perspectives from different agents using perspective.compare. This is useful for discovering where two independent investigations overlap:

{
  "method": "tools/call",
  "params": {
    "name": "m1nd.perspective.compare",
    "arguments": {
      "agent_id": "agent-a",
      "perspective_id_a": "persp-a1b2c3",
      "perspective_id_b": "persp-d4e5f6"
    }
  }
}

Response:

{
  "shared_nodes": ["file::process_manager.py", "file::config.py"],
  "unique_to_a": ["file::auth.py", "file::middleware.py"],
  "unique_to_b": ["file::worker.py", "file::worker.py"],
  "dimension_deltas": {
    "structural": 0.12,
    "semantic": 0.34,
    "temporal": 0.08
  }
}

Lock System for Concurrent Access

When multiple agents might modify the same region of the codebase simultaneously, the lock system prevents conflicts.

Agent A: Lock the Auth Region

{
  "method": "tools/call",
  "params": {
    "name": "m1nd.lock.create",
    "arguments": {
      "agent_id": "agent-a",
      "scope": "subgraph",
      "root_nodes": ["file::auth.py"],
      "radius": 2
    }
  }
}

Response:

{
  "lock_id": "lock-xyz789",
  "nodes_locked": 156,
  "edges_locked": 423,
  "scope": "subgraph",
  "radius": 2
}

Set a Watch Strategy

{
  "method": "tools/call",
  "params": {
    "name": "m1nd.lock.watch",
    "arguments": {
      "agent_id": "agent-a",
      "lock_id": "lock-xyz789",
      "strategy": "on_ingest"
    }
  }
}

Now, whenever any agent triggers an ingest that touches the locked region, the lock records the changes.

Check for Changes

After Agent B modifies some code and re-ingests:

{
  "method": "tools/call",
  "params": {
    "name": "m1nd.lock.diff",
    "arguments": {
      "agent_id": "agent-a",
      "lock_id": "lock-xyz789"
    }
  }
}

Response (in 0.08 microseconds):

{
  "new_nodes": ["file::auth.py::fn::validate_token_v2"],
  "removed_nodes": [],
  "weight_changes": 3,
  "structural_changes": true
}

Agent A now knows exactly what changed in its locked region, without scanning the entire graph.

Rebase or Release

// Accept changes and update baseline
{"method":"tools/call","params":{"name":"m1nd.lock.rebase","arguments":{
  "agent_id":"agent-a","lock_id":"lock-xyz789"
}}}

// Or release when done
{"method":"tools/call","params":{"name":"m1nd.lock.release","arguments":{
  "agent_id":"agent-a","lock_id":"lock-xyz789"
}}}

Trail Sharing

Trails are investigation snapshots: visited nodes, hypotheses, conclusions, open questions, and activation boosts. They persist across sessions and can be shared between agents.

Agent A: Save an Investigation

{
  "method": "tools/call",
  "params": {
    "name": "m1nd.trail.save",
    "arguments": {
      "agent_id": "agent-a",
      "label": "auth-token-leak-investigation",
      "hypotheses": [
        {
          "statement": "Auth tokens leak through session pool",
          "confidence": 0.7,
          "status": "investigating"
        },
        {
          "statement": "Rate limiter missing from auth chain",
          "confidence": 0.9,
          "status": "confirmed"
        }
      ],
      "open_questions": [
        "Does the healing manager observe token lifecycle?",
        "Is there a token rotation policy?"
      ],
      "tags": ["security", "auth", "session"]
    }
  }
}

Response:

{
  "trail_id": "trail-abc123",
  "nodes_captured": 47,
  "hypotheses_saved": 2,
  "activation_boosts_saved": 12
}

Agent B: Resume Agent A’s Trail

{
  "method": "tools/call",
  "params": {
    "name": "m1nd.trail.resume",
    "arguments": {
      "agent_id": "agent-b",
      "trail_id": "trail-abc123"
    }
  }
}

Response:

{
  "trail_id": "trail-abc123",
  "nodes_reactivated": 47,
  "stale_nodes": 2,
  "hypotheses_restored": 2,
  "hypotheses_downgraded": 0
}

Agent B now has Agent A’s exact cognitive context: the same nodes are activated, the same hypotheses are loaded, and any stale nodes (changed since the trail was saved) are flagged.

Merging Trails from Multiple Agents

When two agents investigate independently and you want to combine findings:

{
  "method": "tools/call",
  "params": {
    "name": "m1nd.trail.merge",
    "arguments": {
      "agent_id": "orchestrator",
      "trail_ids": ["trail-abc123", "trail-def456"],
      "label": "combined-auth-investigation"
    }
  }
}

Response:

{
  "merged_trail_id": "trail-merged-789",
  "total_nodes": 83,
  "shared_nodes": 12,
  "conflicts": [
    {
      "node": "file::auth.py",
      "trail_a_hypothesis": "token leak source",
      "trail_b_hypothesis": "not involved"
    }
  ],
  "conflict_count": 3
}

The merge automatically detects where independent investigations converged (12 shared nodes) and where they conflict (3 disagreements). This is essential for synthesizing multi-agent research.

Browsing Trails

{
  "method": "tools/call",
  "params": {
    "name": "m1nd.trail.list",
    "arguments": {
      "agent_id": "orchestrator",
      "filter_tags": ["security"]
    }
  }
}

Real-World Example: Multi-Agent Production System

a production system is a multi-agent orchestration system that uses m1nd as its shared code intelligence layer. Here is how it works in production:

Architecture

orchestrator
  |
  +-- auditor-1 (security agent)     --+
  +-- builder-api (API builder)           --+-- All share one m1nd instance
  +-- analyzer-core (performance)        --+
  +-- sentinel-files (file watcher)     --+
  +-- critic-quality (code reviewer)    --+
  +-- ... 14 more agents               --+

One m1nd instance serves all agents. The graph covers a 335-file Python backend (~52K lines), a React frontend, and MCP server infrastructure.

Orchestrator Boot Sequence

When the orchestrator starts a session:

// Step 1: Check m1nd health
{"method":"tools/call","params":{"name":"m1nd.health","arguments":{"agent_id":"orchestrator"}}}

// Step 2: Check what changed since last session
{"method":"tools/call","params":{"name":"m1nd.drift","arguments":{"agent_id":"orchestrator","since":"last_session"}}}

// Step 3: Re-ingest if the graph is stale
{"method":"tools/call","params":{"name":"m1nd.ingest","arguments":{
  "path":"/project/backend","agent_id":"orchestrator","incremental":true
}}}

Task Delegation with Graph Context

When the orchestrator delegates a security hardening task:

// Before spawning the security agent, get blast radius context
{"method":"tools/call","params":{"name":"m1nd.impact","arguments":{
  "node_id":"file::auth.py","agent_id":"orchestrator"
}}}

// Warm up the graph for the security task
{"method":"tools/call","params":{"name":"m1nd.warmup","arguments":{
  "task_description":"harden authentication token validation","agent_id":"orchestrator"
}}}

// The security agent then uses the primed graph
{"method":"tools/call","params":{"name":"m1nd.activate","arguments":{
  "query":"token validation vulnerabilities","agent_id":"auditor-1"
}}}

Collective Learning

After each agent completes its task, it provides feedback:

// Security agent found useful results
{"method":"tools/call","params":{"name":"m1nd.learn","arguments":{
  "query":"token validation vulnerabilities",
  "agent_id":"auditor-1",
  "feedback":"correct",
  "node_ids":["file::auth.py","file::middleware.py","file::pool.py"]
}}}

// Performance agent found different useful results
{"method":"tools/call","params":{"name":"m1nd.learn","arguments":{
  "query":"connection pool bottleneck",
  "agent_id":"analyzer-core",
  "feedback":"correct",
  "node_ids":["file::worker.py","file::process_manager.py"]
}}}

Over a session with many agents, the graph accumulates thousands of learning signals. Each agent benefits from every other agent’s discoveries.

Investigation Handoff

When Agent A finds something that Agent B needs to investigate:

// Agent A saves its investigation
{"method":"tools/call","params":{"name":"m1nd.trail.save","arguments":{
  "agent_id":"auditor-1",
  "label":"session-hijack-vector",
  "tags":["security","critical"]
}}}

// Orchestrator merges with Agent B's independent findings
{"method":"tools/call","params":{"name":"m1nd.trail.merge","arguments":{
  "agent_id":"orchestrator",
  "trail_ids":["trail-hacker-001","trail-analyst-002"]
}}}

Best Practices

  1. Use consistent agent IDs. The same agent should always use the same ID across sessions. This enables drift detection and trail continuity.

  2. Learn after every useful activation. The more feedback the graph gets, the smarter it becomes. Make learn calls automatic in your agent loop.

  3. Use locks for overlapping work. If two agents might modify the same code region, lock it first. Lock diffs are essentially free (0.08 microseconds).

  4. Save trails at investigation checkpoints. Trails are cheap to save and invaluable for handoff, resume, and post-mortem analysis.

  5. Merge trails for synthesis. When multiple agents investigate the same area independently, merge their trails to find convergence and conflicts.

  6. Warm up before focused tasks. warmup primes the graph for a specific task, boosting relevant regions before the agent starts querying.

  7. Use drift at session start. After any period of inactivity, check what changed. This recovers context efficiently.

Frequently Asked Questions

General

What is m1nd?

m1nd is a neuro-symbolic connectome engine for code intelligence. It parses your codebase into a weighted graph and provides 43 MCP tools for querying, learning, and navigating it. Built in Rust, it runs locally as an MCP server and works with any MCP-compatible AI agent (Claude Code, Cursor, Windsurf, Zed, etc.).

The key differentiator: the graph learns. When you tell m1nd which results were useful and which were not, it adjusts edge weights via Hebbian plasticity. Over time, the graph adapts to how your team thinks about your codebase.

How is m1nd different from grep / ripgrep?

Grep finds text. m1nd finds structure and relationships.

Grep tells you which files contain the word “authentication”. m1nd tells you which modules are structurally connected to authentication, which ones are likely co-changed when auth changes, which hidden dependencies exist between auth and seemingly unrelated modules, and what would break if you removed the auth module.

Grep is fast and essential. m1nd answers questions that grep cannot even formulate.

How is m1nd different from RAG?

RAG (Retrieval-Augmented Generation) embeds code chunks into vectors and retrieves the top-K most similar chunks for each query. Each retrieval is independent – RAG has no memory of previous queries and no understanding of relationships between results.

m1nd maintains a persistent graph where relationships are first-class citizens. The graph learns from feedback, remembers investigations across sessions, and can answer structural questions (“what breaks if I delete X?”, “does A depend on B at runtime?”) that RAG cannot.

RAG is useful for semantic similarity search. m1nd is useful for structural reasoning. They are complementary, not competing.

How is m1nd different from static analysis tools (tree-sitter, ast-grep, Sourcegraph)?

Static analysis tools parse code into ASTs and compute call graphs, type hierarchies, and cross-references. These are accurate but frozen – they represent the code at a single point in time and cannot answer “what if?” questions.

m1nd uses similar structural information as a starting point but adds three things static analysis cannot: (1) Hebbian learning that adapts the graph based on usage, (2) temporal intelligence from co-change history, and (3) simulation engines for hypotheses and counterfactuals.

Sourcegraph is a search engine. m1nd is a reasoning engine.

Do I need an LLM to use m1nd?

No. m1nd makes zero LLM calls. It is pure Rust computation: graph algorithms, spreading activation, Hebbian plasticity. No API keys, no network calls, no token costs.

m1nd is designed to work alongside LLMs (as an MCP tool that agents call), but the graph engine itself is completely self-contained.

What languages does m1nd support?

m1nd has dedicated extractors for:

  • Python (.py)
  • Rust (.rs)
  • TypeScript (.ts, .tsx)
  • JavaScript (.js, .jsx)
  • Go (.go)
  • Java (.java)

All other file types use a generic fallback extractor that identifies functions, classes, and imports through heuristic pattern matching. The generic extractor produces a less detailed graph but still captures useful structure.

Tree-sitter integration is planned, which would add support for 64+ languages.

Is m1nd open source?

Yes. MIT license. Source at github.com/cosmophonix/m1nd.


Installation

What platforms does m1nd support?

Any platform where Rust compiles. Tested on:

  • macOS (ARM64 and x86_64)
  • Linux (x86_64, ARM64)
  • Windows (x86_64)

What is the minimum Rust version?

Rust 1.75 or later. The project uses the 2021 edition.

How large is the binary?

Approximately 8MB for a release build. No runtime dependencies, no shared libraries. The binary is fully self-contained.

Can I install from crates.io?

Yes: cargo install m1nd-mcp


Usage

How many files can m1nd handle?

Tested up to 10,000+ files. At ~2MB of memory for 10,000 nodes, a 100K-file codebase would need roughly 20MB for the graph. This is well within modern machine capacity.

At 400K+ files, the in-memory graph starts to become a consideration (~80MB), but it still works. m1nd was optimized for codebases in the 100 to 50,000 file range.

Does it work with non-code files?

Yes. m1nd has a json adapter for structured data and a memory adapter for text corpora. You can also use the generic fallback extractor for any text file. The graph is not limited to source code – it is a general-purpose knowledge graph that happens to have excellent code extractors.

How much memory does m1nd use?

The graph itself is compact: ~2MB for a 10,000-node graph with 26,000 edges (Compressed Sparse Row format). The MCP server process uses additional memory for the JSON-RPC layer, perspective state, lock baselines, and trail storage. A typical production instance serving a 335-file codebase uses under 50MB total.

Can I ingest multiple codebases into one graph?

Yes, through the federate tool. It ingests multiple repositories into a unified graph with automatic cross-repository edge detection:

{"method":"tools/call","params":{"name":"m1nd.federate","arguments":{
  "agent_id":"dev",
  "repos":[
    {"name":"backend","path":"/project/backend"},
    {"name":"frontend","path":"/project/frontend"}
  ]
}}}

Can I do incremental ingests?

Yes. Pass "incremental": true to the ingest tool. Incremental ingest only re-processes files that changed since the last ingest, preserving learned edge weights for unchanged regions.


Architecture

Where is the graph stored?

In memory during runtime. Optionally persisted to JSON files on disk via the M1ND_GRAPH_SOURCE and M1ND_PLASTICITY_STATE environment variables.

Without persistence configured, the graph is lost when the process exits. Always configure persistence for production use.

How often does it persist?

By default, every 50 queries. Also on shutdown. The auto_persist_interval configuration parameter controls this.

Can I export the graph?

The persisted graph state is a JSON file (graph_snapshot.json). You can read, copy, and process it with standard JSON tools. The format includes all nodes, edges, PageRank scores, and metadata.

The plasticity state is a separate JSON file (plasticity_state.json) containing per-edge synaptic records (learned weights).

What is the graph format?

Compressed Sparse Row (CSR) with forward and reverse adjacency lists. Each node has an external ID (e.g., file::auth.py), a type (file, class, function, module), metadata, and a PageRank score. Each edge has a type (imports, calls, inherits, co_change), a base weight, and an optional plasticity weight.

What is XLR noise cancellation?

Borrowed from professional audio engineering. Like a balanced XLR cable, m1nd transmits the activation signal on two inverted channels and subtracts common-mode noise at the receiver. This reduces false positives in activation queries by cancelling out signals that propagate through generic hub nodes (like config.py or utils.py) rather than through meaningful structural paths.

XLR is enabled by default. Pass "xlr": false to activate to disable it for a single query.


MCP Protocol

What MCP clients work with m1nd?

Any client that speaks MCP over stdio. Tested and verified:

  • Claude Code
  • Cursor
  • Windsurf
  • Zed
  • Cline
  • Roo Code
  • Continue
  • OpenCode
  • GitHub Copilot (MCP mode)
  • Amazon Q Developer

Can I use m1nd without MCP?

The server speaks JSON-RPC over stdio, which is the MCP transport. You can send raw JSON-RPC from any program that can write to stdin and read from stdout. No MCP client library is required – the protocol is just JSON over pipes.

What MCP protocol version does m1nd implement?

Protocol version 2024-11-05. The server reports this in the initialize response.

Does m1nd support MCP notifications?

Yes. The server silently ignores incoming notifications per the MCP specification.


Performance

How fast is spreading activation?

31-77ms for a 9,767-node graph, returning 15-20 ranked results. The variance depends on query specificity and graph density around the activated region.

How fast is ingest?

910ms for 335 Python files producing 9,767 nodes and 26,557 edges. This includes parsing, reference resolution, edge creation, and PageRank computation.

How fast is learning?

Sub-millisecond. A learn call with feedback on 2-3 nodes adjusts hundreds of edges in under 1ms.

What about 100K files?

Ingest would take roughly 30-45 seconds (linear scaling from the 335-file benchmark). Activation would be 100-200ms. The graph would occupy ~20MB in memory. These are estimates – actual performance depends on code density and language.

What is the lock diff speed?

0.08 microseconds (80 nanoseconds). Lock diff is a constant-time operation – it compares fingerprints, not individual nodes. It is essentially free.

Full benchmark table

All numbers from real execution against a production Python backend (335 files, ~52K lines):

OperationTimeScale
Full ingest910ms335 files, 9,767 nodes, 26,557 edges
Spreading activation31-77ms15 results from 9,767 nodes
Blast radius (depth=3)5-52msUp to 4,271 affected nodes
Stacktrace analysis3.5ms5 frames, 4 suspects ranked
Plan validation10ms7 files, 43,152 blast radius
Counterfactual cascade3msFull BFS on 26,557 edges
Hypothesis testing58ms25,015 paths explored
Pattern scan (all 8)38ms335 files, 50 findings per pattern
Multi-repo federation1.3s11,217 nodes, 18,203 cross-repo edges
Lock diff0.08us1,639-node subgraph comparison
Trail merge1.2ms5 hypotheses, 3 conflicts detected

Plasticity and Learning

How does Hebbian learning work?

“Neurons that fire together wire together.” When you call learn with feedback: "correct" and a list of node IDs, m1nd identifies all edges on paths between those nodes and increases their weights (Long-Term Potentiation, LTP). When you call with feedback: "wrong", it decreases weights on paths leading to the marked nodes (Long-Term Depression, LTD).

The strength parameter (default 0.2) controls how aggressively weights shift. The learning_rate server configuration (default 0.08) provides a global scaling factor.

Can I reset learned weights?

Yes. Delete the plasticity_state.json file and restart the server. Alternatively, re-ingest the codebase, which rebuilds the graph from scratch (but does not clear the plasticity state file – you need to delete it separately or pass "incremental": false and delete the plasticity file).

Does the graph overfit?

Hebbian learning includes homeostatic normalization to prevent runaway weight amplification. Edge weights are bounded and periodically normalized so that heavily-used paths do not completely dominate. The decay_rate parameter (default 0.005) provides a slow decay toward baseline weights over time.

In practice, overfitting requires thousands of feedback signals consistently reinforcing the same narrow paths. Normal usage produces a well-distributed weight landscape.

What is “partial” feedback?

The learn tool accepts three feedback types:

  • correct – strengthen paths (LTP)
  • wrong – weaken paths (LTD)
  • partial – mixed signal. Applies a mild strengthening (half the LTP strength) to acknowledged nodes while slightly weakening peripheral paths.

Perspectives

What are perspectives?

A perspective is a stateful navigation session through the graph. You start one with a query or anchor node, and m1nd synthesizes a “route surface” – a ranked set of paths you can follow. As you navigate (follow, back, branch), the perspective maintains breadcrumb history and updates the route surface.

Think of it as a browser for the code graph. You have a current “page” (focus node), links (routes), history (back button), and bookmarks (branches).

How many perspectives can be open simultaneously?

There is no hard limit. Each perspective occupies a small amount of memory (proportional to the number of visited nodes). In practice, 10-20 concurrent perspectives per agent is typical. Close perspectives you are done with to free memory.

Do perspectives persist across sessions?

Perspectives are in-memory only and are lost when the server restarts. For persistent investigation state, use the trail system (trail.save / trail.resume).

Can I branch a perspective?

Yes. perspective.branch forks the current navigation state into a new independent perspective. Both the original and the branch have the same history up to the branch point, and diverge afterward. This is useful for exploring “what if I go this way instead?” without losing your current position.


Comparison

m1nd vs tree-sitter

tree-sitter is a parser. It produces ASTs from source code. m1nd uses structural information similar to what tree-sitter extracts, but builds a weighted, learning graph on top of it. tree-sitter tells you what the code is. m1nd tells you what the code means in context, what changed, what might break, and what is missing.

They are complementary. tree-sitter integration is planned for m1nd to expand language support.

m1nd vs ast-grep

ast-grep is a structural search tool – it finds code patterns using AST matching. m1nd does not do pattern matching on syntax trees. Instead, it does graph-level reasoning: spreading activation, hypothesis testing, counterfactual simulation, learning. ast-grep answers “where does this pattern appear?” m1nd answers “what is connected to this, what breaks if it changes, and what am I missing?”

m1nd vs RAG (Retrieval-Augmented Generation)

RAG embeds code into vectors and retrieves similar chunks. Each retrieval is independent and stateless. m1nd maintains a persistent graph with relationships, learning, and investigation state. RAG cannot answer structural questions, simulate deletions, or learn from feedback.

RAG costs LLM tokens per query. m1nd costs zero tokens per query.

m1nd vs Sourcegraph / CodeGraph / SCIP

Sourcegraph provides cross-repository code search and navigation based on SCIP (Source Code Intelligence Protocol) indexing. It produces accurate, language-server-quality code intelligence.

m1nd is not a code search engine. It is a reasoning engine. Sourcegraph tells you “function X is defined here and called here.” m1nd tells you “if you change function X, here are the 4,189 nodes in the cascade, and the graph predicts these 3 files probably need changes too.”

Sourcegraph is a hosted SaaS product. m1nd is a local binary with zero cost per query.

m1nd vs GitHub Copilot context

Copilot uses a mix of embeddings and heuristics to select context files for the LLM. It does not maintain a persistent graph, does not learn from feedback, and does not support structural queries.

m1nd can be used alongside Copilot – through MCP, Copilot can call m1nd tools to get smarter context before generating code.


Contributing

How do I contribute?

See CONTRIBUTING.md. Fork the repo, create a branch, make your changes with tests, and open a PR.

What is the test suite?

cargo test --all

Tests cover the core graph engine, plasticity, spreading activation, hypothesis engine, all MCP tool handlers, and the JSON-RPC protocol layer.

What are the code style requirements?

  • cargo fmt before committing
  • cargo clippy --all -- -D warnings must pass
  • No unsafe without an explanatory comment
  • All new code needs tests

What areas need the most help?

  1. Language extractors: Adding tree-sitter integration or new language-specific extractors
  2. Graph algorithms: Community detection, better decay functions, embedding-based semantic scoring
  3. Benchmarks: Running m1nd on diverse codebases and reporting real-world performance numbers
  4. Documentation: Tutorials, examples, and translations

Where do I report bugs?

GitHub Issues at github.com/cosmophonix/m1nd/issues. Use the bug label.

Benchmarks

All numbers in this document are from real execution against a production Python backend: 335 files, approximately 52,000 lines of code, producing a graph of 9,767 nodes and 26,557 edges.

No synthetic benchmarks. No cherry-picked runs. These are the numbers you get when you run m1nd against a real codebase.

Core Operations

OperationTimeScale
Full ingest910ms335 files -> 9,767 nodes, 26,557 edges
Spreading activation31-77ms15-20 results from 9,767 nodes
Blast radius (depth=3)5-52msUp to 4,271 affected nodes
Stacktrace analysis3.5ms5 frames -> 4 suspects ranked
Plan validation10ms7 files -> 43,152 blast radius
Counterfactual cascade3msFull BFS on 26,557 edges
Hypothesis testing58ms25,015 paths explored
Pattern scan (all 8 patterns)38ms335 files, 50 findings per pattern
Multi-repo federation1.3s11,217 nodes, 18,203 cross-repo edges
Lock diff0.08us1,639-node subgraph comparison
Trail merge1.2ms5 hypotheses, 3 conflicts detected
Hebbian learn<1ms740 edges adjusted
Health check<1msStatistics only
Seek (semantic search)10-15ms20 results
Warmup (task priming)82-89ms50 seed nodes primed
Resonate (standing wave)37-52msHarmonic analysis
Fingerprint (twin detection)1-107msTopology comparison
Why (path explanation)5-6msShortest path between two nodes
Drift (weight changes)23msSince last session
Timeline (temporal history)~1msNode change history

Comparison Table

m1nd vs grep / ripgrep

Dimensionripgrepm1nd
Query typeText pattern (regex)Natural language intent
ReturnsLines matching patternRanked nodes with 4D scores
RelationshipsNoneFull graph traversal
LearningNoneHebbian plasticity
“What if?” queriesNot possibleCounterfactual, hypothesis, impact
Speed (simple query)~5ms31-77ms
Speed (structural query)Not possible3-58ms
Memory~10MB~50MB
Cost per queryZeroZero

ripgrep is faster for simple text matching and always will be. m1nd answers questions ripgrep cannot ask.

m1nd vs RAG

DimensionRAGm1nd
Retrieval methodEmbedding similarity (top-K)Spreading activation (4D)
StatefulnessStateless per queryPersistent graph + learning
RelationshipsNot trackedFirst-class edges
LearningNoneHebbian feedback loop
Investigation memoryNoneTrail save/resume/merge
Structural queriesNot possibleImpact, counterfactual, hypothesis
Setup costEmbedding computation910ms ingest
Cost per queryLLM tokens for embeddingZero
Typical query latency200-500ms (includes API call)31-77ms (local)

m1nd vs Static Analysis (Sourcegraph, SCIP, LSP)

DimensionStatic Analysism1nd
AccuracyLanguage-server preciseStructural heuristic
LearningNoneHebbian plasticity
Temporal intelligencegit blame onlyCo-change velocity + decay
“What if?” simulationNot possibleCounterfactual cascade
Hypothesis testingNot possibleBayesian path analysis
Investigation stateNot trackedTrail system
Multi-agentRead-only sharingShared graph + isolated perspectives
CostHosted SaaS or self-hosted infraSingle binary, zero cost
SetupMinutes to hours (indexing)910ms (ingest)

Summary: Use the Right Tool

TaskBest Tool
Find text in filesripgrep
Find semantically similar codeRAG
Go-to-definition, find referencesLSP / Sourcegraph
Understand blast radius of a changem1nd
Simulate module removalm1nd
Test structural hypothesesm1nd
Learn from agent feedbackm1nd
Persist investigation statem1nd
Multi-agent shared code intelligencem1nd

Cost Comparison: Tokens Saved

In a typical AI agent coding session, the agent calls grep/ripgrep 20-50 times and reads 10-30 files to navigate a codebase. Each file read costs tokens (the file content is sent to the LLM).

m1nd replaces many of these exploratory reads with graph queries that return ranked results rather than raw file contents. The agent reads fewer files because it reads the right files first.

Estimated Token Savings

Assumptions: 335-file Python backend, 8-hour agent workday, agent using Claude Opus.

Without m1ndWith m1nd
~40 grep calls/hour~15 grep calls/hour
~20 file reads/hour~8 file reads/hour
~150K tokens/hour (context)~60K tokens/hour (context)
~1.2M tokens/day~480K tokens/day

Estimated savings: ~720K tokens/day (60% reduction in context tokens).

These are estimates based on production usage in the a multi-agent system. Your mileage varies with codebase size, task type, and agent behavior.

The key insight: m1nd does not replace search. It focuses search. The agent still uses grep and reads files, but it starts from a much better position because m1nd told it where to look.

Memory and CPU Usage

Memory

ComponentSize
Graph (9,767 nodes, 26,557 edges)~2MB
Plasticity state~500KB
Perspective state (per active perspective)~100KB
Lock baselines (per lock)~200KB
Trail storage (per saved trail)~50KB
JSON-RPC server overhead~5MB
Typical total~50MB

Memory scales linearly with graph size. A 100K-node graph would use approximately 20MB for the graph alone, with similar overhead for the server.

CPU

m1nd is single-threaded for graph operations (no lock contention, deterministic results). Ingest uses Rayon for parallel file parsing. During query serving, CPU usage is negligible between queries and spikes briefly during activation (31-77ms of computation).

On an Apple M2, the server at idle uses <0.1% CPU. During a burst of queries, it peaks at ~5% of a single core.

Scaling Characteristics

Ingest Time vs Codebase Size

Ingest scales linearly with file count. Reference resolution is roughly O(n log n) where n is the number of cross-file references.

FilesEstimated Ingest TimeEstimated Nodes
100~270ms~3,000
335910ms (measured)9,767 (measured)
1,000~2.7s~29,000
10,000~27s~290,000
100,000~4.5min~2,900,000

Activation Time vs Graph Size

Spreading activation is bounded by the number of edges traversed, which depends on graph density and query specificity rather than total graph size. Activation in a 100K-node graph is estimated at 100-200ms.

Persistence Time vs State Size

JSON serialization scales linearly with state size. A 10K-node graph persists in under 100ms. A 100K-node graph would take approximately 1 second.

Reproducibility

To reproduce these benchmarks:

git clone https://github.com/cosmophonix/m1nd.git
cd m1nd
cargo build --release

# Start the server
./target/release/m1nd-mcp

Then send the JSON-RPC calls from the Examples document against your own codebase. Times will vary based on:

  • Hardware (CPU speed, memory bandwidth)
  • Codebase size and language
  • Graph density (codebases with many cross-references produce denser graphs)
  • Plasticity state (learned weights affect activation propagation paths)

Report your benchmarks via GitHub Issues with the benchmark label.

Changelog

All notable changes to m1nd are documented here. This project uses Semantic Versioning.

v0.1.0 – Initial Release

The first public release of m1nd: a neuro-symbolic connectome engine with Hebbian plasticity, spreading activation, and 43 MCP tools. Built in Rust.

Core Engine (m1nd-core)

  • Compressed Sparse Row (CSR) graph with forward and reverse adjacency
  • PageRank computation on ingest
  • 4-dimensional spreading activation: structural, semantic, temporal, causal
  • Hebbian plasticity: Long-Term Potentiation (LTP), Long-Term Depression (LTD), homeostatic normalization
  • XLR differential processing: noise cancellation inspired by balanced audio cables
  • Hypothesis engine: claim testing with Bayesian confidence on graph paths
  • Counterfactual engine: module removal simulation with cascade analysis
  • Structural hole detection: topology-based gap analysis
  • Resonance analysis: standing wave computation for structural hub identification
  • Fingerprint engine: activation fingerprinting for structural twin detection
  • Trail system: investigation state persistence, resume, and multi-trail merge with conflict detection
  • Lock system: subgraph pinning with sub-microsecond diff (0.08us)
  • Temporal engine: co-change history, velocity scoring, decay functions
  • Domain configurations: code, music, memory, generic presets with tuned decay half-lives

Ingest Layer (m1nd-ingest)

  • Language extractors: Python, Rust, TypeScript/JavaScript, Go, Java
  • Generic fallback extractor: heuristic-based for unsupported languages
  • JSON adapter: structured data ingestion
  • Memory adapter: text corpus ingestion
  • Reference resolver: cross-file import and call resolution
  • Incremental ingest: re-process only changed files
  • Multi-repo federation: unified graph with automatic cross-repo edge detection

MCP Server (m1nd-mcp)

  • 43 MCP tools across 7 layers:
    • Foundation (13): activate, impact, missing, why, learn, drift, health, seek, scan, timeline, diverge, warmup, federate
    • Perspective Navigation (12): start, routes, follow, back, peek, inspect, suggest, affinity, branch, compare, list, close
    • Lock System (5): create, watch, diff, rebase, release
    • Superpowers (13): hypothesize, counterfactual, predict, fingerprint, resonate, trace, validate_plan, differential, trail.save, trail.resume, trail.merge, trail.list, seek
  • JSON-RPC over stdio: compatible with MCP protocol version 2024-11-05
  • Dual transport: framed (Content-Length headers) and line-delimited JSON-RPC
  • Auto-persistence: configurable interval (default: every 50 queries) + on shutdown
  • Multi-agent support: agent ID tracking, perspective isolation, shared graph
  • Tool name normalization: underscores automatically converted to dots (e.g., m1nd_activate -> m1nd.activate)

Performance (measured on 335-file Python backend, ~52K lines)

  • Full ingest: 910ms (9,767 nodes, 26,557 edges)
  • Spreading activation: 31-77ms
  • Blast radius: 5-52ms
  • Counterfactual: 3ms
  • Hypothesis testing: 58ms (25,015 paths)
  • Lock diff: 0.08us
  • Trail merge: 1.2ms
  • Memory footprint: ~50MB typical

Known Limitations

  • Semantic scoring uses trigram matching, not neural embeddings (planned for v0.2)
  • No tree-sitter integration yet (planned for v0.2)
  • 6 languages with dedicated extractors; others use generic fallback
  • Graph is fully in-memory; very large codebases (400K+ files) need ~80MB
  • No dataflow or taint analysis (out of scope; use dedicated SAST tools)

Planned: v0.2.0

  • Tree-sitter integration for 64+ language support
  • Optional embedding-based semantic scoring
  • Graph partitioning for very large codebases
  • Community detection algorithms
  • Performance optimizations for 100K+ node graphs
  • MCP Streamable HTTP transport (in addition to stdio)

Planned: v0.3.0

  • Distributed graph (multi-machine federation)
  • Real-time file watcher integration
  • Plugin system for custom extractors and tools
  • Graph visualization export (DOT, D3.js, Mermaid)
  • Metrics and observability (Prometheus, OpenTelemetry)