From ed87c0833b090a5a6350e6c7d2a9b968471aacbe Mon Sep 17 00:00:00 2001 From: R4vager Date: Wed, 20 May 2026 01:26:49 -0400 Subject: [PATCH 1/2] Claustrum Phase 1: cross-modal binding (Avenue 3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Detects when multiple retrieval modalities converge on the same memory_id. Crick & Koch 2005 consciousness-binding hypothesis. - Migration 079: 3 tables. 9 seeded modalities (fts, vector, hybrid_rrf, pagerank_boost, multi_pass, temporal_expand, entorhinal_grid, procedural_search, ca3_completion). - binding_strength = mean(weights) × min(1.0, count/4). - 5 MCP tools: status, record_binding, register_modality, memory_bindings, set. - 10 tests. Phase 2 auto-detects from cmd_search output. Phase 3 boosts memory confidence by binding strength. Co-Authored-By: Claude Opus 4.7 (1M context) --- db/migrations/079_claustrum.sql | 77 +++++++ src/agentmemory/mcp_tools_claustrum.py | 289 +++++++++++++++++++++++++ tests/test_mcp_tools_claustrum.py | 119 ++++++++++ 3 files changed, 485 insertions(+) create mode 100644 db/migrations/079_claustrum.sql create mode 100644 src/agentmemory/mcp_tools_claustrum.py create mode 100644 tests/test_mcp_tools_claustrum.py diff --git a/db/migrations/079_claustrum.sql b/db/migrations/079_claustrum.sql new file mode 100644 index 0000000..2ea6a34 --- /dev/null +++ b/db/migrations/079_claustrum.sql @@ -0,0 +1,77 @@ +-- Migration 079: claustrum — Phase 1 cross-modal binding +-- +-- Avenue 3 from research/autonomous-research-avenues-2026-05-20.md. +-- The claustrum is a thin sheet that everyone projects to and that +-- projects to everyone (Crick & Koch 2005). Function: cross-modal +-- binding / consciousness integration. +-- +-- brainctl analog: detect when multiple retrieval modalities (FTS, +-- vector, hybrid_rrf, pagerank_boost, multi_pass, temporal_expand, +-- entorhinal_grid, procedural_search) converge on the same memory. +-- Cross-modal convergence is a strong signal that wasn't previously +-- tracked. +-- +-- Phase 1 ships: +-- claustrum_binding_events — when ≥2 modalities surface the same +-- memory_id within a window +-- claustrum_modality_catalog — known retrieval modalities + meta +-- claustrum_state — running stats +-- +-- Phase 2 auto-detects from cmd_search. Phase 3 boosts memory +-- confidence by binding_strength when multiple modalities agree. +-- +-- Rollback: +-- DROP TABLE IF EXISTS claustrum_binding_events; +-- DROP TABLE IF EXISTS claustrum_modality_catalog; +-- DROP TABLE IF EXISTS claustrum_state; +-- DELETE FROM schema_version WHERE version = 79; +-- +-- IDEMPOTENT. + +CREATE TABLE IF NOT EXISTS claustrum_state ( + id INTEGER PRIMARY KEY CHECK (id = 1), + binding_window_seconds INTEGER NOT NULL DEFAULT 60 CHECK(binding_window_seconds > 0), + min_modalities_for_binding INTEGER NOT NULL DEFAULT 2 CHECK(min_modalities_for_binding >= 2), + total_bindings INTEGER NOT NULL DEFAULT 0, + enforcement_mode TEXT NOT NULL DEFAULT 'shadow' CHECK(enforcement_mode IN ('shadow', 'enforce', 'disabled')), + updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now')) +); +INSERT OR IGNORE INTO claustrum_state (id) VALUES (1); + +CREATE TABLE IF NOT EXISTS claustrum_modality_catalog ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE, + description TEXT, + weight REAL NOT NULL DEFAULT 1.0 CHECK(weight BETWEEN 0.0 AND 1.0), + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now')) +); + +INSERT OR IGNORE INTO claustrum_modality_catalog (name, description, weight) VALUES + ('fts', 'BM25 full-text search via memories_fts', 1.0), + ('vector', 'cosine-distance search via vec_memories', 1.0), + ('hybrid_rrf', 'reciprocal rank fusion of FTS + vector', 1.0), + ('pagerank_boost', 'SR-style retrieval (PageRank == Successor Representation)', 0.8), + ('multi_pass', 'SDM-style iterative convergence', 0.8), + ('temporal_expand', 'TCM temporal contiguity expansion', 0.7), + ('entorhinal_grid', 'grid-cell hash activation lookup', 0.9), + ('procedural_search', 'procedural memory FTS5 search', 0.9), + ('ca3_completion', 'CA3 pattern-completion via hippocampus_ca3', 1.0); + +CREATE TABLE IF NOT EXISTS claustrum_binding_events ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + bound_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now')), + memory_id INTEGER NOT NULL, + agent_id TEXT, + query_hash TEXT, + modalities TEXT NOT NULL, -- comma-separated list of modality names that converged + modality_count INTEGER NOT NULL CHECK(modality_count >= 2), + binding_strength REAL NOT NULL CHECK(binding_strength BETWEEN 0.0 AND 1.0), + notes TEXT +); +CREATE INDEX IF NOT EXISTS idx_cbe_recent ON claustrum_binding_events(bound_at); +CREATE INDEX IF NOT EXISTS idx_cbe_memory ON claustrum_binding_events(memory_id, bound_at); +CREATE INDEX IF NOT EXISTS idx_cbe_strength ON claustrum_binding_events(binding_strength); + +INSERT OR IGNORE INTO schema_version (version, description, applied_at) +VALUES (79, 'claustrum Phase 1: cross-modal binding (3 tables, 9 modality catalog)', + strftime('%Y-%m-%dT%H:%M:%S', 'now')); diff --git a/src/agentmemory/mcp_tools_claustrum.py b/src/agentmemory/mcp_tools_claustrum.py new file mode 100644 index 0000000..4dc6f39 --- /dev/null +++ b/src/agentmemory/mcp_tools_claustrum.py @@ -0,0 +1,289 @@ +"""brainctl MCP tools — claustrum (cross-modal binding). + +Phase 1 per research-avenues memo Avenue 3. Detects when multiple +retrieval modalities converge on the same memory — a cross-modal +agreement signal that wasn't previously a first-class concept. + +Phase 1 = manual binding events + queries. Phase 2 auto-detects from +cmd_search output. Phase 3 boosts memory confidence by binding strength. +""" +from __future__ import annotations + +import sqlite3 +from collections.abc import Iterable +from pathlib import Path +from typing import Any + +from mcp.types import Tool + +from agentmemory.lib.mcp_helpers import open_db +from agentmemory.paths import get_db_path + +DB_PATH: Path = get_db_path() + +VALID_ENFORCEMENT_MODES = {"shadow", "enforce", "disabled"} + + +def _db() -> sqlite3.Connection: + return open_db(str(DB_PATH)) + + +def _rows(rows: Iterable[sqlite3.Row]) -> list[dict[str, Any]]: + return [dict(r) for r in rows] + + +def _require_schema(conn: sqlite3.Connection) -> str | None: + for t in ("claustrum_state", "claustrum_modality_catalog", "claustrum_binding_events"): + if not conn.execute( + "SELECT 1 FROM sqlite_master WHERE type='table' AND name=?", (t,) + ).fetchone(): + return f"claustrum schema missing: {t}. Run `brainctl migrate` (079)." + return None + + +def tool_claustrum_status(**_kw: Any) -> dict[str, Any]: + with _db() as conn: + conn.row_factory = sqlite3.Row + err = _require_schema(conn) + if err: + return {"error": err} + state = conn.execute("SELECT * FROM claustrum_state WHERE id = 1").fetchone() + catalog = _rows(conn.execute("SELECT * FROM claustrum_modality_catalog ORDER BY name").fetchall()) + last_5 = _rows(conn.execute( + "SELECT * FROM claustrum_binding_events ORDER BY id DESC LIMIT 5" + ).fetchall()) + agg = conn.execute( + """ + SELECT COUNT(*) AS n, + COALESCE(AVG(binding_strength), 0.0) AS mean_strength, + COALESCE(MAX(modality_count), 0) AS peak_modalities + FROM claustrum_binding_events + WHERE bound_at >= datetime('now', '-24 hours') + """ + ).fetchone() + return { + "ok": True, + "state": dict(state) if state else None, + "modality_catalog": catalog, + "last_5_bindings": last_5, + "aggregate_24h": dict(agg) if agg else {}, + } + + +def tool_claustrum_record_binding( + memory_id: int, modalities: list[str] | str, + agent_id: str | None = None, query_hash: str | None = None, + notes: str | None = None, + **_kw: Any, +) -> dict[str, Any]: + """Record a cross-modal binding event. modalities can be a list of + modality names or a comma-separated string. Computes + binding_strength as the mean of modality weights × normalized count.""" + if isinstance(modalities, str): + modality_list = [m.strip() for m in modalities.split(",") if m.strip()] + else: + modality_list = [str(m).strip() for m in modalities if str(m).strip()] + with _db() as conn: + conn.row_factory = sqlite3.Row + err = _require_schema(conn) + if err: + return {"error": err} + state = conn.execute("SELECT * FROM claustrum_state WHERE id = 1").fetchone() + min_n = int(state["min_modalities_for_binding"]) if state else 2 + if len(modality_list) < min_n: + return {"error": f"modality count {len(modality_list)} < min_modalities_for_binding={min_n}"} + catalog = {r["name"]: float(r["weight"]) for r in conn.execute( + "SELECT name, weight FROM claustrum_modality_catalog" + ).fetchall()} + unknown = [m for m in modality_list if m not in catalog] + if unknown: + return {"error": f"unknown modalities: {unknown}. Register via claustrum_register_modality."} + # binding_strength = mean(weights) × min(1.0, count / 4) + mean_w = sum(catalog[m] for m in modality_list) / len(modality_list) + count_factor = min(1.0, len(modality_list) / 4.0) + binding_strength = max(0.0, min(1.0, mean_w * count_factor)) + modalities_csv = ",".join(sorted(set(modality_list))) + cur = conn.execute( + """ + INSERT INTO claustrum_binding_events + (memory_id, agent_id, query_hash, modalities, modality_count, binding_strength, notes) + VALUES (?, ?, ?, ?, ?, ?, ?) + """, + (int(memory_id), agent_id, query_hash, modalities_csv, + len(set(modality_list)), float(binding_strength), notes), + ) + binding_id = cur.lastrowid + conn.execute( + "UPDATE claustrum_state SET total_bindings = total_bindings + 1, updated_at = strftime('%Y-%m-%dT%H:%M:%S', 'now') WHERE id = 1" + ) + conn.commit() + return { + "ok": True, "binding_id": binding_id, + "memory_id": int(memory_id), + "modalities": modalities_csv, + "binding_strength": binding_strength, + } + + +def tool_claustrum_register_modality( + name: str, description: str | None = None, weight: float = 1.0, + **_kw: Any, +) -> dict[str, Any]: + if not 0.0 <= weight <= 1.0: + return {"error": "weight must be in [0, 1]"} + with _db() as conn: + conn.row_factory = sqlite3.Row + err = _require_schema(conn) + if err: + return {"error": err} + conn.execute( + """ + INSERT INTO claustrum_modality_catalog (name, description, weight) + VALUES (?, ?, ?) + ON CONFLICT(name) DO UPDATE SET + weight = excluded.weight, + description = COALESCE(excluded.description, claustrum_modality_catalog.description) + """, + (name, description, float(weight)), + ) + conn.commit() + row = conn.execute( + "SELECT * FROM claustrum_modality_catalog WHERE name = ?", (name,) + ).fetchone() + return {"ok": True, "modality": dict(row) if row else None} + + +def tool_claustrum_memory_bindings(memory_id: int, limit: int = 10, **_kw: Any) -> dict[str, Any]: + limit = max(1, min(int(limit), 100)) + with _db() as conn: + conn.row_factory = sqlite3.Row + err = _require_schema(conn) + if err: + return {"error": err} + rows = conn.execute( + """ + SELECT * FROM claustrum_binding_events + WHERE memory_id = ? ORDER BY id DESC LIMIT ? + """, + (int(memory_id), limit), + ).fetchall() + return {"ok": True, "memory_id": int(memory_id), "bindings": _rows(rows)} + + +def tool_claustrum_set( + binding_window_seconds: int | None = None, + min_modalities_for_binding: int | None = None, + enforcement_mode: str | None = None, + **_kw: Any, +) -> dict[str, Any]: + if binding_window_seconds is not None and binding_window_seconds <= 0: + return {"error": "binding_window_seconds must be > 0"} + if min_modalities_for_binding is not None and min_modalities_for_binding < 2: + return {"error": "min_modalities_for_binding must be ≥ 2"} + if enforcement_mode is not None and enforcement_mode not in VALID_ENFORCEMENT_MODES: + return {"error": f"invalid enforcement_mode"} + with _db() as conn: + conn.row_factory = sqlite3.Row + err = _require_schema(conn) + if err: + return {"error": err} + updates, params = [], [] + if binding_window_seconds is not None: + updates.append("binding_window_seconds = ?"); params.append(int(binding_window_seconds)) + if min_modalities_for_binding is not None: + updates.append("min_modalities_for_binding = ?"); params.append(int(min_modalities_for_binding)) + if enforcement_mode is not None: + updates.append("enforcement_mode = ?"); params.append(enforcement_mode) + if not updates: + return {"error": "no fields to update"} + updates.append("updated_at = strftime('%Y-%m-%dT%H:%M:%S', 'now')") + conn.execute(f"UPDATE claustrum_state SET {', '.join(updates)} WHERE id = 1", tuple(params)) + conn.commit() + state = conn.execute("SELECT * FROM claustrum_state WHERE id = 1").fetchone() + return {"ok": True, "state": dict(state) if state else None} + + +TOOLS: list[Tool] = [ + Tool( + name="claustrum_status", + description="Claustrum state + modality catalog + last 5 bindings + 24h aggregate.", + inputSchema={"type": "object", "properties": {}}, + ), + Tool( + name="claustrum_record_binding", + description=( + "Record a cross-modal binding event when ≥min_modalities retrieval modalities " + "converged on the same memory. modalities can be a list or CSV. binding_strength " + "auto-computed from mean modality weights × min(1.0, count/4)." + ), + inputSchema={ + "type": "object", + "properties": { + "memory_id": {"type": "integer"}, + "modalities": {"oneOf": [ + {"type": "array", "items": {"type": "string"}}, + {"type": "string"}, + ]}, + "agent_id": {"type": "string"}, + "query_hash": {"type": "string"}, + "notes": {"type": "string"}, + }, + "required": ["memory_id", "modalities"], + }, + ), + Tool( + name="claustrum_register_modality", + description="Idempotent UPSERT on modality_catalog. weight in [0, 1]. Used when a new retrieval modality is added to brainctl.", + inputSchema={ + "type": "object", + "properties": { + "name": {"type": "string"}, + "description": {"type": "string"}, + "weight": {"type": "number", "default": 1.0}, + }, + "required": ["name"], + }, + ), + Tool( + name="claustrum_memory_bindings", + description="All binding events for a specific memory_id, newest first. limit clamped to [1, 100].", + inputSchema={ + "type": "object", + "properties": { + "memory_id": {"type": "integer"}, + "limit": {"type": "integer", "default": 10}, + }, + "required": ["memory_id"], + }, + ), + Tool( + name="claustrum_set", + description="Update claustrum state knobs. binding_window_seconds > 0; min_modalities_for_binding ≥ 2; enforcement_mode ∈ {shadow, enforce, disabled}.", + inputSchema={ + "type": "object", + "properties": { + "binding_window_seconds": {"type": "integer"}, + "min_modalities_for_binding": {"type": "integer"}, + "enforcement_mode": {"type": "string", "enum": sorted(VALID_ENFORCEMENT_MODES)}, + }, + }, + ), +] + + +_CLAUSTRUM_TOOLS = { + "claustrum_status": tool_claustrum_status, + "claustrum_record_binding": tool_claustrum_record_binding, + "claustrum_register_modality": tool_claustrum_register_modality, + "claustrum_memory_bindings": tool_claustrum_memory_bindings, + "claustrum_set": tool_claustrum_set, +} + +DISPATCH: dict[str, Any] = { + name: (lambda _func=func, **kw: _func(**kw)) + for name, func in _CLAUSTRUM_TOOLS.items() +} + + +def register_tools() -> tuple[list[Tool], dict[str, Any]]: + return TOOLS, DISPATCH diff --git a/tests/test_mcp_tools_claustrum.py b/tests/test_mcp_tools_claustrum.py new file mode 100644 index 0000000..7d55c55 --- /dev/null +++ b/tests/test_mcp_tools_claustrum.py @@ -0,0 +1,119 @@ +"""Tests for mcp_tools_claustrum — Phase 1.""" +from __future__ import annotations + +import sqlite3 +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[1] +MIGRATION_079 = REPO_ROOT / "db" / "migrations" / "079_claustrum.sql" + + +def _bootstrap(conn): + conn.executescript( + "CREATE TABLE IF NOT EXISTS schema_version (version INTEGER PRIMARY KEY, description TEXT, applied_at TEXT);" + ) + + +def _apply(db_path): + conn = sqlite3.connect(str(db_path)) + try: + _bootstrap(conn) + conn.executescript(MIGRATION_079.read_text()) + conn.commit() + finally: + conn.close() + + +def _make_db(tmp_path, monkeypatch): + db = tmp_path / "brain.db" + _apply(db) + from agentmemory import mcp_tools_claustrum as mod + monkeypatch.setattr(mod, "DB_PATH", db) + return mod + + +def test_migration_seeds_modalities(tmp_path): + db = tmp_path / "brain.db" + _apply(db) + conn = sqlite3.connect(str(db)) + try: + n = conn.execute("SELECT COUNT(*) FROM claustrum_modality_catalog").fetchone()[0] + assert n == 9 + state = conn.execute( + "SELECT binding_window_seconds, min_modalities_for_binding, enforcement_mode FROM claustrum_state" + ).fetchone() + assert state == (60, 2, "shadow") + finally: + conn.close() + + +def test_status_returns_state(tmp_path, monkeypatch): + mod = _make_db(tmp_path, monkeypatch) + out = mod.tool_claustrum_status() + assert out["ok"] is True + assert len(out["modality_catalog"]) == 9 + + +def test_record_binding_with_list(tmp_path, monkeypatch): + mod = _make_db(tmp_path, monkeypatch) + out = mod.tool_claustrum_record_binding( + memory_id=42, modalities=["fts", "vector", "hybrid_rrf"], agent_id="a1", + ) + assert out["ok"] is True + assert out["binding_id"] is not None + # All 3 weights = 1.0 → mean=1.0; count_factor = 3/4 = 0.75 → strength = 0.75 + assert abs(out["binding_strength"] - 0.75) < 1e-9 + + +def test_record_binding_with_csv(tmp_path, monkeypatch): + mod = _make_db(tmp_path, monkeypatch) + out = mod.tool_claustrum_record_binding( + memory_id=42, modalities="fts,vector", + ) + assert out["ok"] is True + # 2 modalities × weight 1.0 → mean=1.0; count_factor=2/4=0.5 → strength 0.5 + assert abs(out["binding_strength"] - 0.5) < 1e-9 + + +def test_record_binding_rejects_below_min(tmp_path, monkeypatch): + mod = _make_db(tmp_path, monkeypatch) + out = mod.tool_claustrum_record_binding(memory_id=1, modalities=["fts"]) + assert "error" in out + + +def test_record_binding_rejects_unknown_modality(tmp_path, monkeypatch): + mod = _make_db(tmp_path, monkeypatch) + out = mod.tool_claustrum_record_binding(memory_id=1, modalities=["fts", "nope"]) + assert "error" in out + + +def test_register_modality_idempotent(tmp_path, monkeypatch): + mod = _make_db(tmp_path, monkeypatch) + first = mod.tool_claustrum_register_modality(name="my_custom", weight=0.6) + second = mod.tool_claustrum_register_modality(name="my_custom", weight=0.8) + assert first["ok"] and second["ok"] + assert second["modality"]["weight"] == 0.8 + + +def test_register_modality_validates(tmp_path, monkeypatch): + mod = _make_db(tmp_path, monkeypatch) + assert "error" in mod.tool_claustrum_register_modality(name="x", weight=1.5) + + +def test_memory_bindings_history(tmp_path, monkeypatch): + mod = _make_db(tmp_path, monkeypatch) + mod.tool_claustrum_record_binding(memory_id=99, modalities=["fts", "vector"]) + mod.tool_claustrum_record_binding(memory_id=99, modalities=["fts", "ca3_completion"]) + out = mod.tool_claustrum_memory_bindings(memory_id=99) + assert out["ok"] is True + assert len(out["bindings"]) == 2 + + +def test_set_validates(tmp_path, monkeypatch): + mod = _make_db(tmp_path, monkeypatch) + assert "error" in mod.tool_claustrum_set(binding_window_seconds=0) + assert "error" in mod.tool_claustrum_set(min_modalities_for_binding=1) + assert "error" in mod.tool_claustrum_set(enforcement_mode="bogus") + out = mod.tool_claustrum_set(min_modalities_for_binding=3, enforcement_mode="enforce") + assert out["ok"] is True + assert out["state"]["min_modalities_for_binding"] == 3 From f63faa4d9f2b943df55b6205bd623a624c478a15 Mon Sep 17 00:00:00 2001 From: R4vager Date: Wed, 20 May 2026 01:27:07 -0400 Subject: [PATCH 2/2] claustrum: register mcp_server import (fixup) --- src/agentmemory/mcp_server.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/agentmemory/mcp_server.py b/src/agentmemory/mcp_server.py index 95e5f45..4e4a218 100755 --- a/src/agentmemory/mcp_server.py +++ b/src/agentmemory/mcp_server.py @@ -48,6 +48,7 @@ mcp_tools_belief_merge, mcp_tools_beliefs, mcp_tools_cerebellum, + mcp_tools_claustrum, mcp_tools_consolidation, mcp_tools_dmn, mcp_tools_drives, @@ -91,6 +92,7 @@ mcp_tools_belief_merge, mcp_tools_beliefs, mcp_tools_cerebellum, + mcp_tools_claustrum, mcp_tools_consolidation, mcp_tools_dmn, mcp_tools_drives,