Graph-native workspace backend for OpenClaw — powered by Neo4j
Replace flat workspace markdown files with Cypher graph directives. 316 skills across 27 clusters · 217 skill relationships · proper graph modeling with SkillCluster nodes · namespaced labels for multi-graph coexistence.
Rust event-driven sync daemon. The workspace materializer is now a long-running Rust binary with FSEvents file watching — replacing the Python polling script. Sub-second write-back, persistent Bolt connection, 3.8 MB binary, ~4 MB RSS.
| v1.4 | v1.5 | |
|---|---|---|
| Sync daemon | Python polling (60s) | ✅ Rust event-driven (FSEvents) |
| Write-back latency | up to 60s | ✅ <500ms |
| Per-sync cost | ~555ms (Python import overhead) | ✅ ~5ms |
| Memory (RSS) | ~50 MB | ✅ ~4 MB |
| Neo4j connections/hour | 60 new | ✅ 1 persistent |
| Binary size | N/A (interpreter) | ✅ 3.8 MB (arm64) |
See tools/neo4j-sync/ for the full source.
Neo4j-native. OpenClaw Graph runs on Neo4j with proper graph modeling — SkillCluster nodes, typed relationships, namespaced labels, and workspace-scoped isolation.
Why Neo4j? Native graph storage with index-free adjacency. Sub-millisecond traversals, Cypher queries, proper relationship modeling. The 316 skills + 27 clusters + workspace nodes add only ~10 MB to a Neo4j instance.
OpenClaw agents load workspace context from flat markdown files (SOUL.md, MEMORY.md, TOOLS.md, AGENTS.md). As your workspace grows, these files balloon — injecting tens of thousands of tokens into every system prompt.
openclaw-graph replaces those files with single-line Cypher directives:
<!-- GRAPH: MATCH (s:Soul) WHERE s.workspace = 'myapp' RETURN s.section AS section, s.content AS content ORDER BY s.priority ASC -->When OpenClaw loads the workspace, the directive is resolved by querying Neo4j, formatted as structured markdown, and injected into the system prompt — exactly as if it were a flat file.
Result: Workspace stubs shrink from ~25,000 bytes to ~660 bytes. Sub-millisecond query times. Skill lookup across 316 nodes in ~2ms.
OpenClaw Session
│
├── loadWorkspaceBootstrapFiles()
│ ├── SOUL.md (1 line: <!-- GRAPH: MATCH (s:Soul)... -->)
│ ├── MEMORY.md (1 line: <!-- GRAPH: MATCH (m:OCMemory)... -->)
│ ├── USER.md (1 line: <!-- GRAPH: MATCH (m:OCMemory) WHERE m.domain STARTS WITH 'User:'... -->)
│ ├── TOOLS.md (1 line: <!-- GRAPH: MATCH (t:OCTool)... -->)
│ └── AGENTS.md (1 line: <!-- GRAPH: MATCH (a:AgentConfig)... -->)
│ │
│ └── readFileWithCache()
│ ├── detect GRAPH directive
│ ├── execute Cypher query against Neo4j
│ ├── cache result 60s (in-process Map)
│ └── return formatted markdown
│
└── buildAgentSystemPrompt()
└── injects graph-resolved markdown → system prompt
Neo4j (bolt://localhost:7687)
├── Skill 316 nodes · 27 SkillCluster nodes · 217 RELATED_TO edges
├── SkillCluster proper graph nodes with IN_CLUSTER relationships
├── Soul identity, persona, capabilities per workspace
├── OCMemory project context, infrastructure facts (namespaced)
├── OCTool available tools + usage notes (namespaced)
├── OCAgent agent definitions (namespaced)
├── AgentConfig per-workspace agent behavior overrides
└── Bootstrap boot identity
Install these before running seed.py:
| Dependency | Version | macOS | Ubuntu / Debian | Fedora |
|---|---|---|---|---|
| Neo4j | 2026.01+ | brew install neo4j |
See below | See below |
| Python | 3.10+ | pre-installed or brew install python |
sudo apt install python3 |
sudo dnf install python3 |
| neo4j driver | 5.x | pip install neo4j |
pip install neo4j |
pip install neo4j |
macOS — step by step
# 1. Install Homebrew (skip if already installed)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# 2. Install Neo4j
brew install neo4j
brew services start neo4j
# 3. Install Python neo4j driver
pip install neo4jUbuntu / Debian — step by step
# 1. Add Neo4j repository and install
wget -O - https://debian.neo4j.com/neotechnology.gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/neo4j.gpg
echo "deb [signed-by=/usr/share/keyrings/neo4j.gpg] https://debian.neo4j.com stable latest" | sudo tee /etc/apt/sources.list.d/neo4j.list
sudo apt-get update && sudo apt-get install -y neo4j
# 2. Start Neo4j
sudo systemctl start neo4j
# 3. Install Python neo4j driver
pip install neo4jFedora — step by step
# 1. Add Neo4j repository and install
sudo rpm --import https://debian.neo4j.com/neotechnology.gpg.key
cat << EOF | sudo tee /etc/yum.repos.d/neo4j.repo
[neo4j]
name=Neo4j RPM Repository
baseurl=https://yum.neo4j.com/stable/5
gpgcheck=1
gpgkey=https://debian.neo4j.com/neotechnology.gpg.key
EOF
sudo dnf install -y neo4j
# 2. Start Neo4j
sudo systemctl start neo4j
# 3. Install Python neo4j driver
pip install neo4jgit clone https://github.com/alphaonedev/openclaw-graph.git
cd openclaw-graph# One command — seeds Neo4j, deploys workspace stubs, verifies
./install.sh
# Or step by step:
python3 seed.py --dry-run # Preview (no writes)
python3 seed.py # Seed Neo4j (skills + workspace)
cp workspace-stubs/*.md ~/.openclaw/workspace/cd path/to/openclaw
git apply path/to/openclaw-graph/patches/workspace-cache-fix.patch
pnpm buildNote: If you use the Rust sync daemon (
neo4j-sync), it materializes flat files directly — no OpenClaw patch needed.
# Update your identity via Cypher
cypher-shell -a bolt://localhost:7687 --format plain \
"MATCH (s:Soul {id:'soul-openclaw-identity'}) \
SET s.content = 'Name: MyAgent | Role: Expert Engineer | Workspace: openclaw'"
# See CUSTOMIZING.md for the full guide| Label | Count | Description |
|---|---|---|
Skill |
316 | Skills with full SKILL.md content |
SkillCluster |
27 | Clusters as proper graph nodes |
Soul |
4 | Agent personality/behavior |
OCMemory |
2 | Workspace memory (prefixed to avoid collisions) |
AgentConfig |
9 | Runtime config directives |
OCAgent |
8 | Agent fleet definitions (prefixed — Agent may exist) |
OCTool |
26 | Tool definitions (prefixed for namespacing) |
Bootstrap |
1 | Boot identity |
Relationships: (:Skill)-[:IN_CLUSTER]->(:SkillCluster) (316) and (:Skill)-[:RELATED_TO]->(:Skill) (217).
All nodes get a workspace property for multi-tenant isolation.
Namespaced labels (OCAgent, OCMemory, OCTool) let OpenClaw Graph nodes coexist safely with other graph domains in the same Neo4j instance. If your Neo4j already hosts other workloads (knowledge graphs, analytics, etc.), OpenClaw Graph's namespaced labels and workspace-scoped queries ensure zero interference — no label collisions, no cross-domain leakage.
Add three blocks to src/agents/workspace.ts:
1. After imports — add the graph backend:
import path from "node:path";
import os from "node:os";
import { execFileSync } from "node:child_process";
const GRAPH_DIRECTIVE_PREFIX = "<!-- GRAPH:";
const GRAPH_NODE_BIN = process.env.OPENCLAW_GRAPH_NODE_BIN ?? "node";
const GRAPH_QUERY_SCRIPT =
process.env.OPENCLAW_GRAPH_QUERY_SCRIPT ?? "";
function extractGraphDirective(content: string): string | null {
const trimmed = content.trim();
if (!trimmed.startsWith(GRAPH_DIRECTIVE_PREFIX)) return null;
const end = trimmed.indexOf("-->");
if (end === -1) return null;
return trimmed.slice(GRAPH_DIRECTIVE_PREFIX.length, end).trim();
}
function executeGraphQuery(cypher: string): string {
try {
const result = execFileSync(
GRAPH_NODE_BIN,
[GRAPH_QUERY_SCRIPT, "--workspace", "--cypher", cypher],
{ encoding: "utf-8", timeout: 8000 },
);
return result.trim();
} catch { return ""; }
}
const graphQueryCache = new Map<string, { content: string; fetchedAt: number }>();
function resolveGraphContent(cypher: string): string {
const now = Date.now();
const cached = graphQueryCache.get(cypher);
if (cached && now - cached.fetchedAt < 60_000) return cached.content;
const content = executeGraphQuery(cypher);
graphQueryCache.set(cypher, { content, fetchedAt: now });
return content;
}2. Inside readFileWithCache() — add directive resolution after reading raw file:
const raw = await fs.readFile(filePath, "utf-8");
// ── Graph directive resolution ──────────────────────────────────
const cypher = extractGraphDirective(raw);
const content = cypher ? resolveGraphContent(cypher) : raw;
// ───────────────────────────────────────────────────────────────
workspaceFileCache.set(filePath, { content, mtimeMs });
return content;3. Set environment variables (optional, for custom paths):
export OPENCLAW_GRAPH_NODE_BIN=/usr/local/bin/node
export OPENCLAW_GRAPH_QUERY_SCRIPT=/path/to/openclaw-graph/scripts/query.js316 skills across 27 clusters, covering the full OpenClaw development surface:
| Cluster | Skills | Description |
|---|---|---|
| community | 62 | ClaWHub community skills |
| coding | 18 | Languages, code review, refactoring |
| mobile | 14 | iOS, Android, React Native, Flutter |
| web-dev | 13 | HTML/CSS/JS, frameworks, APIs |
| se-architecture | 12 | System design, microservices, DDD |
| computer-science | 11 | Algorithms, data structures, theory |
| cloud-gcp | 10 | Google Cloud Platform |
| devops-sre | 10 | CI/CD, Kubernetes, monitoring |
| aimlops | 10 | ML pipelines, model serving, MLflow |
| game-dev | 10 | Unity, Unreal, game architecture |
| financial | 10 | Trading, options, quant analysis |
| data-engineering | 10 | Pipelines, Spark, dbt, warehouses |
| blue-team | 10 | Threat detection, SIEM, incident response |
| ar-vr | 10 | ARKit, WebXR, spatial computing |
| twilio | 10 | SMS, Voice, Verify, Conversations |
| macos | 10 | macOS automation, Shortcuts, Swift |
| blockchain | 10 | Smart contracts, DeFi, Web3 |
| iot | 10 | Edge computing, MQTT, embedded |
| cloud-aws | 10 | AWS services, CDK, Lambda |
| core-openclaw | 8 | OpenClaw skills, memory, compaction |
| testing | 8 | Unit, integration, E2E, TDD |
| ai-apis | 8 | OpenAI, Anthropic, xAI, Google AI |
| existing | 6 | Existing OpenClaw built-in skills |
| abtesting | 6 | Experimentation, feature flags |
| linux | 5 | Shell, systemd, package management |
| distributed-comms | 4 | Kafka, RabbitMQ, event-driven |
# Cypher queries against Neo4j
python3 -c "
from neo4j import GraphDatabase
d = GraphDatabase.driver('bolt://localhost:7687')
with d.session() as s:
# Find skills by cluster
for r in s.run('MATCH (s:Skill)-[:IN_CLUSTER]->(c:SkillCluster {name: \"devops-sre\"}) RETURN s.name, s.description'):
print(f'{r[\"s.name\"]:30s} {r[\"s.description\"]}')
# Multi-hop skill discovery (2 hops)
for r in s.run('MATCH (s:Skill {name: \"terraform\"})-[:RELATED_TO*1..2]->(t:Skill) RETURN DISTINCT t.name, t.cluster'):
print(f'{r[\"t.name\"]:30s} {r[\"t.cluster\"]}')
d.close()
"A long-running Rust binary that materializes Neo4j graph data into OpenClaw workspace flat files and syncs agent writes back to Neo4j — with event-driven file watching for instant write-back.
┌──────────────────────────────┐
│ tokio::select! event loop │
│ (current_thread runtime) │
│ │
│ ticker (60s) ─── read sync │ Neo4j → .md files
│ FSEvents ─── write-back │ IDENTITY.md → Neo4j
│ SIGTERM ─── shutdown │
└──────────────────────────────┘
│
┌─────────┴─────────┐
│ │
[neo4rs Graph] [notify debouncer]
persistent macOS FSEvents
Bolt connection 500ms debounce
| Path | Direction | Trigger | Latency |
|---|---|---|---|
| Read | Neo4j → flat .md files | Periodic (60s) | ~5ms per cycle |
| Write-back | IDENTITY.md → Neo4j Soul node | FSEvents file change | <500ms |
tools/neo4j-sync/src/
├── main.rs — tokio event loop, signal handling
├── config.rs — paths, env vars, constants
├── db.rs — Neo4j connection (neo4rs), query execution
├── stub.rs — <!-- GRAPH: ... --> directive parsing
├── formatter.rs — query results → plain-text CSV
├── sync_read.rs — Neo4j → flat file materialization
├── sync_write.rs — IDENTITY.md → Neo4j write-back
├── state.rs — sync state persistence (JSON)
└── watcher.rs — FSEvents file watcher setup
| Crate | Purpose |
|---|---|
neo4rs 0.8 |
Async Neo4j Bolt driver |
notify 8 + notify-debouncer-mini 0.7 |
FSEvents file watching with debounce |
tokio 1 |
Async runtime (current_thread) |
serde + serde_json |
State serialization |
regex |
Cypher extraction + name parsing |
tracing |
Structured logging (RUST_LOG env filter) |
cd tools/neo4j-sync
cargo build --release
mkdir -p ~/.openclaw/bin
cp target/release/neo4j-sync ~/.openclaw/bin/neo4j-sync# Copy and edit the template
cp tools/neo4j-sync/launchd.plist.template \
~/Library/LaunchAgents/com.alphaone.neo4j-workspace-sync.plist
# Edit paths ($HOME → your home directory)
# Load
launchctl load ~/Library/LaunchAgents/com.alphaone.neo4j-workspace-sync.plist
# Verify
launchctl list | grep neo4j-workspace-sync
tail -10 ~/.openclaw/logs/neo4j-sync.stdout.logMeasured on Apple Silicon, Neo4j 2026.01, 8 workspace files across 2 workspaces.
| Metric | Python (v1.4) | Rust (v1.5) | Improvement |
|---|---|---|---|
| Per-sync wall time | 555ms | 5ms | 111x faster |
| Python import overhead | 550ms | 0ms (compiled) | eliminated |
| Write-back latency | up to 60s | <500ms | 120x faster |
| Memory (RSS) | ~50 MB | ~4 MB | 12x smaller |
| Binary size | N/A (interpreter) | 3.8 MB | — |
| Neo4j connections/hour | 60 new | 1 persistent | 60x fewer |
| CPU duty cycle | 0.9% | <0.01% | — |
Measured on production Neo4j 2026.01 — 316 skills, 27 clusters, 393 total nodes, Mac mini M-series.
| Query | avg |
|---|---|
| Skill lookup by ID | <1ms |
| Full skill scan (316 nodes) | ~2ms |
| Cluster traversal (IN_CLUSTER) | <1ms |
| Multi-hop skill reasoning (2 hops) | ~3ms |
| Workspace stubs (Soul/Memory/Config) | <1ms |
| Full seed (seed.py) | ~1s |
| Neo4j storage overhead | ~10 MB |
| Metric | Value |
|---|---|
| Workspace stub total size | ~655 bytes (5 files) |
| Flat-file workspace size | ~11,000–25,000+ bytes |
| Size reduction | 94–97% |
| Token savings per session | ~2,700–6,200 tokens |
See benchmarks/results.md for full measurements.
For teams running multiple OpenClaw instances (e.g., a fleet of AI agents), all instances share the same Neo4j. Differentiate instances by workspace identifier:
-- Instance A (intelligence agent)
MATCH (s:Soul) WHERE s.workspace = 'intel' RETURN ...
-- Instance B (code agent)
MATCH (s:Soul) WHERE s.workspace = 'code' RETURN ...
-- Instance C (infrastructure agent)
MATCH (s:Soul) WHERE s.workspace = 'infra' RETURN ...Each instance gets its own identity, memory, and tool configuration from the shared graph.
# macOS (Homebrew)
brew services start neo4j
brew services stop neo4j
brew services restart neo4j
# Ubuntu / Debian / Fedora (systemd)
sudo systemctl start neo4j
sudo systemctl stop neo4j
sudo systemctl restart neo4j
sudo systemctl status neo4j# macOS (launchd — ai.openclaw.gateway)
launchctl bootstrap gui/$UID ~/Library/LaunchAgents/ai.openclaw.gateway.plist
launchctl bootout gui/$UID/ai.openclaw.gateway
launchctl kickstart -k gui/$UID/ai.openclaw.gateway
# Ubuntu & Fedora (systemd)
systemctl --user start openclaw-gateway.service
systemctl --user stop openclaw-gateway.service
systemctl --user restart openclaw-gateway.servicepython3 seed.py --verify
# Or manually:
python3 -c "
from neo4j import GraphDatabase
d = GraphDatabase.driver('bolt://localhost:7687')
with d.session() as s:
for r in s.run('MATCH (n) WHERE n.workspace = \$ws RETURN labels(n)[0] AS type, count(n) AS cnt ORDER BY type', ws='openclaw'):
print(f'{r[\"type\"]:20s} {r[\"cnt\"]}')
d.close()
"Pull requests welcome. See CONTRIBUTING.md for guidelines.
To add skills: create a SKILL.md file in the appropriate cluster directory and run python3 seed.py to load into Neo4j.
Skill format:
---
name: my-skill
cluster: my-cluster
description: "What this skill does"
tags: ["tag1", "tag2"]
dependencies: []
composes: []
similar_to: []
called_by: []
authorization_required: false
scope: general
model_hint: claude-sonnet
embedding_hint: "keywords for text search"
---
# My Skill
## Purpose
What the agent can do with this skill.
## When to Use
- When the task involves X
- Match query: keywords
## Key Capabilities
- Capability 1
- Capability 2| Guide | Description |
|---|---|
| Admin Guide | Installation, database layout, CLI reference, fleet management, upgrading, troubleshooting |
| User Guide | Workspace stubs, GRAPH directives, node schemas (Soul/Memory/AgentConfig/Tool/Skill), loading flow |
| Rust Sync Daemon | Event-driven Neo4j workspace sync — Rust source, build instructions, launchd template |
| Customizing | Step-by-step workspace personalization — identity, memory, tools, multi-workspace setup |
| Benchmarks | Full performance measurements with methodology |
| GitHub Pages | Interactive documentation site |
MIT © 2025 openclaw-graph contributors
OpenClaw is © its respective authors. This project is an independent community contribution and is not affiliated with or endorsed by the OpenClaw project.