🦀 CrabPath: The Graph is the Prompt. LLM-guided memory traversal with learned pointer weights.
Project description
🦀 CrabPath: The Graph is the Prompt
LLM-guided memory traversal with learned pointer weights and corrected policy gradients.
CrabPath is a memory architecture for AI agents where documents are nodes, weighted pointers are edges, and an LLM is the activation function. The graph learns which paths lead to good outcomes — and which to suppress — using trajectory-aware credit assignment (Gu, 2016). Over time, expensive LLM reasoning compiles into cheap reflexive routing.
The key result: CrabPath matches BM25 on retrieval accuracy (0.742 vs 0.737) but achieves perfect negation accuracy where BM25 and standard RAG score zero. The system learns what not to retrieve — inhibitory edges actively suppress known-wrong paths.
Headline Numbers
| Metric | Value |
|---|---|
| Context reduction vs static | 90–99% |
| Negation accuracy (CrabPath vs BM25) | 1.000 vs 0.000 |
| Corrected PG vs myopic REINFORCE | +11 pp (non-overlapping 95% CIs) |
| Cost per turn (two-tier routing) | $0.004 vs $0.091 static |
| External benchmark (HotpotQA) | BM25 dominates cold-start IR; CrabPath learns (1948 edge updates) but topic diversity limits transfer |
| Recurring-topic benchmark (200 queries) | CrabPath R@2: 0.02→0.07 vs BM25 flat 0.27 |
| Sparsity crossover benchmark | Synthetic clusters: Phase2 Recall@3 crossover at ~50 nodes in sparse regimes (edge_ratio 0.1/0.05) |
| Tests | 360, 0 lint errors |
| Dependencies | Zero (stdlib only) |
Install
pip install crabpath
Requires Python 3.10+. Zero dependencies. Embeddings require one of:
OPENAI_API_KEY— OpenAI text-embedding-3-small (~$0.02 for 200 nodes)GEMINI_API_KEY— Gemini text-embedding-004 (free tier)- Local Ollama —
ollama pull nomic-embed-text(free, local)
Quick Start
from crabpath import Graph, Node, Edge, activate, learn
g = Graph()
g.add_node(Node(id="check-tests", content="Run test suite before deploy"))
g.add_node(Node(id="skip-tests", content="Skip tests, deploy directly"))
g.add_node(Node(id="deploy", content="Deploy to production"))
g.add_edge(Edge(source="check-tests", target="deploy", weight=0.5))
g.add_edge(Edge(source="skip-tests", target="deploy", weight=0.5))
result = activate(g, seeds={"check-tests": 1.0, "skip-tests": 1.0})
learn(g, result, outcome=1.0) # STDP: causal edges strengthened
g.save("memory.json")
Recommended Import Path
Use MemoryController as the primary API for new integrations; it wraps query and learn in one place.
Query-only
from crabpath import Graph, MemoryController
graph = Graph.load("graph.json")
controller = MemoryController(graph)
result = controller.query("How do I respond to a rollback?")
print(result.context)
Query + learn
from crabpath import Graph, MemoryController
graph = Graph.load("graph.json")
controller = MemoryController(graph)
result = controller.query("How do I respond to a rollback?")
controller.learn(result, reward=1.0)
graph.save("graph.json")
Full shadow mode (query, log, learn)
import json
from pathlib import Path
from time import time
from crabpath import Graph, MemoryController
graph = Graph.load("graph.json")
controller = MemoryController(graph)
result = controller.query("Incident failed after deploy")
log_path = Path.home() / ".crabpath" / "shadow.log"
log_path.parent.mkdir(parents=True, exist_ok=True)
with log_path.open("a", encoding="utf-8") as stream:
stream.write(json.dumps({"ts": time(), "query": "Incident failed after deploy", "nodes": result.selected_nodes}) + "\n")
controller.learn(result, reward=1.0)
graph.save("graph.json")
Lower-level modules like synaptogenesis, mitosis, and autotune are still available for advanced users who need direct control.
Architecture
CrabPath has three components:
- Nodes — documents with content, summary, and semantic type (
fact,procedure,action,tool_call) - Edges — signed weighted pointers (excitatory or inhibitory) with kind labels (
support,inhibit,follows,tool) - LLM activation — reads node content, inspects candidate edges, decides which to traverse
Core Modules
| Module | Purpose |
|---|---|
controller.py |
MemoryController — orchestrates query → retrieve → learn cycles |
inhibition.py |
Negative-edge routing, correction signals, suppression scoring |
learning.py |
Gu-corrected policy gradient + LearningPhaseManager (Hebbian → RL transition) |
synaptogenesis.py |
Proto-edge formation → promotion → Hebbian reinforcement → competition |
mitosis.py |
Recursive cell division: LLM-driven splitting, merging, neurogenesis |
autotune.py |
Self-regulation with meta-learning, safety guardrails, emergency brake |
router.py |
Three-tier LLM routing (reflex/habitual/dormant) |
traversal.py |
Multi-hop traversal with damped edges (max_hops=30, episode_edge_damping=0.3) |
decay.py |
Exponential weight decay |
embeddings.py |
Multi-provider embeddings (OpenAI, Gemini, Cohere, Ollama) |
migrate.py |
Bootstrap graphs from workspace files + session log replay |
adapter.py |
OpenClawCrabPathAdapter — drop-in integration for OpenClaw agents |
feedback.py |
Correction detection, LLM scoring, auto-feedback |
shadow_logger.py |
Shadow mode logging for safe evaluation |
MemoryController (recommended entry point)
from crabpath import MemoryController, ControllerConfig
from crabpath.learning import LearningConfig
from crabpath.synaptogenesis import SynaptogenesisConfig
from crabpath.inhibition import InhibitionConfig
from crabpath.decay import DecayConfig
controller = MemoryController(
graph_path="graph.json",
config=ControllerConfig(
learning=LearningConfig(),
synaptogenesis=SynaptogenesisConfig(),
inhibition=InhibitionConfig(),
decay=DecayConfig(),
),
embed_fn=my_embed_fn, # or None for keyword fallback
router_fn=my_llm_call, # or None for weight-only routing
)
# Query
result = controller.query("deploy broke after config change")
context = result.context # rendered context string
context_chars = result.context_chars
# Learn from outcome
controller.learn(result, reward=1.0) # or -1.0 for correction
# Save
controller.save()
Three-Tier Routing
| Tier | Weight | Behavior | Cost |
|---|---|---|---|
| Reflex | > 0.9 | Auto-follow, no LLM call | Very low |
| Habitual | 0.1–0.9 | Presented as candidates to LLM | Moderate |
| Dormant | < 0.1 | Skipped unless explicit override | Near zero |
Learning: Two-Phase Dynamics
- Phase 1 — Hebbian (~20–100 queries): Co-firing differentiates edge weights. Policy gradient signals wash out because softmax is near-uniform.
- Phase 2 — RL refinement (post-transition): Weights are differentiated enough for sharp softmax. Corrected policy gradient propagates reward across full traversal path.
The LearningPhaseManager detects the transition adaptively via weight-entropy and gradient-magnitude thresholds.
Inhibition
The differentiator. When the user says "the codeword is elephant, NOT giraffe":
- BM25 returns both (semantically similar) → score: 0.0
- CrabPath's inhibitory edge suppresses giraffe → score: 1.0
from crabpath.inhibition import apply_correction, is_inhibited
from crabpath.inhibition import InhibitionConfig
# After user correction
apply_correction(graph, trajectory=["giraffe", "elephant"], reward=-0.8, config=InhibitionConfig())
# During routing
if is_inhibited(graph, "giraffe", "elephant"):
# skip this node
Embeddings
from crabpath import auto_embed, openai_embed, gemini_embed, ollama_embed
# Automatic (tries OpenAI → Gemini → Ollama)
vectors = auto_embed(["deploy broke", "check CI"])
# Or specific provider
vectors = openai_embed(["deploy broke"]) # needs OPENAI_API_KEY
vectors = gemini_embed(["deploy broke"]) # needs GEMINI_API_KEY
vectors = ollama_embed(["deploy broke"]) # free, local
CLI
All commands emit JSON on stdout.
# Query the graph
crabpath query "deploy broke" --graph graph.json --index embed.json --top 8
# Learn from outcome
crabpath learn --graph graph.json --outcome 1.0 --fired-ids node1,node2
# Check graph health (8 metrics)
crabpath health --graph graph.json
# Track evolution over time
crabpath evolve --graph graph.json --snapshots evolution.jsonl --report
# Run lifecycle simulation
crabpath sim --queries 200 --output results.json
# Bootstrap from workspace
crabpath migrate --workspace ~/.openclaw/workspace --output-graph graph.json --output-embeddings embed.json
# Split a node via LLM
crabpath split --graph graph.json --node-id tools --save
# Stats
crabpath stats --graph graph.json
# Snapshot + feedback
crabpath snapshot --graph graph.json --session sess-1 --turn 42 --fired-ids n1,n2
crabpath feedback --session sess-1 --turn-window 5
MCP Server
python -m crabpath.mcp_server
Tools: query, migrate, learn, stats, split, add, remove, consolidate
OpenClaw Integration
from crabpath import OpenClawCrabPathAdapter
adapter = OpenClawCrabPathAdapter(
graph_path="graph.json",
index_path="embed.json",
embed_fn=auto_embed,
)
graph, index = adapter.load()
seeds = adapter.seed("deploy broke", memory_search_ids=["hit-1"])
firing = adapter.activate(seeds)
context = adapter.context(firing)
adapter.learn(firing, outcome=1.0)
adapter.save()
Self-Regulation (Autotuner)
The autotuner maintains 8 health metrics in target ranges:
| Metric | Target |
|---|---|
| Avg nodes fired/query | 3–8 |
| Cross-file edge % | 5–20% |
| Dormant % | 60–90% |
| Reflex % | 1–5% |
| Context compression | ≤ 20% |
| Proto promotion rate | 5–15% |
| Reconvergence rate | ≤ 10% |
| Orphan nodes | 0 |
from crabpath import measure_health, autotune
from crabpath.autotune import self_tune, TuneMemory
health = measure_health(graph, state, query_stats)
health, adjustments, changes = self_tune(
graph, state, query_stats,
syn_config, decay_config, mitosis_config,
cycle_number=1,
tune_memory=TuneMemory(),
)
Recursive Cell Division (Mitosis)
Files start as monolithic nodes → LLM splits into coherent chunks → sibling edges at weight 1.0 → activation/decay differentiates → reconverged chunks get re-split differently. The graph finds its own resolution.
from crabpath.mitosis import bootstrap_workspace, mitosis_maintenance, MitosisState
graph = Graph()
state = MitosisState()
bootstrap_workspace(graph, workspace_files, llm_call, state)
# Run periodically
mitosis_maintenance(graph, llm_call, state)
What Emerges
Five phenomena observed in production:
- Procedural Memory — multi-hop reflex chains (muscle memory for common workflows)
- Domain Separation with Bridges — knowledge clusters + cross-domain connections
- Selective Forgetting — 95% of sibling edges decay to dormant (the graph learns what NOT to load)
- Self-Regulation — autotuner homeostasis prevents drift
- Individuation — same files, different graph shapes based on usage patterns
Getting Started
Three commands, ~3 seconds total:
# 1. Install
pip install crabpath
# 2. Bootstrap graph + embeddings + replay session history
crabpath init --workspace ~/.openclaw/workspace \
--sessions ~/.openclaw/agents/main/sessions/
# 3. Wire into your agent (writes integration block to AGENTS.md)
crabpath install-hook --agent-workspace ~/.openclaw/workspace
That's it. Your agent now has a memory graph at ~/.crabpath/graph.json with semantic embeddings.
# Query the graph
crabpath query "how do I deploy" --graph ~/.crabpath/graph.json \
--index ~/.crabpath/embed.json --json
# Check health
crabpath health --graph ~/.crabpath/graph.json \
--query-stats ~/.crabpath/graph.stats.json
# Track evolution over time
crabpath evolve --graph ~/.crabpath/graph.json \
--snapshots ~/.crabpath/evolution.jsonl --report
Kill switch: pip uninstall crabpath && rm -rf ~/.crabpath — all state is in that one directory.
Without an embedding provider: crabpath init --no-embeddings falls back to keyword routing (less accurate, but works with zero API keys).
Reproducibility
See REPRODUCIBILITY.md for a complete mapping of every paper claim to a script, seed, and expected output.
# Run all benchmarks
cd experiments && python run_all.py
# Run ablation study (seed=2026, 10K bootstrap resamples)
python scripts/ablation_study.py
# Run phase transition diagnostic
python scripts/phase_transition_plot.py
Requirements
- Python 3.10+ (macOS ships 3.9 —
brew install python@3.12if needed) - Zero dependencies (stdlib only)
- Embeddings:
OPENAI_API_KEY,GEMINI_API_KEY, or local Ollama (see Install above) - LLM routing (optional): GPT-5-mini recommended — the router emits tiny JSON decisions, so use the cheapest model that follows instructions. This is the default in
RouterConfig. Without an LLM, CrabPath uses weight-only heuristic routing.
The Name
Carcinisation: nature keeps independently evolving crabs. Agent memory keeps converging on index + similarity retrieval. CrabPath evolves past that local optimum.
License
Apache 2.0
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file crabpath-1.0.7.tar.gz.
File metadata
- Download URL: crabpath-1.0.7.tar.gz
- Upload date:
- Size: 2.9 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3ddd95127e1884c0cf69eba3aa7e26f30edd494cf547b8b15f9c8b6db17f7520
|
|
| MD5 |
55f149af03e333823efb34925e2e44bb
|
|
| BLAKE2b-256 |
58f877983128e6032c18614a2763a667fdee8e1b4e90f471a08527f9ef3ef134
|
Provenance
The following attestation bundles were made for crabpath-1.0.7.tar.gz:
Publisher:
publish.yml on jonathangu/crabpath
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
crabpath-1.0.7.tar.gz -
Subject digest:
3ddd95127e1884c0cf69eba3aa7e26f30edd494cf547b8b15f9c8b6db17f7520 - Sigstore transparency entry: 997588961
- Sigstore integration time:
-
Permalink:
jonathangu/crabpath@3df044885b2bb25354fcd0b3cc00f0eb66694001 -
Branch / Tag:
refs/tags/v1.0.7 - Owner: https://github.com/jonathangu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3df044885b2bb25354fcd0b3cc00f0eb66694001 -
Trigger Event:
push
-
Statement type:
File details
Details for the file crabpath-1.0.7-py3-none-any.whl.
File metadata
- Download URL: crabpath-1.0.7-py3-none-any.whl
- Upload date:
- Size: 109.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4b741b3646ab0d0a7d260ba6f46b213a5be88037fd39c1e3ba1d2af177f0989e
|
|
| MD5 |
14ee012b96d5f63e9b9a6c64318c09c3
|
|
| BLAKE2b-256 |
263d17c9b0f562a313439e9335889c212201a4cb01936226353a8ec0cf450086
|
Provenance
The following attestation bundles were made for crabpath-1.0.7-py3-none-any.whl:
Publisher:
publish.yml on jonathangu/crabpath
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
crabpath-1.0.7-py3-none-any.whl -
Subject digest:
4b741b3646ab0d0a7d260ba6f46b213a5be88037fd39c1e3ba1d2af177f0989e - Sigstore transparency entry: 997589011
- Sigstore integration time:
-
Permalink:
jonathangu/crabpath@3df044885b2bb25354fcd0b3cc00f0eb66694001 -
Branch / Tag:
refs/tags/v1.0.7 - Owner: https://github.com/jonathangu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3df044885b2bb25354fcd0b3cc00f0eb66694001 -
Trigger Event:
push
-
Statement type: