## 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 local code graph engine for MCP agents. It ingests a repository once, turns it into a queryable graph, and helps agents ask for structure, impact, connected context, continuity, and likely risk instead of reconstructing the repo from raw files every time.

The current public shape of the product is not just “graph search.” It is a guided runtime with:

- graph-grounded retrieval and impact analysis
- `proof_state` on the main structural flows
- `next_suggested_tool`, `next_suggested_target`, and `next_step_hint`
- actionable continuity through `trail_resume`
- observable multi-file writes through `apply_batch`
- recovery loops that teach the next valid move when a tool is used badly

m1nd ships as an MCP server, runs locally, and works with any MCP-compatible client over stdio. The current exported schema exposes 78 MCP tools.

## The Problem

Most agent loops still waste time in the same place: navigation.

An LLM can reason about a file once it has the file. The expensive part is getting the right file, the right neighbors, and enough proof to act without reopening half the repo.

Without a structural layer, the loop usually looks like this:

1. grep for a symbol or phrase
2. open a file
3. grep for callers, callees, or related paths
4. open more files
5. repeat until the subsystem shape becomes clear

That cost shows up as:

- more file reads than necessary
- more token burn on repo reconstruction
- weaker stopping rules during triage
- more false starts before editing
- more friction resuming prior investigations

## What m1nd Changes

m1nd keeps the graph local and lets an agent ask for structure directly:

- `trace` maps stacktraces to likely suspects
- `impact` inspects blast radius before edits
- `seek` and `activate` find intent and connected structure
- `validate_plan` and `surgical_context_v2` prepare safer multi-file changes
- `trail_resume` restores investigations with next-focus and next-tool hints
- `apply_batch` exposes progress, phases, and final handoff signals

The result is less context churn and better decision quality per step.

Current benchmark truth from the recorded warm-graph corpus:

- `10518 -> 5182` aggregate token proxy
- `50.73%` aggregate reduction
- `14 -> 0` false starts
- `39` guided follow-throughs
- `12` successful recovery loops

Not every scenario is a token win. Some wins are continuity, recovery, or execution clarity. That is part of the product truth too.

## Core Runtime Ideas

### Graph-grounded retrieval

The graph is still the foundation. Activation, semantic retrieval, path search, temporal history, and blast-radius analysis all sit on top of a shared structural model rather than a stateless grep loop.

### Guided handoff

Several high-value tools now return more than raw results. They can expose:

- `proof_state`
- `next_suggested_tool`
- `next_suggested_target`
- `next_step_hint`

That turns the server from a catalog of answers into a layer that helps the agent decide what to do next.

### Continuity

`trail_resume` is no longer just bookmark restore. It can return compact resume hints, reactivated nodes, the next focus node, the next open question, and the likely next tool. This is one of the main reasons the benchmark corpus now records fewer false starts.

### Observable execution

`apply_batch` is now an observable write surface:

- `status_message`
- `proof_state`
- lifecycle phases such as `validate`, `write`, `reingest`, `verify`, and `done`
- coarse progress fields like `progress_pct`
- structured `progress_events`
- live SSE progress in serve mode

### Recovery loops

Common failures no longer have to be dead ends. Many invalid calls now return hints, examples, and a suggested next step so the agent can repair the call instead of rediscovering the workflow from scratch.

## Who This Is For

- agent builders who want a local structural layer for navigation and edit prep
- MCP client users who want better triage, continuity, and connected context
- multi-agent systems that need shared graph truth without shipping code to an API
- teams that want safer workflow around stacktrace triage, blast radius, and multi-file changes

m1nd is not a compiler, debugger, or test runner replacement. It is best when the real bottleneck is structural understanding and repo navigation.

## How To Read This Wiki

Architecture explains how the three crates fit together and how the MCP server turns graph truth into agent-facing runtime behavior.

Concepts covers the underlying graph ideas such as activation, plasticity, and structural holes.

API Reference documents the current MCP surface, including underscore-based canonical tool names, guided outputs, and transport behavior.

Tutorials walks through the main workflows from first ingest to connected edit prep.

The Benchmarks page is the current product-truth layer for token proxy, false starts, guided follow-through, and recovery loops. The Changelog tracks the release history from `v0.6.0` and `v0.6.1` onward.

# System Architecture Overview

m1nd is a local code graph engine for MCP agents. Internally it is still a graph-and-analysis system, but the current public runtime is organized around a more agent-operational contract:

- graph-grounded retrieval and ranking
- proof-aware structural flows
- next-step handoff on high-value tools
- continuity through trails and perspectives
- observable multi-file writes through `apply_batch`
- repair-friendly errors when a tool call goes wrong

The workspace currently exposes 78 MCP tools over JSON-RPC stdio, with an HTTP/UI surface in the default build.

## 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"

Crate | Role | Key Dependency
--- | --- | ---
m1nd-core | Graph engine, activation, plasticity, XLR, resonance, temporal, semantic | `parking_lot`, `smallvec`, `static_assertions`
m1nd-ingest | File walking, language-specific extraction, reference resolution, diff | `rayon`, `walkdir`, `regex`
m1nd-mcp | JSON-RPC transport, tool dispatch, session management, persistence | `tokio`, `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/>63 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 structural workflow now has two layers: graph execution and agent handoff.

### Example: `activate`

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.

### Example: guided structural flow

A more agent-native flow such as `trace -> view -> surgical_context_v2 -> validate_plan -> apply_batch` adds two extra kinds of behavior on top of the graph work:

1. Cognitive state: tools can expose `proof_state` such as `triaging`, `proving`, or `ready_to_edit`
2. Next-step guidance: tools can return `next_suggested_tool`, `next_suggested_target`, and `next_step_hint`

This is one of the biggest architectural changes in `v0.6.0`: the MCP layer is no longer just a transport for graph answers. It is a transport for graph answers plus workflow guidance.

## 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

There are two truths worth separating:

1. engine/runtime timings
2. workflow-level benchmark truth

Engine timings still matter, especially for ingest and structural queries. Workflow benchmarks matter when the goal is to reduce context churn, false starts, and repair loops.

Representative measured engine timings:

Operation | Time | Notes
--- | --- | ---
Full ingest | ~910ms | Walk + parallel extract + resolve + finalize (CSR + PageRank)
Activate query | ~31ms | 4-dimension with XLR, top-20 results
Impact analysis | ~5ms | BFS blast radius, 3-hop default
Trace analysis | ~3.5ms | Stacktrace to ranked suspects
Trail resume | ~0.2ms | Restore continuity and next-step hints
Apply batch | ~165ms | Atomic multi-file write before deeper verification

Workflow benchmark truth lives on the Benchmarks page. The current recorded warm-graph corpus shows:

- `10518 -> 5182` aggregate token proxy
- `50.73%` aggregate reduction
- `14 -> 0` false starts
- `39` guided follow-throughs
- `12` successful recovery loops

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, trail system, and shared runtime state provide isolation where needed without duplicating the underlying graph.

The important modern detail is that agent-facing state is not limited to ownership and transport. Several flows now expose cognitive runtime state directly, including:

- `proof_state`
- next-step guidance fields
- trail continuation hints
- batch progress and handoff metadata

`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 graph runtime. It owns the graph data structure, the 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`:

Type | Invariant | Use
--- | --- | ---
`FiniteF32` | Never NaN or Inf | All activation scores, edge weights, scores
`PosF32` | Strictly positive, finite | Wavelength, 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:

Type | Wraps | Purpose
--- | --- | ---
`NodeId(u32)` | Node index | Index into `NodeStorage` parallel arrays
`EdgeIdx(u32)` | Edge index | Index into CSR parallel arrays
`InternedStr(u32)` | String handle | Opaque index into `StringInterner`
`CommunityId(u32)` | Community | Louvain community membership
`Generation(u64)` | Mutation counter | Plasticity 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., “chat_” matches “chat_handler”)
3. Substring match (e.g., “handler” matches “chat_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`):

Parameter | Value | Purpose
--- | --- | ---
`DEFAULT_LEARNING_RATE` | 0.08 | Hebbian weight change rate
`DEFAULT_DECAY_RATE` | 0.005 | Inactive synapse decay per query
`LTP_THRESHOLD` | 5 | Consecutive strengthens before permanent bonus
`LTD_THRESHOLD` | 5 | Consecutive weakens before permanent penalty
`LTP_BONUS` | 0.15 | Permanent weight increase
`LTD_PENALTY` | 0.15 | Permanent weight decrease
`HOMEOSTATIC_CEILING` | 5.0 | Max sum of incoming weights per node
`WEIGHT_FLOOR` | 0.05 | Minimum edge weight (prevents extinction)
`WEIGHT_CAP` | 3.0 | Maximum 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:

Parameter | Value | Purpose
--- | --- | ---
`F_HOT` | 1.0 | Hot signal frequency
`F_COLD` | 3.7 | Cold signal frequency
`SPECTRAL_BANDWIDTH` | 0.8 | Gaussian kernel bandwidth
`IMMUNITY_HOPS` | 2 | BFS immunity radius
`SIGMOID_STEEPNESS` | 6.0 | Gating 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

Module | Purpose
--- | ---
`lib.rs` | `Ingestor` pipeline, `IngestAdapter` trait, config, stats
`walker.rs` | `DirectoryWalker`, binary detection, git enrichment
`extract/mod.rs` | `Extractor` trait, comment stripping, `CommentSyntax`
`extract/python.rs` | Python: classes, functions, decorators, imports
`extract/typescript.rs` | TypeScript/JS: classes, functions, interfaces, imports
`extract/rust_lang.rs` | Rust: structs, enums, impls, traits, functions, mods
`extract/go.rs` | Go: structs, interfaces, functions, packages
`extract/java.rs` | Java: classes, interfaces, methods, packages
`extract/generic.rs` | Fallback: file-level node with tag extraction
`resolve.rs` | `ReferenceResolver`, proximity disambiguation
`cargo_workspace.rs` | Cargo workspace/crate/dependency enrichment for Rust repos
`cross_file.rs` | Python-weighted cross-file enrichment (`imports`, `tests`, `registers`)
`diff.rs` | `GraphDiff` for incremental updates
`json_adapter.rs` | Generic JSON-to-graph adapter
`memory_adapter.rs` | Markdown/memory document adapter
`merge.rs` | Graph merge utilities
`patent_adapter.rs` | USPTO/EPO patent XML ingestion
`jats_adapter.rs` | PubMed/JATS scientific article XML ingestion
`bibtex_adapter.rs` | BibTeX bibliography file ingestion
`rfc_adapter.rs` | IETF RFC XML v3 ingestion
`crossref_adapter.rs` | CrossRef API JSON (DOI metadata) ingestion
`document_router.rs` | Auto-detect document format and route to correct adapter
`cross_domain.rs` | Cross-domain edge resolution (DOI, ORCID, keyword bridges)

## 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 + Enrich"
            REFS["ReferenceResolver<br/>ref:: → actual nodes"]
            PROX["Proximity Disambiguation<br/>same file > same dir > same project"]
            HINTS["Import Hints<br/>module path → target preference"]
            CARGO["Cargo Workspace<br/>workspace/crate/dependency graph"]
            XFILE["Cross-File Enrichment<br/>currently strongest for Python"]
        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 --> CARGO --> 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:

Extension | Extractor | Extracted Entities
--- | --- | ---
`.py`, `.pyi` | `PythonExtractor` | Classes, functions, decorators, imports, global assignments
`.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`, `.cjs` | `TypeScriptExtractor` | Classes, functions, interfaces, type aliases, imports, exports
`.rs` | `RustExtractor` | Structs, enums, traits, impls, functions, modules, macros
`.go` | `GoExtractor` | Structs, interfaces, functions, methods, packages
`.java` | `JavaExtractor` | Classes, interfaces, methods, fields, packages
everything else | `GenericExtractor` | File-level node with tag extraction from content

This stack is hybrid:

- native/manual extractors for Python, TypeScript/JavaScript, Rust, Go, and Java
- tree-sitter-backed tiers for additional languages
- generic fallback for unsupported files

### 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/chat_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/chat_handler.py` (file node)
- `file::backend/chat_handler.py::ChatHandler` (class node)
- `file::backend/chat_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:

Relation | Causal Strength | Direction
--- | --- | ---
`contains` | 0.8 | Bidirectional
`implements` | 0.7 | Bidirectional
`imports` | 0.6 | Forward
`calls` | 0.5 | Forward
`references` | 0.3 | Forward
other | 0.4 | Forward

`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 And Enrichment

### 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:

Proximity | Score | Condition
--- | --- | ---
Same file | 100 | Source and target share the same `file::` prefix
Same directory | 50 | Source and target share the same directory
Same project | 10 | Default (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.

### Cargo Workspace Enrichment

For Rust repos, `cargo_workspace.rs` adds a workspace-aware layer before finalization:

- workspace nodes
- crate nodes
- crate -> file `contains` edges
- crate -> crate `depends_on` edges for internal workspace dependencies
- external dependency nodes for non-workspace dependencies

This means Rust repos are no longer represented only as file graphs.

### Cross-File Enrichment

After reference resolution and Cargo enrichment, `cross_file.rs` adds a narrower set of shipped cross-file edges.

Today this pass is strongest for Python and focuses on:

- `imports`
- `tests`
- `registers`

It should not be described as a language-uniform cross-file engine yet.

## 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:

Scenario | Strategy
--- | ---
Single file changed | Incremental diff (fast, ~10ms)
Many files changed (>20%) | Full re-ingest (cleaner CSR, correct PageRank)
New codebase | Full ingest
Plasticity state important | Full 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:

Classification | Pattern | Example
--- | --- | ---
Task | Contains “TODO”, “FIXME”, “pending”, “implement” | “- TODO: add tests”
Decision | Contains “decision:”, “decided:”, “chose” | “- Decision: use CSR format”
State | Contains “status:”, “state:”, “current:” | “- Status: in progress”
Event | Contains date pattern (`YYYY-MM-DD`) | “- 2026-03-12: deployed”
Note | Default | “- 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:

Adapter | Domain | Input | MCP `adapter=`
--- | --- | --- | ---
`Ingestor` | `"code"` | Source code directories | `code`
`MemoryIngestAdapter` | `"memory"` | Markdown/text documents | `memory`
`JsonIngestAdapter` | `"generic"` | Arbitrary JSON with `nodes[]` and `edges[]` | `json`
`PatentIngestAdapter` | `"patent"` | USPTO/EPO patent XML | `patent`
`JatsArticleAdapter` | `"article"` | PubMed NLM / JATS Z39.96 XML | `article`
`BibTexAdapter` | `"bibtex"` | BibTeX bibliography files | `bibtex`, `bib`
`RfcAdapter` | `"rfc"` | IETF RFC XML v3 | `rfc`
`CrossRefAdapter` | `"crossref"` | CrossRef API JSON (DOI metadata) | `crossref`, `doi`
`L1ghtIngestAdapter` | `"light"` | L1GHT protocol Markdown | `light`

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}`).

## Document Router (Auto-Detection)

`DocumentRouter` inspects file content and extension to auto-detect the correct adapter:

    #![allow(unused)]
    fn main() {
    let (format, adapter) = DocumentRouter::detect(path);
    let (format, adapter) = DocumentRouter::detect_directory(root); // samples ≤20 files
    }

Detection Method | Format | Heuristic
--- | --- | ---
Extension `.bib` / `.bibtex` | BibTeX | Extension only
Extension `.md` + `Protocol: L1GHT` | L1GHT | Content check
Extension `.xml` / `.nxml` | Patent, JATS, or RFC | Root element inspection
Extension `.json` | CrossRef | Checks for `DOI` + `publisher` + `type` keys
Fallback | Code | Default pipeline

Used via MCP: `m1nd.ingest(adapter="auto")` or `adapter="document"`.

For directory detection, the router samples up to 20 files and returns the dominant format.

## Cross-Domain Resolution

`CrossDomainResolver` merges multiple adapter outputs and discovers cross-domain connections automatically.

### Bridge Strategies

Bridge | Weight | Source | Description
--- | --- | --- | ---
`same_as` | 1.0 | DOI/PMID | Same identifier in different domains → identity edge
`cross_cites` | 0.95 | Citation edges | Citation target exists as a full node in another domain
`same_orcid` | 0.95 | ORCID tags | Same researcher ORCID across different domains
`same_author` | 0.7 | Author name | Same author name across different namespaces
`shared_keyword` | 0.6 | Keyword tags | Shared `keyword:`, `article:keyword:`, or `subject:` tags
`citation_chain` | 0.5 | Citation adjacency | Transitive A→B→C bridging with decayed weight

### Safety Guards

- Keyword cap: Keywords shared by >20 nodes are ignored to prevent hub explosion.
- Cross-domain only: All bridges require nodes from ≥2 different namespaces. Same-domain matches are skipped.
- Self-loop prevention: Citation chains A→B→A do not generate self-loop edges.
- Deduplication: Nodes with identical external IDs are deduplicated (first wins).

### Resolution Statistics

    #![allow(unused)]
    fn main() {
    pub struct ResolutionStats {
        pub graphs_merged: usize,
        pub total_nodes: u32,
        pub total_edges: usize,
        pub cross_edges_created: usize,
        pub identity_matches: usize,
        pub author_bridges: usize,
        pub keyword_bridges: usize,
        pub orcid_bridges: usize,
        pub citation_chains: usize,
    }
    }

## 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 78 MCP tools over JSON-RPC stdio, manages the shared graph lifecycle, handles multi-agent sessions, and turns graph results into a more agent-operational runtime with proof-state, next-step guidance, recovery-oriented errors, and observable batch execution.

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

## Module Map

Module | Purpose
--- | ---
`main.rs` | Binary entry point, config loading, tokio runtime, SIGINT handling
`server.rs` | `McpServer`, JSON-RPC transport (framed + line), tool schema registry
`session.rs` | `SessionState`, engine lifecycle, auto-persist, perspective/lock management
`tools.rs` | Tool handler implementations for the exported MCP surface
`engine_ops.rs` | Read-only engine wrappers for perspective synthesis
`protocol.rs` | JSON-RPC request/response types
`perspective/` | Perspective branching, lock state, watcher events
`layer_handlers.rs` | Layer-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":"jimi"}},"id":1}

Line mode (raw JSON):

    {"jsonrpc":"2.0","method":"tools/call","params":{"name":"m1nd.activate","arguments":{"query":"chat","agent_id":"jimi"}},"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": "jimi",
                "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 63 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 77 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 graph",
        "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):

Tool | Purpose | Key Parameters
--- | --- | ---
`m1nd.activate` | Spreading activation query | `query`, `top_k`, `dimensions`, `xlr`
`m1nd.impact` | Blast radius analysis | `node_id`, `direction` (forward/reverse/both)
`m1nd.missing` | Structural hole detection | `query`, `min_sibling_activation`
`m1nd.why` | Path explanation between nodes | `source`, `target`, `max_hops`
`m1nd.warmup` | Task-based priming | `task_description`, `boost_strength`
`m1nd.counterfactual` | Node removal simulation | `node_ids`, `include_cascade`
`m1nd.predict` | Co-change prediction | `changed_node`, `top_k`, `include_velocity`
`m1nd.fingerprint` | Equivalence detection | `target_node`
`m1nd.drift` | Weight changes since baseline | `since`
`m1nd.learn` | Hebbian feedback | `feedback` (correct/wrong)
`m1nd.resonate` | Standing wave analysis | `query`, `frequencies`, `num_harmonics`
`m1nd.seek` | Seed-level node lookup | `query`
`m1nd.scan` | Full graph summary | (none)

Graph Mutation Tools:

Tool | Purpose
--- | ---
`m1nd.ingest` | Ingest codebase into graph
`m1nd.health` | Server diagnostics
`m1nd.timeline` | Temporal event timeline

Perspective Tools (12):

Tool | Purpose
--- | ---
`m1nd.perspective_start` | Open a named perspective branch
`m1nd.perspective_close` | Close a perspective
`m1nd.perspective_list` | List open perspectives for an agent
`m1nd.perspective_inspect` | View perspective state and cached results
`m1nd.perspective_compare` | Diff two perspectives
`m1nd.perspective_branch` | Fork a perspective
`m1nd.perspective_suggest` | Generate suggestions from perspective context
`m1nd.perspective_back` | Undo last perspective operation
`m1nd.perspective_peek` | Read source file content from within perspective
`m1nd.perspective_follow` | Follow links from perspective results
`m1nd.perspective_routes` | View cached activation routes
`m1nd.perspective_affinity` | Cross-perspective affinity analysis

Lock Tools (5):

Tool | Purpose
--- | ---
`m1nd.lock_create` | Create a baseline snapshot for change tracking
`m1nd.lock_diff` | Diff current state against lock baseline
`m1nd.lock_rebase` | Update lock baseline to current state
`m1nd.lock_release` | Release a lock
`m1nd.lock_watch` | Watch for changes against lock baseline

Trail Tools (4):

Tool | Purpose
--- | ---
`m1nd.trail_save` | Save current exploration trail
`m1nd.trail_list` | List saved trails
`m1nd.trail_resume` | Resume a saved trail
`m1nd.trail_merge` | Merge trails

Topology Tools:

Tool | Purpose
--- | ---
`m1nd.federate` | Cross-graph federation
`m1nd.diverge` | Divergence analysis between graph regions
`m1nd.differential` | Differential activation (compare two queries)
`m1nd.hypothesize` | Generate hypotheses from graph structure
`m1nd.validate_plan` | Validate 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:

Counter | Bumped By | Purpose
--- | --- | ---
`graph_generation` | Ingest, rebuild_engines | Detects stale engine indexes
`plasticity_generation` | Learn | Detects stale plasticity state
`cache_generation` | max(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:

Strategy | Relevance score | Example
--- | --- | ---
Exact label match | 1.0 | “auth” matches node “auth”
Prefix match | 0.9 | “auth” matches “auth_handler”
Substring match | 0.8 | “auth” matches “pre_auth_check”
Tag match | 0.85 | “auth” matches node tagged “authentication”
Fuzzy trigram | 0.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:

Node | D1 | D2 | D3 | D4 | Dims | Bonus | Final
--- | --- | --- | --- | --- | --- | --- | ---
auth.py | 0.90 | 0.88 | 0.70 | 0.65 | 4 | 1.5x | 0.88
session.py | 0.85 | 0.40 | 0.50 | 0.45 | 4 | 1.5x | 0.64
user_model.py | 0.50 | 0.30 | 0.10 | 0.20 | 4 | 1.5x | 0.40
middleware.py | 0.47 | 0.15 | 0.60 | 0.30 | 4 | 1.5x | 0.39
database.py | 0.50 | 0.05 | 0.20 | 0.10 | 3 | 1.3x | 0.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

Parameter | Default | Purpose
--- | --- | ---
`DEFAULT_LEARNING_RATE` | 0.08 | Hebbian delta_w scaling
`DEFAULT_DECAY_RATE` | 0.005 | Per-query inactive edge decay
`LTP_THRESHOLD` | 5 | Consecutive strengthens for long-term bonus
`LTD_THRESHOLD` | 5 | Consecutive weakens for long-term penalty
`LTP_BONUS` | 0.15 | One-time weight bonus at LTP threshold
`LTD_PENALTY` | 0.15 | One-time weight penalty at LTD threshold
`HOMEOSTATIC_CEILING` | 5.0 | Max total incoming weight per node
`WEIGHT_FLOOR` | 0.05 | Minimum edge weight (never decays below)
`WEIGHT_CAP` | 3.0 | Maximum edge weight (never strengthens above)
`DEFAULT_MEMORY_CAPACITY` | 1000 | Ring buffer size for query memory
`CAS_RETRY_LIMIT` | 64 | Atomic 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”

Rank | Node | Activation | Relevant?
--- | --- | --- | ---
1 | config.py | 0.82 | No – imports everywhere
2 | payment_handler.py | 0.78 | Yes
3 | database.py | 0.75 | No – generic persistence
4 | logger.py | 0.72 | No – called from everything
5 | billing.py | 0.70 | Yes
6 | utils.py | 0.68 | No – shared utilities
7 | invoice.py | 0.65 | Yes
8 | middleware.py | 0.63 | No – request pipeline
9 | refund.py | 0.60 | Yes
10 | routes.py | 0.58 | No – 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.

Rank | Node | Hot | Cold | Differential | Relevant?
--- | --- | --- | --- | --- | ---
1 | payment_handler.py | 0.78 | 0.05 | 0.73 | Yes
2 | billing.py | 0.70 | 0.08 | 0.62 | Yes
3 | invoice.py | 0.65 | 0.06 | 0.59 | Yes
4 | refund.py | 0.60 | 0.04 | 0.56 | Yes
5 | payment_models.py | 0.55 | 0.03 | 0.52 | Yes
6 | stripe_adapter.py | 0.50 | 0.02 | 0.48 | Yes
7 | config.py | 0.82 | 0.79 | 0.03 | No (cancelled)
8 | database.py | 0.75 | 0.71 | 0.04 | No (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

Parameter | Value | Purpose
--- | --- | ---
`F_HOT` | 1.0 | Hot channel frequency
`F_COLD` | 3.7 | Cold channel frequency
`SPECTRAL_BANDWIDTH` | 0.8 | Gaussian kernel width for overlap
`IMMUNITY_HOPS` | 2 | BFS depth for seed immunity
`SIGMOID_STEEPNESS` | 6.0 | Sharpness of activation gate
`SPECTRAL_BUCKETS` | 20 | Resolution of frequency overlap
`DENSITY_FLOOR` | 0.3 | Minimum density modulation
`DENSITY_CAP` | 2.0 | Maximum density modulation
`INHIBITORY_COLD_ATTENUATION` | 0.5 | Cold signal reduction at inhibitory edges
Default anti-seeds | 3 | Number of cold-channel origins
Default pulse budget | 50,000 | Total 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.

### RAG / embedding search

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 type | What it means | Action
--- | --- | ---
Missing test file | A module has no tests while its siblings do | Write tests or mark as intentionally untested
Missing error handling | A module does not use the error patterns its siblings use | Add error handling or document why it is unnecessary
Missing validation | An endpoint lacks input validation that peer endpoints have | Add validation – likely a security gap
Missing import | A module does not import a shared utility that all siblings import | Check if the module implements the functionality differently
Missing documentation | A module has no doc-edges while siblings do | Write documentation or accept the gap
Intentional isolation | A module is deliberately decoupled from a cluster | No 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 currently exposes 78 MCP tools over JSON-RPC 2.0 via stdio. Every tool requires `agent_id` as a parameter. The exported MCP schema uses underscore-based canonical names such as `trail_resume`, `perspective_start`, and `apply_batch`.

Several tools now do more than return raw results. On the main structural flows you should expect some combination of:

- `proof_state`
- `next_suggested_tool`
- `next_suggested_target`
- `next_step_hint`

That matters for how you integrate m1nd into an agent loop: treat many responses as workflow guidance, not just data blobs.

## Tool Index

### Core Activation

Tool | Description
--- | ---
`activate` | Spreading activation query across the graph
`warmup` | Task-based warmup and priming
`resonate` | Resonance analysis: harmonics, sympathetic pairs, and resonant frequencies

### Analysis

Tool | Description
--- | ---
`impact` | Impact radius / blast analysis for a node
`predict` | Co-change prediction for a modified node
`counterfactual` | What-if node removal simulation
`fingerprint` | Activation fingerprint and equivalence detection
`hypothesize` | Graph-based hypothesis testing against structural claims
`differential` | Focused structural diff between two graph snapshots
`diverge` | Structural drift between a baseline and current graph state

### Memory, Trails, and Learning

Tool | Description
--- | ---
`learn` | Explicit feedback-based edge adjustment
`drift` | Weight and structural drift analysis
`why` | Path explanation between two nodes
`trail_save` | Persist current investigation state
`trail_resume` | Restore a saved investigation with next-step guidance
`trail_list` | List saved investigation trails
`trail_merge` | Combine two or more investigation trails

### Exploration

Tool | Description
--- | ---
`seek` | Intent-aware semantic code search
`scan` | Pattern-aware structural code analysis
`missing` | Detect structural holes and missing connections
`trace` | Map runtime errors to structural root causes
`timeline` | Git-based temporal history for a node
`federate` | Multi-repository federated graph ingestion
`federate_auto` | Discover repo candidates from external path evidence and optionally federate them

### Perspectives

Tool | Description
--- | ---
`perspective_start` | Enter a perspective: navigable route surface from a query
`perspective_routes` | Browse the current route set with pagination
`perspective_inspect` | Expand a route with metrics, provenance, and affinity
`perspective_peek` | Extract a code/doc slice from a route target
`perspective_follow` | Follow a route: move focus to target, synthesize new routes
`perspective_suggest` | Get the next best move suggestion
`perspective_affinity` | Discover probable connections a route target might have
`perspective_branch` | Fork navigation state into a new branch
`perspective_back` | Navigate back to previous focus
`perspective_compare` | Compare two perspectives on shared/unique nodes
`perspective_list` | List all perspectives for an agent
`perspective_close` | Close a perspective and release associated locks

### Lifecycle, Search, and Surgical

Tool | Description
--- | ---
`ingest` | Ingest or re-ingest a codebase, descriptor, or memory corpus
`health` | Server health and statistics
`search` | Literal, regex, or semantic-graph-aware content search
`glob` | Graph-aware file globbing
`view` | Fast line-numbered file inspection
`validate_plan` | Validate a modification plan against the code graph
`surgical_context_v2` | Pull connected edit context with proof-oriented options
`apply_batch` | Atomic multi-file write with progress, verification, and handoff

The grouped reference pages below still organize the surface by area, but the current best operational map is in `help`, the README tool table, and the live `tools/list` response.

## Common Parameters

Every tool requires the `agent_id` parameter:

Parameter | Type | Required | Description
--- | --- | --- | ---
`agent_id` | `string` | Yes | Identifier 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. `"jimi"`, `"hacker-auth"`, `"forge-build"`).

## 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": "activate",
        "arguments": {
          "agent_id": "jimi",
          "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. Many current errors are recovery-oriented and may include fields such as `hint`, `workflow_hint`, `example`, or `suggested_next_step`.

    {
      "jsonrpc": "2.0",
      "id": 1,
      "result": {
        "content": [
          {
            "type": "text",
            "text": "{ \"error\": \"...\", \"hint\": \"...\", \"suggested_next_step\": \"...\" }"
          }
        ],
        "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

Code | Meaning
--- | ---
`-32700` | Parse error – invalid JSON
`-32601` | Method not found – unknown JSON-RPC method
`-32603` | Internal 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

The exported MCP schema uses underscore-based canonical names, but the server still accepts legacy dotted and underscored aliases when possible:

- `activate` is canonical
- `m1nd.activate` is accepted as a compatibility alias
- `m1nd_activate` is also accepted where normalization applies

If you are generating tool calls from an MCP client, prefer the canonical schema names returned by `tools/list`.

## 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.6.1" },
        "capabilities": { "tools": {} }
      }
    }

Then list available tools:

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

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

# Activation Tools

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

## `activate`

Spreading activation query across the graph. The primary structural 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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`query` | `string` | Yes | – | Search query for spreading activation. Matched against node labels, tags, and provenance to find seed nodes.
`agent_id` | `string` | Yes | – | Calling agent identifier.
`top_k` | `integer` | No | `20` | Number of top activated nodes to return.
`dimensions` | `string[]` | No | `["structural", "semantic", "temporal", "causal"]` | Activation dimensions to include. Each dimension contributes independently to the final activation score. Values: `"structural"`, `"semantic"`, `"temporal"`, `"causal"`.
`xlr` | `boolean` | No | `true` | Enable XLR noise cancellation. Filters low-confidence activations to reduce false positives.
`include_ghost_edges` | `boolean` | No | `true` | Include ghost edge detection. Ghost edges are probable but unconfirmed connections inferred from activation patterns.
`include_structural_holes` | `boolean` | No | `false` | Include structural hole detection. Identifies nodes that should be connected but are not.

### Example Request

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

### Example Response

    {
      "query": "session pool management",
      "seeds": [
        { "node_id": "file::session_pool.py", "label": "session_pool.py", "relevance": 0.95 }
      ],
      "activated": [
        {
          "node_id": "file::session_pool.py",
          "label": "session_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/session_pool.py",
            "line_start": 1,
            "line_end": 245,
            "canonical": true
          }
        },
        {
          "node_id": "file::session_pool.py::class::SessionPool",
          "label": "SessionPool",
          "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/session_pool.py",
            "line_start": 15,
            "line_end": 180,
            "canonical": true
          }
        }
      ],
      "ghost_edges": [
        {
          "source": "session_pool.py",
          "target": "healing_manager.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.

### Related Tools

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

## `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`task_description` | `string` | Yes | – | Description of the task to warm up for. Natural language.
`agent_id` | `string` | Yes | – | Calling agent identifier.
`boost_strength` | `number` | No | `0.15` | Priming 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": "warmup",
        "arguments": {
          "agent_id": "jimi",
          "task_description": "Refactor the WhatsApp message routing to support group chats",
          "boost_strength": 0.2
        }
      }
    }

### Example Response

    {
      "task": "Refactor the WhatsApp message routing to support group chats",
      "primed_nodes": 23,
      "top_primed": [
        { "node_id": "file::whatsapp_routes.py", "label": "whatsapp_routes.py", "boost": 0.2 },
        { "node_id": "file::whatsapp_manager.py", "label": "whatsapp_manager.py", "boost": 0.18 },
        { "node_id": "file::whatsapp_models.py", "label": "whatsapp_models.py", "boost": 0.15 },
        { "node_id": "file::chat_handler.py", "label": "chat_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.

### Related Tools

- `activate` – raw activation query without the priming boost
- `trail_resume` – restores a full investigation context including activation boosts

## `resonate`

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

### Parameters

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`query` | `string` | No | – | Search query to find seed nodes for resonance analysis. Provide either `query` or `node_id` (or neither for global resonance).
`node_id` | `string` | No | – | Specific node identifier to use as seed. Alternative to `query`.
`agent_id` | `string` | Yes | – | Calling agent identifier.
`top_k` | `integer` | No | `20` | Number of top resonance results to return.

### Example Request

    {
      "jsonrpc": "2.0",
      "id": 3,
      "method": "tools/call",
      "params": {
        "name": "resonate",
        "arguments": {
          "agent_id": "jimi",
          "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.

### Related Tools

- `m1nd.activate` – simpler spreading activation without harmonic analysis
- `m1nd.fingerprint` – finds structurally equivalent nodes
- `m1nd.missing` – finds gaps in the resonance structure

# 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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`node_id` | `string` | Yes | – | Target node identifier. Can be an external_id (`file::backend/config.py`) or a node label.
`agent_id` | `string` | Yes | – | Calling agent identifier.
`direction` | `string` | No | `"forward"` | Propagation direction. Values: `"forward"` (what does this affect?), `"reverse"` (what affects this?), `"both"`.
`include_causal_chains` | `boolean` | No | `true` | Include 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": "jimi",
          "node_id": "file::chat_handler.py",
          "direction": "forward",
          "include_causal_chains": true
        }
      }
    }

### Example Response

    {
      "source": "file::chat_handler.py",
      "source_label": "chat_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::stream_parser.py", "label": "stream_parser.py", "type": "file", "signal_strength": 0.65, "hop_distance": 2 }
      ],
      "total_energy": 4271.0,
      "max_hops_reached": 3,
      "causal_chains": [
        {
          "path": ["chat_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

### Related Tools

- `m1nd.predict` – predicts which files will co-change (more actionable than blast radius)
- `m1nd.counterfactual` – simulates deletion rather than change
- `m1nd.validate_plan` – validates an entire modification plan

## `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`changed_node` | `string` | Yes | – | Node identifier that was changed.
`agent_id` | `string` | Yes | – | Calling agent identifier.
`top_k` | `integer` | No | `10` | Number of top predictions to return.
`include_velocity` | `boolean` | No | `true` | Include 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": "jimi",
          "changed_node": "file::session_pool.py",
          "top_k": 5
        }
      }
    }

### Example Response

    {
      "changed_node": "file::session_pool.py",
      "predictions": [
        { "node_id": "file::worker_pool.py", "label": "worker_pool.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 session_pool" },
        { "node_id": "file::tests/test_session_pool.py", "label": "test_session_pool.py", "confidence": 0.71, "velocity": 0.80, "reason": "test file" },
        { "node_id": "file::spawner.py", "label": "spawner.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

### Related Tools

- `m1nd.impact` – blast radius (broader, less actionable)
- `m1nd.timeline` – detailed co-change history
- `m1nd.validate_plan` – validates an entire plan against co-change predictions

## `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`node_ids` | `string[]` | Yes | – | Node identifiers to simulate removal of.
`agent_id` | `string` | Yes | – | Calling agent identifier.
`include_cascade` | `boolean` | No | `true` | Include 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": "jimi",
          "node_ids": ["file::spawner.py"],
          "include_cascade": true
        }
      }
    }

### Example Response

    {
      "removed_nodes": ["file::spawner.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

### Related Tools

- `m1nd.impact` – change impact (less extreme than removal)
- `m1nd.hypothesize` – test a structural claim about dependencies

## `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`target_node` | `string` | No | – | Target node to find equivalents for. If omitted, performs global fingerprinting.
`agent_id` | `string` | Yes | – | Calling agent identifier.
`similarity_threshold` | `number` | No | `0.85` | Cosine similarity threshold for equivalence. Range: 0.0 to 1.0. Lower values find more (but weaker) matches.
`probe_queries` | `string[]` | No | – | Optional 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": "jimi",
          "target_node": "file::session_pool.py",
          "similarity_threshold": 0.7
        }
      }
    }

### Example Response

    {
      "target": "file::session_pool.py",
      "fingerprint": [0.92, 0.45, 0.78, 0.33, 0.67],
      "equivalents": [
        { "node_id": "file::worker_pool.py", "label": "worker_pool.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

### Related Tools

- `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`claim` | `string` | Yes | – | Natural language claim about the codebase. Examples: `"chat_handler never validates session tokens"`, `"all external calls go through smart_router"`, `"critic is independent of whatsapp"`.
`agent_id` | `string` | Yes | – | Calling agent identifier.
`max_hops` | `integer` | No | `5` | Maximum BFS hops for evidence search.
`include_ghost_edges` | `boolean` | No | `true` | Include ghost edges as weak evidence. Ghost edges count as lower-weight supporting evidence.
`include_partial_flow` | `boolean` | No | `true` | Include partial flow when full path not found. Shows how far the search reached.
`path_budget` | `integer` | No | `1000` | Budget 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": "jimi",
          "claim": "worker_pool depends on whatsapp_manager at runtime"
        }
      }
    }

### Example Response

    {
      "claim": "worker_pool depends on whatsapp_manager at runtime",
      "claim_type": "depends_on",
      "subject_nodes": ["file::worker_pool.py"],
      "object_nodes": ["file::whatsapp_manager.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_pool.py", "file::process_manager.py", "file::whatsapp_manager.py"],
          "relations": ["calls", "imports"],
          "path_weight": 0.68
        }
      ],
      "contradicting_evidence": [],
      "paths_explored": 25015,
      "elapsed_ms": 58.0
    }

### Verdict Values

Verdict | Confidence Range | Meaning
--- | --- | ---
`"likely_true"` | > 0.8 | Strong structural evidence supports the claim
`"likely_false"` | < 0.2 | Strong structural evidence contradicts the claim
`"inconclusive"` | 0.2 – 0.8 | Evidence 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”)

### Related Tools

- `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier.
`snapshot_a` | `string` | Yes | – | Path to snapshot A, or `"current"` for the in-memory graph.
`snapshot_b` | `string` | Yes | – | Path to snapshot B, or `"current"` for the in-memory graph.
`question` | `string` | No | – | Focus filter question. Narrows the diff output. Examples: `"what new coupling was introduced?"`, `"what modules became isolated?"`.
`focus_nodes` | `string[]` | 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": "jimi",
          "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": "whatsapp_routes.py", "target": "chat_handler.py", "relation": "calls", "weight": 0.45 }
      ],
      "removed_edges": [],
      "weight_changes": [
        { "source": "session_pool.py", "target": "worker_pool.py", "relation": "imports", "old_weight": 0.5, "new_weight": 0.78, "delta": 0.28 }
      ],
      "new_nodes": ["file::whatsapp_chat_bridge.py"],
      "removed_nodes": [],
      "coupling_deltas": [
        { "community_a": "whatsapp", "community_b": "chat", "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

### Related Tools

- `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier.
`baseline` | `string` | Yes | – | Baseline reference. Values: ISO date (`"2026-03-01"`), git ref (SHA or tag), or `"last_session"` to use the saved GraphFingerprint.
`scope` | `string` | No | – | File path glob to limit scope. Example: `"backend/stormender*"`. `None` = all nodes.
`include_coupling_changes` | `boolean` | No | `true` | Include coupling matrix delta between communities.
`include_anomalies` | `boolean` | No | `true` | Detect 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": "jimi",
          "baseline": "2026-03-01",
          "scope": "backend/stormender*"
        }
      }
    }

### Example Response

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

### Anomaly Types

Type | Description
--- | ---
`test_deficit` | New or modified file with no corresponding test file
`velocity_spike` | Unusually high churn rate
`new_coupling` | Previously independent modules are now coupled
`isolation` | Module that was connected became isolated

### When to Use

- Session start – `m1nd.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

### Related Tools

- `m1nd.drift` – weight-level drift (lighter, faster)
- `m1nd.differential` – lower-level snapshot diff
- `m1nd.timeline` – single-node temporal history

# 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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`query` | `string` | Yes | – | The original query this feedback relates to. Must match the query used in the activation.
`agent_id` | `string` | Yes | – | Calling agent identifier.
`feedback` | `string` | Yes | – | Feedback type. Values: `"correct"` (strengthen edges), `"wrong"` (weaken edges), `"partial"` (strengthen confirmed nodes only).
`node_ids` | `string[]` | Yes | – | Node identifiers to apply feedback to. For `"correct"`, these are the relevant results. For `"wrong"`, these are the irrelevant ones.
`strength` | `number` | No | `0.2` | Feedback 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": "jimi",
          "query": "session pool management",
          "feedback": "correct",
          "node_ids": ["file::session_pool.py", "file::worker_pool.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.

### Related Tools

- `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier.
`since` | `string` | No | `"last_session"` | Baseline reference point. Values: `"last_session"` (saved state from previous session), or a timestamp.
`include_weight_drift` | `boolean` | No | `true` | Include 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": "jimi",
          "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": "session_pool.py -> worker_pool.py", "old": 0.45, "new": 0.72, "delta": 0.27 },
        { "edge": "chat_handler.py -> stream_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

### Related Tools

- `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`source` | `string` | Yes | – | Source node identifier.
`target` | `string` | Yes | – | Target node identifier.
`agent_id` | `string` | Yes | – | Calling agent identifier.
`max_hops` | `integer` | No | `6` | Maximum 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": "jimi",
          "source": "file::worker_pool.py",
          "target": "file::whatsapp_manager.py",
          "max_hops": 4
        }
      }
    }

### Example Response

    {
      "source": "file::worker_pool.py",
      "target": "file::whatsapp_manager.py",
      "paths": [
        {
          "nodes": ["worker_pool.py", "process_manager.py", "whatsapp_manager.py"],
          "relations": ["calls::cancel", "imports"],
          "cumulative_strength": 0.68,
          "hops": 2
        },
        {
          "nodes": ["worker_pool.py", "spawner.py", "chat_handler.py", "whatsapp_manager.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

### Related Tools

- `m1nd.hypothesize` – tests a claim about the relationship (more powerful)
- `m1nd.impact` – finds all affected nodes (broader scope)

## `trail_save`

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

### Parameters

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier.
`label` | `string` | Yes | – | Human-readable label for this investigation.
`hypotheses` | `object[]` | No | `[]` | Hypotheses formed during investigation. Each object has: `statement` (string, required), `confidence` (number, default 0.5), `supporting_nodes` (string[]), `contradicting_nodes` (string[]).
`conclusions` | `object[]` | No | `[]` | Conclusions reached. Each object has: `statement` (string, required), `confidence` (number, default 0.5), `from_hypotheses` (string[]), `supporting_nodes` (string[]).
`open_questions` | `string[]` | No | `[]` | Open questions remaining for future investigation.
`tags` | `string[]` | No | `[]` | Tags for organization and search.
`summary` | `string` | No | – | Optional summary. Auto-generated if omitted.
`visited_nodes` | `object[]` | 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_boosts` | `object` | No | `{}` | 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": "trail_save",
        "arguments": {
          "agent_id": "jimi",
          "label": "auth-leak-investigation",
          "hypotheses": [
            {
              "statement": "Auth tokens leak through session pool",
              "confidence": 0.7,
              "supporting_nodes": ["file::session_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::session_pool.py": 0.8,
            "file::auth_discovery.py": 0.6
          }
        }
      }
    }

### Example Response

    {
      "trail_id": "trail_jimi_001_a1b2c3",
      "label": "auth-leak-investigation",
      "agent_id": "jimi",
      "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

### Related Tools

- `trail_resume` – restore a saved trail
- `trail_list` – find saved trails
- `trail_merge` – combine trails from parallel investigations

## `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier.
`trail_id` | `string` | Yes | – | Trail ID to resume (from `trail_save` or `trail_list`).
`force` | `boolean` | No | `false` | Resume 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": "trail_resume",
        "arguments": {
          "agent_id": "jimi",
          "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": "jimi",
        "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

### Related Tools

- `trail_save` – save a trail to resume later
- `warmup` – simpler priming without full trail restoration

## `trail_list`

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

### Parameters

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier.
`filter_agent_id` | `string` | No | – | Filter to a specific agent’s trails. `None` = all agents.
`filter_status` | `string` | No | – | Filter by status: `"active"`, `"saved"`, `"archived"`, `"stale"`, `"merged"`.
`filter_tags` | `string[]` | No | `[]` | Filter by tags (any match).

### Example Request

    {
      "jsonrpc": "2.0",
      "id": 6,
      "method": "tools/call",
      "params": {
        "name": "trail_list",
        "arguments": {
          "agent_id": "jimi",
          "filter_status": "saved",
          "filter_tags": ["security"]
        }
      }
    }

### Example Response

    {
      "trails": [
        {
          "trail_id": "trail_jimi_001_a1b2c3",
          "agent_id": "jimi",
          "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

### Related Tools

- `trail_resume` – resume a trail from this list
- `trail_merge` – combine related 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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier.
`trail_ids` | `string[]` | Yes | – | Two or more trail IDs to merge.
`label` | `string` | No | – | Label 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": "jimi",
          "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::session_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

### Related Tools

- `trail_save` – save individual trails
- `m1nd.trail.list` – find trails to merge

# 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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`query` | `string` | Yes | – | Natural language description of what the agent is looking for. Example: `"code that validates user credentials"`.
`agent_id` | `string` | Yes | – | Calling agent identifier.
`top_k` | `integer` | No | `20` | Maximum results to return.
`scope` | `string` | No | – | File path prefix to limit search scope. Example: `"backend/"`. `None` = entire graph.
`node_types` | `string[]` | No | `[]` | Filter by node type: `"function"`, `"class"`, `"struct"`, `"module"`, `"file"`. Empty = all types.
`min_score` | `number` | No | `0.1` | Minimum combined score threshold. Range: 0.0 to 1.0.
`graph_rerank` | `boolean` | No | `true` | Whether 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": "jimi",
          "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

### Related Tools

- `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`pattern` | `string` | Yes | – | Pattern ID or custom pattern string. Built-in patterns: `"error_handling"`, `"resource_cleanup"`, `"api_surface"`, `"state_mutation"`, `"concurrency"`, `"auth_boundary"`, `"test_coverage"`, `"dependency_injection"`.
`agent_id` | `string` | Yes | – | Calling agent identifier.
`scope` | `string` | No | – | File path prefix to limit scan scope.
`severity_min` | `number` | No | `0.3` | Minimum severity threshold. Range: 0.0 to 1.0.
`graph_validate` | `boolean` | No | `true` | Validate findings against graph edges (cross-file analysis). Disable for raw pattern matching only.
`limit` | `integer` | No | `50` | Maximum findings to return.

### Example Request

    {
      "jsonrpc": "2.0",
      "id": 2,
      "method": "tools/call",
      "params": {
        "name": "m1nd.scan",
        "arguments": {
          "agent_id": "jimi",
          "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::spawner.py::fn::spawn_agent",
          "label": "spawn_agent",
          "file_path": "backend/spawner.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

Status | Meaning
--- | ---
`"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

### Related Tools

- `m1nd.hypothesize` – test a specific structural claim
- `m1nd.trace` – analyze a specific error (not patterns)

## `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`query` | `string` | Yes | – | Search query to find structural holes around.
`agent_id` | `string` | Yes | – | Calling agent identifier.
`min_sibling_activation` | `number` | No | `0.3` | Minimum 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": "jimi",
          "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

### Related Tools

- `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`error_text` | `string` | Yes | – | Full error output (stacktrace + error message).
`agent_id` | `string` | Yes | – | Calling agent identifier.
`language` | `string` | No | – | Language hint: `"python"`, `"rust"`, `"typescript"`, `"javascript"`, `"go"`. Auto-detected if omitted.
`window_hours` | `number` | No | `24.0` | Temporal window (hours) for co-change suspect scan.
`top_k` | `integer` | No | `10` | Max suspects to return.

### Example Request

    {
      "jsonrpc": "2.0",
      "id": 4,
      "method": "tools/call",
      "params": {
        "name": "m1nd.trace",
        "arguments": {
          "agent_id": "jimi",
          "error_text": "Traceback (most recent call last):\n  File \"backend/chat_handler.py\", line 234, in handle_message\n  File \"backend/session_pool.py\", line 89, in acquire\n  File \"backend/worker_pool.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_pool.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_pool.py",
          "line_start": 150,
          "line_end": 175,
          "related_callers": ["session_pool.py::acquire"]
        },
        {
          "node_id": "file::session_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/session_pool.py",
          "line_start": 80,
          "line_end": 110,
          "related_callers": ["chat_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_pool.py::submit", "session_pool.py::acquire", "chat_handler.py::handle_message"],
      "fix_scope": {
        "files_to_inspect": ["backend/worker_pool.py", "backend/session_pool.py", "backend/config.py"],
        "estimated_blast_radius": 23,
        "risk_level": "medium"
      },
      "unmapped_frames": [],
      "elapsed_ms": 3.5
    }

### Suspiciousness Scoring

Signal | Weight | Description
--- | --- | ---
`trace_depth_score` | High | 1.0 = deepest frame (most specific); decays linearly
`recency_score` | Medium | Exponential decay from last modification time
`centrality_score` | Medium | Normalized 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 scoping – `fix_scope` tells you which files to inspect and the risk level

### Related Tools

- `m1nd.hypothesize` – test a hypothesis about the root cause
- `m1nd.impact` – assess the blast radius of a fix
- `m1nd.validate_plan` – validate your fix plan before implementing

## `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`node` | `string` | Yes | – | Node external_id. Example: `"file::backend/chat_handler.py"`.
`agent_id` | `string` | Yes | – | Calling agent identifier.
`depth` | `string` | No | `"30d"` | Time depth. Values: `"7d"`, `"30d"`, `"90d"`, `"all"`.
`include_co_changes` | `boolean` | No | `true` | Include co-changed files with coupling scores.
`include_churn` | `boolean` | No | `true` | Include lines added/deleted churn data.
`top_k` | `integer` | No | `10` | Max co-change partners to return.

### Example Request

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

### Example Response

    {
      "node": "file::backend/chat_handler.py",
      "depth": "30d",
      "changes": [
        {
          "date": "2026-03-10",
          "commit": "a1b2c3d",
          "author": "cosmophonix",
          "delta": "+45/-12",
          "co_changed": ["stream_parser.py", "chat_routes.py"]
        },
        {
          "date": "2026-03-05",
          "commit": "e4f5g6h",
          "author": "cosmophonix",
          "delta": "+120/-30",
          "co_changed": ["session_pool.py", "worker_pool.py"]
        }
      ],
      "co_changed_with": [
        { "file": "stream_parser.py", "times": 8, "coupling_degree": 0.72 },
        { "file": "chat_routes.py", "times": 6, "coupling_degree": 0.55 },
        { "file": "session_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

Value | Meaning
--- | ---
`"accelerating"` | Change frequency is increasing
`"decelerating"` | Change frequency is decreasing
`"stable"` | Consistent change rate

### Pattern Values

Value | Meaning
--- | ---
`"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

### Related Tools

- `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier.
`repos` | `object[]` | Yes | – | List of repositories to federate. Each object has: `name` (string, required – namespace prefix), `path` (string, required – absolute path), `adapter` (string, default `"code"`).
`detect_cross_repo_edges` | `boolean` | No | `true` | Auto-detect cross-repo edges: config, API, import, type, deployment, MCP contract.
`incremental` | `boolean` | No | `false` | Only re-ingest repos that changed since last federation.

### Cross-Repo Edge Types

Edge Type | Description
--- | ---
`shared_config` | Two repos reference the same configuration key
`api_contract` | One repo’s API client matches another’s API server
`package_dep` | Direct package dependency
`shared_type` | Same type/interface definition used across repos
`deployment_dep` | Deployment configuration dependency
`mcp_contract` | MCP tool consumer/provider relationship

### Example Request

    {
      "jsonrpc": "2.0",
      "id": 6,
      "method": "tools/call",
      "params": {
        "name": "m1nd.federate",
        "arguments": {
          "agent_id": "jimi",
          "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

### Related Tools

- `m1nd.ingest` – single-repo ingestion
- `m1nd.impact` – blast radius analysis (works across federated repos)
- `m1nd.why` – path explanation (traverses cross-repo edges)

## `m1nd.federate_auto`

Turn explicit external path evidence, local manifest/workspace hints, import/package-name evidence, shared API-route signals, or contract artifacts such as `.proto`, MCP tools, and OpenAPI into repo candidates, namespace suggestions, and an optional one-shot `federate` call.

### Parameters

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier.
`scope` | `string` | No | – | File path prefix to limit discovery sources.
`current_repo_name` | `string` | No | auto | Optional namespace override for the current workspace.
`max_repos` | `integer` | No | `8` | Maximum discovered external repos to include.
`detect_cross_repo_edges` | `boolean` | No | `true` | Whether `execute=true` should auto-detect cross-repo edges.
`execute` | `boolean` | No | `false` | If true, immediately run `m1nd.federate` with the current repo plus discovered candidates.

### Example Request

    {
      "jsonrpc": "2.0",
      "id": 7,
      "method": "tools/call",
      "params": {
        "name": "m1nd.federate_auto",
        "arguments": {
          "agent_id": "jimi",
          "scope": "docs",
          "execute": false
        }
      }
    }

### Example Response

    {
      "current_repo": { "namespace": "m1nd", "repo_root": "/repo/m1nd" },
      "discovered_repos": [
        {
          "namespace": "runtime",
          "repo_root": "/repo/runtime",
          "marker": ".git",
          "confidence": "high",
          "evidence_types": ["markdown_link"],
          "source_nodes": ["file::docs/architecture.md"],
          "source_files": ["/repo/m1nd/docs/architecture.md"],
          "sampled_paths": ["/repo/runtime/docs/ARCH.md"],
          "suggested_action": "run federate_auto with execute=true or pass suggested_repos into federate"
        }
      ],
      "suggested_repos": [
        { "name": "runtime", "path": "/repo/runtime", "adapter": "code" }
      ],
      "skipped_paths": [],
      "executed": false,
      "federate_result": null,
      "elapsed_ms": 42.0
    }

### When to Use

- Cross-repo audits – when `audit` or `external_references` already surfaced sibling repo paths
- Manifest-driven workspaces – when `Cargo.toml`, `package.json`, `pnpm-workspace.yaml`, `pyproject.toml`, or `go.work` already point at sibling repos
- Import-driven discovery – when code already imports a sibling package/crate name but the current repo has no explicit path hint
- Contract-driven discovery – when nearby repos expose `.proto` or MCP tool contracts that the current workspace already references by name
- OpenAPI-driven discovery – when the current workspace references `operationId`s or routes defined in a nearby OpenAPI/Swagger spec
- Schema-driven discovery – when the current workspace references message/component names defined in `.proto` or OpenAPI schemas
- Planning/doc hubs – when docs point to runtime repos and you want a namespace plan without manual copy-paste
- Worktree-heavy setups – when the current workspace path is a worktree but you still want repo-shaped namespace suggestions

### Related Tools

- `m1nd.external_references` – raw external path evidence
- `m1nd.federate` – explicit multi-repo federation
- `m1nd.audit` – broader audit that can surface the evidence before auto-federation

# 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.).

## `perspective_start`

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

### Parameters

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier.
`query` | `string` | Yes | – | Seed query for route synthesis.
`anchor_node` | `string` | No | – | Anchor to a specific node. If provided, activates anchored mode where all routes maintain relevance to this node.
`lens` | `object` | No | – | Starting lens configuration. Controls dimension weights, node type filters, etc.

### Example Request

    {
      "jsonrpc": "2.0",
      "id": 1,
      "method": "tools/call",
      "params": {
        "name": "perspective_start",
        "arguments": {
          "agent_id": "jimi",
          "query": "session management and connection pooling",
          "anchor_node": "file::session_pool.py"
        }
      }
    }

### Example Response

    {
      "perspective_id": "persp_jimi_001",
      "mode": "anchored",
      "anchor_node": "file::session_pool.py",
      "focus_node": "file::session_pool.py",
      "routes": [
        {
          "route_id": "R_a1b2c3",
          "index": 1,
          "target_node": "file::worker_pool.py",
          "target_label": "worker_pool.py",
          "family": "structural_neighbor",
          "score": 0.89,
          "path_preview": ["session_pool.py", "worker_pool.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": ["session_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

### Related Tools

- `m1nd.activate` – one-shot query without navigation state
- `m1nd.perspective.routes` – paginate through routes
- `m1nd.perspective.follow` – follow a route to navigate

## `m1nd.perspective.routes`

Browse the current route set with pagination.

### Parameters

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier.
`perspective_id` | `string` | Yes | – | Perspective to browse.
`page` | `integer` | No | `1` | Page number (1-based).
`page_size` | `integer` | No | `6` | Routes per page. Clamped to `[1, 10]`.
`route_set_version` | `integer` | No | – | Version from previous response for staleness check.

### Example Request

    {
      "jsonrpc": "2.0",
      "id": 2,
      "method": "tools/call",
      "params": {
        "name": "m1nd.perspective.routes",
        "arguments": {
          "agent_id": "jimi",
          "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::session_pool.py",
      "focus": "file::session_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": ["session_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

### Related Tools

- `m1nd.perspective.inspect` – expand a specific route for detail
- `m1nd.perspective.follow` – follow a route to navigate

## `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier.
`perspective_id` | `string` | Yes | – | Perspective containing the route.
`route_id` | `string` | No | – | Stable content-addressed route ID.
`route_index` | `integer` | No | – | 1-based page-local position.
`route_set_version` | `integer` | Yes | – | Route set version for staleness check.

### Example Request

    {
      "jsonrpc": "2.0",
      "id": 3,
      "method": "tools/call",
      "params": {
        "name": "perspective_inspect",
        "arguments": {
          "agent_id": "jimi",
          "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_pool.py",
      "target_label": "worker_pool.py",
      "target_type": "file",
      "path_preview": ["session_pool.py", "spawner.py", "worker_pool.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_pool.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

### Related Tools

- `perspective_peek` – extract actual code content
- `perspective_follow` – navigate to the route target
- `perspective_affinity` – deeper affinity analysis

## `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier.
`perspective_id` | `string` | Yes | – | Perspective containing the route.
`route_id` | `string` | No | – | Stable content-addressed route ID.
`route_index` | `integer` | No | – | 1-based page-local position.
`route_set_version` | `integer` | Yes | – | Route set version for staleness check.

### Example Request

    {
      "jsonrpc": "2.0",
      "id": 4,
      "method": "tools/call",
      "params": {
        "name": "perspective_peek",
        "arguments": {
          "agent_id": "jimi",
          "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_pool.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_pool.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

### Related Tools

- `m1nd.perspective.inspect` – structural information about the route (no code content)
- `m1nd.perspective.follow` – navigate to the route target

## `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier.
`perspective_id` | `string` | Yes | – | Perspective to navigate within.
`route_id` | `string` | No | – | Stable content-addressed route ID.
`route_index` | `integer` | No | – | 1-based page-local position.
`route_set_version` | `integer` | Yes | – | Route set version for staleness check.

### Example Request

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

### Example Response

    {
      "perspective_id": "persp_jimi_001",
      "previous_focus": "file::session_pool.py",
      "new_focus": "file::worker_pool.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_pool.py", "process_manager.py"],
          "reason": "Direct caller of worker pool submit"
        },
        {
          "route_id": "R_m4n5o6",
          "index": 2,
          "target_node": "file::spawner.py",
          "target_label": "spawner.py",
          "family": "structural_neighbor",
          "score": 0.71,
          "path_preview": ["worker_pool.py", "spawner.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

### Related Tools

- `m1nd.perspective.back` – undo a follow
- `m1nd.perspective.branch` – fork before following to explore alternatives

## `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier.
`perspective_id` | `string` | Yes | – | Perspective to get a suggestion for.
`route_set_version` | `integer` | Yes | – | Route set version for staleness check.

### Example Request

    {
      "jsonrpc": "2.0",
      "id": 6,
      "method": "tools/call",
      "params": {
        "name": "m1nd.perspective.suggest",
        "arguments": {
          "agent_id": "jimi",
          "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

### Related Tools

- `m1nd.perspective.follow` – follow the suggested route
- `m1nd.perspective.inspect` – inspect the suggested route first

## `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier.
`perspective_id` | `string` | Yes | – | Perspective containing the route.
`route_id` | `string` | No | – | Stable content-addressed route ID.
`route_index` | `integer` | No | – | 1-based page-local position.
`route_set_version` | `integer` | Yes | – | Route set version for staleness check.

### Example Request

    {
      "jsonrpc": "2.0",
      "id": 7,
      "method": "tools/call",
      "params": {
        "name": "m1nd.perspective.affinity",
        "arguments": {
          "agent_id": "jimi",
          "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::healing_manager.py", "label": "healing_manager.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

### Related Tools

- `m1nd.missing` – finds structural holes (broader scope)
- `m1nd.perspective.inspect` – affinity candidates are also included in inspect output

## `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier.
`perspective_id` | `string` | Yes | – | Perspective to branch from.
`branch_name` | `string` | No | – | Optional 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": "jimi",
          "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_pool.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

### Related Tools

- `m1nd.perspective.compare` – compare the branch with the original
- `m1nd.perspective.follow` – navigate within the branch

## `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier.
`perspective_id` | `string` | Yes | – | Perspective to navigate back in.

### Example Request

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

### Example Response

    {
      "perspective_id": "persp_jimi_001",
      "restored_focus": "file::session_pool.py",
      "restored_mode": "anchored",
      "routes": [
        {
          "route_id": "R_a1b2c3",
          "index": 1,
          "target_node": "file::worker_pool.py",
          "target_label": "worker_pool.py",
          "family": "structural_neighbor",
          "score": 0.89,
          "path_preview": ["session_pool.py", "worker_pool.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

### Related Tools

- `m1nd.perspective.follow` – the forward navigation that `back` undoes
- `m1nd.perspective.branch` – alternative: branch instead of back+follow

## `m1nd.perspective.compare`

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

### Parameters

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier.
`perspective_id_a` | `string` | Yes | – | First perspective ID.
`perspective_id_b` | `string` | Yes | – | Second perspective ID.
`dimensions` | `string[]` | 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": "jimi",
          "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_pool.py", "process_manager.py", "session_pool.py"],
      "unique_to_a": ["spawner.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

### Related Tools

- `m1nd.perspective.branch` – create branches to compare
- `m1nd.trail.merge` – merge investigation trails (deeper than compare)

## `m1nd.perspective.list`

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

### Parameters

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier.

### Example Request

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

### Example Response

    {
      "agent_id": "jimi",
      "perspectives": [
        {
          "perspective_id": "persp_jimi_001",
          "mode": "anchored",
          "focus_node": "file::worker_pool.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

### Related Tools

- `m1nd.perspective.close` – close perspectives you no longer need

## `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier.
`perspective_id` | `string` | Yes | – | Perspective to close.

### Example Request

    {
      "jsonrpc": "2.0",
      "id": 12,
      "method": "tools/call",
      "params": {
        "name": "m1nd.perspective.close",
        "arguments": {
          "agent_id": "jimi",
          "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

### Related Tools

- `m1nd.perspective.list` – find perspectives to close

## Full Workflow Example

A complete perspective exploration session, from start to close:

    1. START a perspective from a query
       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 session_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: [session_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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`path` | `string` | Yes | – | Filesystem path to the source root or memory corpus.
`agent_id` | `string` | Yes | – | Calling agent identifier.
`incremental` | `boolean` | No | `false` | Incremental ingest (code adapter only). Only re-processes files that changed since the last ingest.
`adapter` | `string` | No | `"code"` | Adapter to use for parsing. Values: `"code"` (source code – Python, Rust, TypeScript, etc.), `"json"` (graph snapshot JSON), `"memory"` (markdown memory corpus).
`mode` | `string` | No | `"replace"` | How to handle the existing graph. Values: `"replace"` (clear and rebuild), `"merge"` (add new nodes/edges into existing graph).
`namespace` | `string` | No | – | Optional 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": "jimi",
          "path": "/Users/cosmophonix/clawd/roomanizer-os/backend",
          "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

Adapter | Input | Node Types | Edge Types
--- | --- | --- | ---
`code` | Source code directory | file, class, function, struct, module | imports, calls, registers, configures, tests, inherits
`json` | Graph snapshot JSON | (preserved from snapshot) | (preserved from snapshot)
`memory` | Markdown files | document, concept, entity | references, relates_to

### Mode Behavior

Mode | Behavior
--- | ---
`replace` | Clears the existing graph, ingests fresh, finalizes (PageRank + CSR). All perspectives and locks are invalidated.
`merge` | Adds 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

### Related Tools

- `m1nd.health` – check graph status before deciding to ingest
- `m1nd.drift` – see what changed since last session
- `m1nd.federate` – multi-repo ingestion

## `m1nd.health`

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

### Parameters

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier.

### Example Request

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

### 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": "jimi", "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

### Related Tools

- `m1nd.ingest` – load data if the graph is empty
- `m1nd.drift` – check what changed since last session

## `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier.
`actions` | `object[]` | Yes | – | Ordered 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_impact` | `boolean` | No | `true` | Analyze test coverage for modified files.
`include_risk_score` | `boolean` | No | `true` | Compute composite risk score.

### Example Request

    {
      "jsonrpc": "2.0",
      "id": 3,
      "method": "tools/call",
      "params": {
        "name": "m1nd.validate_plan",
        "arguments": {
          "agent_id": "jimi",
          "actions": [
            { "action_type": "modify", "file_path": "backend/session_pool.py", "description": "Add connection timeout" },
            { "action_type": "modify", "file_path": "backend/worker_pool.py", "description": "Update pool acquire to use timeout" },
            { "action_type": "test", "file_path": "backend/tests/test_session_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 session_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_pool.py -- calls worker_pool.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_pool.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_pool.py", "reason": "Modified file has no test action in plan" }
      ],
      "blast_radius_total": 47,
      "elapsed_ms": 35.0
    }

### Risk Levels

Level | Score Range | Meaning
--- | --- | ---
`"low"` | < 0.3 | Small, well-tested change
`"medium"` | 0.3 – 0.6 | Moderate scope, some gaps
`"high"` | 0.6 – 0.8 | Large scope or missing tests
`"critical"` | >= 0.8 | Very 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

### Related Tools

- `m1nd.impact` – blast radius for a single node
- `m1nd.predict` – co-change prediction for a single node
- `m1nd.trace` – validate a fix plan for a specific error

## `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier. Lock is owned by this agent.
`scope` | `string` | Yes | – | Scope type. Values: `"node"` (single nodes only), `"subgraph"` (BFS expansion from roots), `"query_neighborhood"` (nodes matching a query), `"path"` (ordered node list).
`root_nodes` | `string[]` | Yes | – | Root nodes for the lock scope. Non-empty. Matched by external_id (exact), then label, then substring.
`radius` | `integer` | No | – | BFS radius for `subgraph` scope. Range: 1 to 4. Required for subgraph scope.
`query` | `string` | No | – | Query string for `query_neighborhood` scope.
`path_nodes` | `string[]` | No | – | Ordered node list for `path` scope.

### Scope Types

Scope | Description
--- | ---
`node` | Lock only the specified root nodes
`subgraph` | BFS expansion from root nodes up to `radius` hops
`query_neighborhood` | Nodes matching a query activation
`path` | An 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": "jimi",
          "scope": "subgraph",
          "root_nodes": ["file::chat_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

### Related Tools

- `m1nd.lock.watch` – set automatic change detection
- `m1nd.lock.diff` – compute what changed since baseline
- `m1nd.lock.release` – release the lock when done

## `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier. Must own the lock.
`lock_id` | `string` | Yes | – | Lock to set the watcher on.
`strategy` | `string` | Yes | – | Watcher 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": "jimi",
          "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

### Related Tools

- `m1nd.lock.diff` – manually trigger a diff (always available regardless of strategy)
- `m1nd.lock.create` – create the lock first

## `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier. Must own the lock.
`lock_id` | `string` | Yes | – | Lock to diff.

### Example Request

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

### Example Response (changes detected)

    {
      "diff": {
        "lock_id": "lock_jimi_001",
        "no_changes": false,
        "new_nodes": ["file::chat_handler.py::fn::new_method"],
        "removed_nodes": [],
        "new_edges": ["chat_handler.py|new_method->stream_parser.py|calls"],
        "removed_edges": [],
        "boundary_edges_added": [],
        "boundary_edges_removed": [],
        "weight_changes": [
          { "edge_key": "chat_handler.py|stream_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

### Related Tools

- `m1nd.lock.rebase` – re-capture baseline after acknowledging changes
- `m1nd.lock.watch` – set automatic change detection

## `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

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier. Must own the lock.
`lock_id` | `string` | Yes | – | Lock to rebase.

### Example Request

    {
      "jsonrpc": "2.0",
      "id": 7,
      "method": "tools/call",
      "params": {
        "name": "m1nd.lock.rebase",
        "arguments": {
          "agent_id": "jimi",
          "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

### Related Tools

- `m1nd.lock.diff` – the diff that triggers a rebase
- `m1nd.lock.create` – creating a new lock is an alternative to rebasing

## `m1nd.lock.release`

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

### Parameters

Parameter | Type | Required | Default | Description
--- | --- | --- | --- | ---
`agent_id` | `string` | Yes | – | Calling agent identifier. Must own the lock.
`lock_id` | `string` | Yes | – | Lock to release.

### Example Request

    {
      "jsonrpc": "2.0",
      "id": 8,
      "method": "tools/call",
      "params": {
        "name": "m1nd.lock.release",
        "arguments": {
          "agent_id": "jimi",
          "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`

### Related Tools

- `m1nd.lock.create` – create a new lock
- `m1nd.perspective.close` – cascade-releases associated locks

# Quick Start

Five minutes from zero to your first useful graph query.

## Prerequisites

- Rust toolchain: current stable recommended if you build from source
- A codebase: Python, Rust, TypeScript/JavaScript, Go, and Java have the strongest handling; additional languages are available through tree-sitter and fallback extraction
- An MCP client: Claude Code, Codex, Cursor, Windsurf, Zed, Cline, Continue, or any client that can connect to an MCP server over stdio

## Install

Choose one path:

### 1. Build from source

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

The binary will be at:

    ./target/release/m1nd-mcp

If you want it on your PATH:

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

### 2. Install from crates.io

    cargo install m1nd-mcp

### 3. Download a release binary

The current release workflow publishes these artifact names:

- `m1nd-mcp-linux-x86_64`
- `m1nd-mcp-macos-x86_64`
- `m1nd-mcp-macos-aarch64`

If you use release binaries, download them from the latest GitHub release page instead of relying on hardcoded tarball names.

## Verify the binary

    m1nd-mcp --help

If the binary is healthy, you should see the CLI help for the MCP server.

## Configure your MCP client

m1nd is an MCP server over stdio. Your client starts the binary and calls tools through MCP.

### Claude Code

Add this to your Claude Code MCP config.

The exact file path depends on how you run Claude Code, but the current repo README uses this shape:

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

### Cursor

In MCP Servers, use the same binary + env setup:

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

### Antigravity

Antigravity supports a workspace-local `mcp_config.json` shape.

Use:

    {
      "mcpServers": {
        "m1nd": {
          "command": "python3",
          "args": ["/path/to/m1nd-antigravity-proxy.py"],
          "cwd": "/path/to/workspace",
          "env": {
            "M1ND_OPENCLAW_SOCKET": "/tmp/m1nd-openclaw.sock",
            "M1ND_GRAPH_SOURCE": "/tmp/m1nd-graph.json",
            "M1ND_PLASTICITY_STATE": "/tmp/m1nd-plasticity.json"
          }
        }
      }
    }

This keeps the host-facing contract stdio-compatible while routing into the hot native daemon.

### Other MCP clients

The pattern is the same:

- point the client at `m1nd-mcp`
- optionally set graph/plasticity persistence env vars
- then call tools through MCP

## Persistence variables

Variable | Purpose | If omitted
--- | --- | ---
`M1ND_GRAPH_SOURCE` | Persist the graph snapshot | graph is memory-only
`M1ND_PLASTICITY_STATE` | Persist learned edge weights | learning is memory-only

Recommendation: set both if you want continuity across restarts.

## Important note on tool names

The live MCP registry exposes bare tool names like:

- `ingest`
- `activate`
- `search`
- `impact`

Some clients or docs may show `m1nd.ingest`-style names. Treat that as presentation sugar. The canonical live registry names are the bare tool names shown by `tools/list`.

## First run

Once your client is configured, the first thing to do is ingest a project.

### Step 1: ingest a repo

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

Example response shape:

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

What happened:

- files were parsed
- structural nodes and edges were created
- references were resolved
- the graph was finalized for querying

### Step 2: check server health

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

Current response shape in the repo:

    {
      "status": "ok",
      "node_count": 9767,
      "edge_count": 26557,
      "queries_processed": 1,
      "uptime_seconds": 12.4,
      "memory_usage_bytes": 0,
      "plasticity_state": "0 edges tracked",
      "last_persist_time": null,
      "active_sessions": [
        {
          "agent_id": "dev",
          "query_count": 1
        }
      ]
    }

If you see `node_count: 0`, your ingest path was wrong or the ingest did not run.

### Step 3: ask the graph something real

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

Example response shape:

    {
      "query": "authentication",
      "activated": [
        {
          "node_id": "file::auth.py",
          "score": 0.89
        },
        {
          "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
        }
      ],
      "elapsed_ms": 31,
      "proof_state": "triaging"
    }

What happened:

- seed nodes were selected from the query
- spreading activation propagated across graph structure
- the result was ranked by graph signal, not just text matching

## What to do next

After the first `activate`, these are the most useful next steps:

- `search` for exact text or regex
- `seek` for intent-based retrieval
- `impact` before touching a central file
- `surgical_context_v2` before multi-file edits
- `validate_plan` when you already know the files you want to touch

## Troubleshooting

### “No graph snapshot found, starting fresh”

Normal on first run. The graph is empty until you call `ingest`.

### Ingest returns 0 files

Check the path and confirm it points at a real project root.

### My client cannot see the tools

Check:

- the `m1nd-mcp` path
- execute permissions on the binary
- the MCP client logs

### Learned state disappears between restarts

Set:

- `M1ND_GRAPH_SOURCE`
- `M1ND_PLASTICITY_STATE`

### I want persistent low-latency operation for a large codebase

See:

- Deployment

That doc covers the persistent server + stdio proxy pattern for near-zero startup overhead.

# 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::session_pool.py",
          "score": 0.89,
          "dimension_scores": {
            "structural": 0.92,
            "semantic": 0.95,
            "temporal": 0.78,
            "causal": 0.71
          }
        },
        {"node_id": "file::session_pool.py::class::SessionPool", "score": 0.84},
        {"node_id": "file::worker_pool.py", "score": 0.61},
        {"node_id": "file::session_pool.py::fn::acquire", "score": 0.58},
        {"node_id": "file::process_manager.py", "score": 0.45}
      ],
      "ghost_edges": [
        {
          "from": "file::session_pool.py",
          "to": "file::healing_manager.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, `session_pool.py` and `healing_manager.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 (`session_pool.py` and the `SessionPool` 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::session_pool.py",
            "file::session_pool.py::class::SessionPool"
          ],
          "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_pool.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_pool.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_pool.py` from this query region. The graph now knows: for session pool queries, `worker_pool.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::session_pool.py", "score": 0.93},
        {"node_id": "file::session_pool.py::class::SessionPool", "score": 0.88},
        {"node_id": "file::session_pool.py::fn::acquire", "score": 0.65},
        {"node_id": "file::process_manager.py", "score": 0.47},
        {"node_id": "file::healing_manager.py", "score": 0.39}
      ]
    }

Compare with Step 2:

Node | Before | After | Change
--- | --- | --- | ---
`session_pool.py` | 0.89 | 0.93 | +0.04 (strengthened)
`SessionPool` class | 0.84 | 0.88 | +0.04 (strengthened)
`worker_pool.py` | 0.61 | dropped | Pushed below top-5 (weakened)
`healing_manager.py` | ghost only | 0.39 | Promoted from ghost to main results

The graph learned. `worker_pool.py` fell out of the top results. `healing_manager.py`, previously only a ghost edge, got promoted because the strengthened paths through `session_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::spawner.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 `spawner.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). `spawner.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_pool depends on whatsapp_manager at runtime",
          "agent_id": "dev"
        }
      }
    }

Response:

    {
      "verdict": "likely_true",
      "confidence": 0.72,
      "paths_explored": 25015,
      "evidence": [
        {
          "path": [
            "file::worker_pool.py",
            "file::process_manager.py::fn::cancel",
            "file::whatsapp_manager.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:

Tool | When to Use
--- | ---
`missing` | Before designing new features – find what your codebase lacks
`counterfactual` | Before deleting or rewriting – simulate the blast radius
`hypothesize` | When debugging – test assumptions about hidden dependencies
`impact` | Before modifying a file – understand the blast radius
`predict` | After modifying a file – which other files probably need changes too
`trace` | When 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 ROOMANIZER OS uses m1nd with 19 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: "jimi"          -- orchestrator
    agent_id: "hacker-auth"   -- security hardening agent
    agent_id: "forge-api"     -- API building agent
    agent_id: "analyst-perf"  -- 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: "session_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::session_pool.py"]
        }
      }
    }
    // -> 740 edges strengthened

    // Agent B: immediately benefits from stronger session_pool.py edges
    {
      "method": "tools/call",
      "params": {
        "name": "m1nd.activate",
        "arguments": {
          "query": "session management",
          "agent_id": "agent-b",
          "top_k": 5
        }
      }
    }
    // -> session_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": "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": "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": "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": "perspective_list",
        "arguments": {"agent_id": "agent-b"}
      }
    }
    // -> [{"perspective_id": "persp-d4e5f6", "focus": "file::worker_pool.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": "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_pool.py", "file::spawner.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": "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: ROOMANIZER OS

ROOMANIZER OS is a multi-agent orchestration system that uses m1nd as its shared code intelligence layer. Here is how it works in production:

### Architecture

    JIMI (orchestrator)
      |
      +-- hacker-auth (security agent)     --+
      +-- forge-api (API builder)           --+-- All share one m1nd instance
      +-- analyst-perf (performance)        --+
      +-- sentinel-files (file watcher)     --+
      +-- critic-quality (code reviewer)    --+
      +-- ... 14 more agents               --+

One m1nd instance serves 19 agents. The graph covers a 335-file Python backend (~52K lines), a React frontend, and MCP server infrastructure.

### Orchestrator Boot Sequence

When JIMI (the orchestrator) starts a session:

    // Step 1: Check m1nd health
    {"method":"tools/call","params":{"name":"m1nd.health","arguments":{"agent_id":"jimi"}}}

    // Step 2: Check what changed since last session
    {"method":"tools/call","params":{"name":"m1nd.drift","arguments":{"agent_id":"jimi","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":"jimi","incremental":true
    }}}

### Task Delegation with Graph Context

When JIMI 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":"jimi"
    }}}

    // Warm up the graph for the security task
    {"method":"tools/call","params":{"name":"m1nd.warmup","arguments":{
      "task_description":"harden authentication token validation","agent_id":"jimi"
    }}}

    // The security agent then uses the primed graph
    {"method":"tools/call","params":{"name":"m1nd.activate","arguments":{
      "query":"token validation vulnerabilities","agent_id":"hacker-auth"
    }}}

### 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":"hacker-auth",
      "feedback":"correct",
      "node_ids":["file::auth.py","file::middleware.py","file::session_pool.py"]
    }}}

    // Performance agent found different useful results
    {"method":"tools/call","params":{"name":"m1nd.learn","arguments":{
      "query":"connection pool bottleneck",
      "agent_id":"analyst-perf",
      "feedback":"correct",
      "node_ids":["file::worker_pool.py","file::process_manager.py"]
    }}}

Over a session with 19 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":"trail_save","arguments":{
      "agent_id":"hacker-auth",
      "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":"jimi",
      "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 local code graph engine for MCP agents. It turns a repo into a queryable graph and currently exposes 78 MCP tools for structure, impact, connected context, continuity, audit, and edit preparation. Built in Rust, it runs locally and works with any MCP-compatible client.

The current differentiator is not just that the graph learns. The runtime also exposes guidance surfaces such as `proof_state`, `next_suggested_tool`, `next_suggested_target`, and `next_step_hint`, plus observable progress for long-running writes like `apply_batch`.

### 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. If you already know the repo list, use `federate`. If the current workspace only contains explicit path evidence to sibling repos, use `federate_auto` first and let m1nd suggest the namespace plan for you.

Direct federation:

    {"method":"tools/call","params":{"name":"m1nd.federate","arguments":{
      "agent_id":"dev",
      "repos":[
        {"name":"backend","path":"/project/backend"},
        {"name":"frontend","path":"/project/frontend"}
      ]
    }}}

Auto-discovery first:

    {"method":"tools/call","params":{"name":"m1nd.federate_auto","arguments":{
      "agent_id":"dev",
      "scope":"docs",
      "execute":false
    }}}

### 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):

Operation | Time | Scale
--- | --- | ---
Full ingest | 910ms | 335 files, 9,767 nodes, 26,557 edges
Spreading activation | 31-77ms | 15 results from 9,767 nodes
Blast radius (depth=3) | 5-52ms | Up to 4,271 affected nodes
Stacktrace analysis | 3.5ms | 5 frames, 4 suspects ranked
Plan validation | 10ms | 7 files, 43,152 blast radius
Counterfactual cascade | 3ms | Full BFS on 26,557 edges
Hypothesis testing | 58ms | 25,015 paths explored
Pattern scan (all 8) | 38ms | 335 files, 50 findings per pattern
Multi-repo federation | 1.3s | 11,217 nodes, 18,203 cross-repo edges
Lock diff | 0.08us | 1,639-node subgraph comparison
Trail merge | 1.2ms | 5 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

For the raw research artifacts and patch backlog that produced the current public numbers, see:

- `docs/BENCHMARK_RESEARCH_2026-03-24.md`
- `docs/BENCHMARK_PATCH_PLAN_2026-03-24.md`
- `docs/BENCHMARK_HARNESS_SPEC_2026-03-24.md`

This page is the short product-truth layer for the current benchmark corpus.

## What We Measure Now

The current benchmark system is not only about token proxy. It also tracks whether m1nd improves workflow behavior:

- token proxy / context churn
- `false_starts`
- guided follow-through
- recovery loops
- proof-state progression
- progress observability on long-running writes

That matters because some of m1nd’s strongest wins are continuity, repair, and execution clarity rather than raw compression in every single scenario.

## Current Warm-Graph Corpus

The current recorded aggregate warm-graph corpus shows:

Metric | Manual | `m1nd_warm` | Result
--- | --- | --- | ---
Aggregate token proxy | `10518` | `5182` | `50.73%` reduction
False starts | `14` | `0` | m1nd eliminates the recorded false starts
Guided follow-throughs | `0` | `31` | guided next-step behavior is being followed in real runs
Successful recovery loops | `0` | `12` | repair loops are closing instead of restarting from scratch

These are the public numbers reflected in the README and landing. They are the benchmark truth to mirror across docs.

## Representative Engine Timings

The underlying engine remains fast on the measured production backend (~335 files, ~52K lines, 9,767 nodes, 26,557 edges):

Operation | Time | Notes
--- | --- | ---
Full ingest | ~910ms | Walk + extract + resolve + finalize
Activate query | ~31ms | Four-dimensional ranking
Impact analysis | ~5ms | Blast-radius path
Trace analysis | ~3.5ms | Stacktrace to suspects
Trail resume | ~0.2ms | Continuity restore + hints
Apply batch | ~165ms | Atomic multi-file write before deeper verification

These timings are useful, but they are no longer the whole story. Current m1nd is also measured on guided behavior and recovery quality.

## Where m1nd Wins

m1nd wins most clearly when the task is structural, stateful, or risky:

- stacktrace triage with `trace`
- blast-radius analysis with `impact`
- continuity restoration with `trail_resume`
- edit preparation with `surgical_context_v2` and `validate_plan`
- long-running writes with `apply_batch`
- repair loops after invalid regex, stale route sets, stale trails, protected writes, and stale edit previews

## Where Plain Tools Still Win

m1nd is not the headline tool for:

- exact text search
- one-file lookup when you already know the file
- compiler truth
- runtime logs and debugger work

Use `rg`, the compiler, the test runner, and logs when execution truth is the question. Use m1nd when navigation and connected structure are the bottleneck.

## Why The Corpus Matters

The benchmark corpus is now part of product development, not just a marketing appendix.

Recent runtime and UX improvements were driven directly by measured benchmark pain:

- `proof_state` and next-step guidance on core flows
- more actionable `trail_resume`
- better `seek` handling for natural-language prompts
- reduced `validate_plan` noise
- more useful `surgical_context_v2`
- observable `apply_batch` progress and SSE handoff
- recovery-oriented error payloads for invalid or stale tool calls

## Reproducibility

To inspect the current benchmark system:

    git clone https://github.com/maxkle1nz/m1nd.git
    cd m1nd
    cargo build --release --workspace
    python3 scripts/benchmark/run_benchmark.py --help
    python3 scripts/benchmark/summarize_benchmarks.py --help

The versioned scenarios, events, and run outputs live under `docs/benchmarks/`.

### Memory

Component | Size
--- | ---
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.

Files | Estimated Ingest Time | Estimated Nodes
--- | --- | ---
100 | ~270ms | ~3,000
335 | 910ms (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.

## [0.6.2] — 2026-03-26

### Added

#### Universal Knowledge Connectome

The ingestion pipeline now supports scholarly and standards documents alongside code:

- Patent adapter (`patent`): USPTO/EPO XML with claims, citations, and assignee extraction
- JATS adapter (`article`): PubMed NLM / JATS Z39.96 scientific article XML
- BibTeX adapter (`bibtex`/`bib`): BibTeX bibliography file ingestion
- RFC adapter (`rfc`): IETF RFC XML v3 with section-level granularity
- CrossRef adapter (`crossref`/`doi`): CrossRef API JSON for DOI metadata

#### Document Router (Auto-Detection)

`DocumentRouter` auto-detects file format by extension and content heuristics. Used via `m1nd.ingest(adapter="auto")`.

#### Cross-Domain Resolution — 6 Bridge Strategies

`CrossDomainResolver` merges outputs from multiple adapters and discovers connections:

Bridge | Weight | Discovery
--- | --- | ---
`same_as` | 1.0 | Shared DOI/PMID identity
`cross_cites` | 0.95 | Cross-domain citation targets
`same_orcid` | 0.95 | Researcher identity via ORCID
`same_author` | 0.7 | Name matching across namespaces
`shared_keyword` | 0.6 | Topic clustering via keyword/subject tags (≤20 cap)
`citation_chain` | 0.5 | Transitive A→B→C bridging

All bridges are cross-domain only — same namespace never bridges.

### Changed

- README hero stats added to all 7 translated READMEs
- `.gitignore` updated to exclude `.DS_Store` and `*.bak` files
- Architecture docs updated with all new adapters, router, and bridges

### Removed

- `.DS_Store` and `antibodies.json.bak` removed from repository

### Stats

- 10 ingestion adapters registered in MCP CLI
- 131+ tests passing
- 3/3 domains validated bridging via shared DOIs (RFC × CrossRef × BibTeX)

## [0.6.1] — 2026-03-25

### Fixed

#### Release and Publish Alignment

This patch release aligns the public release surfaces after the `v0.6.0` rollout.

- workspace crates now include the missing crates.io metadata needed for clean publish
- internal workspace dependencies now use explicit published-version constraints
- the release workflow now skips crates.io publish cleanly when `CARGO_REGISTRY_TOKEN` is missing instead of failing the full release

## [0.6.0] — 2026-03-25

### Added

#### Guided Proof State Across Core Agent Flows

Several high-value tools now surface `proof_state` plus explicit handoff guidance so an agent can tell whether it is still triaging, actively proving, or ready to move into edit preparation.

- `seek`, `trace`, `impact`, `timeline`, `hypothesize`, `validate_plan`, and `surgical_context_v2` now participate in a shared proof-state model
- guided outputs now include `next_suggested_tool`, `next_suggested_target`, and `next_step_hint` across the main structural triage and edit-prep paths
- `trail_resume` now behaves more like continuity orchestration than bookmark restore, returning compact resume hints, next-focus guidance, and tool-aware follow-up

#### `apply_batch` Progress, Correlation, and Handoff Signals

`apply_batch` has been upgraded from a “wait until the batch finishes” write surface into an observable execution flow with stable correlation and final handoff data.

- final outputs now expose `batch_id` for correlating progress and final result
- progress reporting now includes coarse lifecycle fields such as `active_phase`, `completed_phase_count`, `phase_count`, `remaining_phase_count`, `progress_pct`, and `next_phase`
- `phases` now act as a structured execution timeline across `validate`, `write`, `reingest`, `verify`, and `done`
- `progress_events` now provide a streaming-friendly event log for the same lifecycle
- live `apply_batch_progress` SSE emission now happens during execution in serve mode
- the final `batch_completed` event now carries the batch’s `proof_state` and next-step guidance

#### Benchmark Harness Expansion

The benchmark system has been extended so progress UX, guidance, and repair loops can be measured as first-class product behavior, not only token proxy.

- benchmark runs now record `execution_origin` and `source_ref`
- long-running flows can distinguish `live`, `replay`, and `snapshot` progress delivery
- the harness records progress event counts, proof-state transitions, recovery loops, and guidance-followed behavior
- the current aggregate warm-graph corpus stands at `10518 -> 5182`, or `50.73%` savings, while also reducing `false_starts` from `14` to `0`

### Changed

#### Help and Docs Are More Agent-Operational

- help entries now include `WHEN TO USE`, `AVOID WHEN`, benchmark-aware guidance, composed workflows, and proof-state handoff cues
- common tool failures are now framed as repair loops with hint/example/next-step guidance
- README, examples, benchmark docs, and public landing surfaces now describe the current guided runtime more accurately

## [0.5.1] — 2026-03-24

Two-phase transactional editing for LLM agents. Preview before you write.

### New Tools

- `edit_preview`: Build an in-memory preview of a single-file edit without touching disk. Returns preview handle, source snapshot (hash, bytes, line count), unified diff, and validation report (empty content check, noop detection, ready-to-commit flag).
- `edit_commit`: Commit a previously created preview to disk. Three safety guards:
  - Confirm guard: `confirm` must be explicitly set to `true` (default `false`).
  - TTL: Previews expire after 5 minutes. Garbage-collected on access.
  - Source hash verification: Re-reads the file at commit time; rejects if it changed since preview.

### Technical Details

- `EditPreviewState` stored in `SessionState` with agent isolation (agent_id must match).
- Preview handles are single-use — consumed on successful commit.
- Delegates to existing `handle_apply()` path for actual disk write + graph re-ingest.
- 7 new integration tests covering: happy path, nonexistent file, commit, TTL expiry, source tampering, confirm guard, invalid handle.
- Help system updated with ToolDoc entries and suggest_next chains.

### Stats

- 77 MCP tools total on the current audit branch (was 71 before the audit/session additions).
- 342 tests all passing.

## [0.1.0] — Initial Release

The first public release of m1nd: a graph-grounded code intelligence 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)