Overview
openclaw-graph replaces OpenClaw's flat workspace files (SOUL.md, MEMORY.md, TOOLS.md, AGENTS.md) with single-line stubs that resolve against an embedded graph database at session start.
| Before (flat files) | After (graph stubs) | Node type | |
|---|---|---|---|
| SOUL.md | ~1,800 bytes of markdown | 144 bytes — one GRAPH directive | Soul |
| MEMORY.md | ~5,000+ bytes | 130 bytes | Memory |
| USER.md | ~800+ bytes | 142 bytes | Memory (prefix: User:) |
| TOOLS.md | ~2,000 bytes | 112 bytes | Tool |
| AGENTS.md | ~500 bytes | 127 bytes | AgentConfig |
| Total | ~11,000+ bytes | ~655 bytes |
Content is stored as nodes in Neo4j. Stubs query the graph database at runtime and inject the resolved content into your agent's system prompt — exactly as if you'd written it by hand.
How It Works
OpenClaw Session Start 1. Read ~/.openclaw/workspace/SOUL.md 2. Detect <!-- GRAPH: ... --> directive on first line 3. Execute Cypher query against Neo4j 4. Format result as markdown 5. Inject into system prompt (as if SOUL.md was flat) Same for MEMORY.md, AGENTS.md, TOOLS.md
From the agent's perspective, nothing changes — it sees the same markdown it always did. The difference is that the content now lives in a queryable graph database instead of hand-maintained files.
Workspace Files
After deploying stubs, your workspace directory looks like this:
~/.openclaw/workspace/ ├── SOUL.md → resolves Soul nodes (identity, prime directive, safety) ├── MEMORY.md → resolves Memory nodes (project facts, infrastructure) ├── USER.md → resolves Memory nodes where domain starts with 'User:' ├── AGENTS.md → resolves AgentConfig nodes (session behavior, delegation) └── TOOLS.md → resolves Tool nodes (available tools and usage notes)
Each file is a single line:
SOUL.md
<!-- GRAPH: MATCH (s:Soul) WHERE s.workspace = 'openclaw' RETURN s.section AS section, s.content AS content ORDER BY s.priority ASC LIMIT 8 -->
MEMORY.md
<!-- GRAPH: MATCH (m:Memory) WHERE m.workspace = 'openclaw' RETURN m.domain AS domain, m.content AS content ORDER BY m.domain -->
USER.md
User context lives as Memory nodes with a User: domain prefix — importable from your existing USER.md via import-workspace.mjs.
<!-- GRAPH: MATCH (m:Memory) WHERE m.workspace = 'openclaw' AND m.domain STARTS WITH 'User:' RETURN m.domain AS domain, m.content AS content ORDER BY m.domain -->
AGENTS.md
<!-- GRAPH: MATCH (a:AgentConfig) WHERE a.workspace = 'openclaw' RETURN a.key AS section, a.value AS content ORDER BY a.id -->
TOOLS.md
<!-- GRAPH: MATCH (t:Tool) WHERE t.available = true RETURN t.name AS name, t.notes AS notes ORDER BY t.name -->
Node Types — Soul SOUL
Purpose: Defines your agent's identity, core directives, and behavioral boundaries.
Schema
| Field | Type | Description |
|---|---|---|
id | STRING | Unique identifier (e.g., soul-openclaw-identity) |
workspace | STRING | Workspace this node belongs to (default: openclaw) |
section | STRING | Section heading (e.g., "Prime Directive", "Identity") |
content | STRING | Markdown content for this section |
priority | INT64 | Sort order — lower numbers load first (default: 5) |
protected | BOOLEAN | If true, cannot be overwritten by --reset (default: false) |
Default nodes (4)
| Priority | Section | Purpose |
|---|---|---|
| 1 | Prime Directive | Core mission statement — what the agent exists to do |
| 2 | Identity | Agent name, role, emoji, workspace ID |
| 3 | Safety | Behavioral guardrails and constraints |
| 4 | Heartbeat Protocol | Periodic self-check and health reporting format |
Resolved output format: ## Section\n\nContent (standard markdown headings)
Memory MEMORY
Purpose: Stores factual knowledge the agent needs across sessions — who it works for, what infrastructure exists, API keys (as env var references), project state.
Schema
| Field | Type | Description |
|---|---|---|
id | STRING | Unique identifier (e.g., mem-openclaw-principal) |
workspace | STRING | Workspace this node belongs to (default: openclaw) |
domain | STRING | Category label (e.g., "Principal", "Infrastructure") |
content | STRING | Factual content for this domain |
timestamp | STRING | When this memory was last updated (ISO format) |
Default nodes (2)
| Domain | Purpose |
|---|---|
| Principal | Who the agent serves — name, timezone, preferred contact channel |
| Infrastructure | Environment facts — DB paths, Node.js location, workspace directory |
Both ship as placeholders — you fill in your own details. See Customizing below.
Resolved output format: ## Domain\n\nContent
AgentConfig CONFIG
Purpose: Controls agent session behavior — how it delegates tasks, handles errors, compresses output, validates schemas, and resolves paths.
Schema
| Field | Type | Description |
|---|---|---|
id | STRING | Unique identifier (e.g., agentcfg-openclaw-delegation) |
workspace | STRING | Workspace this node belongs to (default: openclaw) |
key | STRING | Configuration key (displayed as section heading) |
value | STRING | Configuration value (displayed as content) |
Default nodes (9)
| Key | Purpose |
|---|---|
| Every Session | Bootstrap instructions — what to read on every session start |
| Delegation | Rules for spawning sub-agents (when to delegate, how many) |
| Safety | Session-level safety behaviors (confirmation thresholds, etc.) |
| Heartbeats | Periodic health check format and interval |
| Memory | How to persist and retrieve memories across sessions |
| Token Optimization — TOON | JSON compression rules for large payloads (30-60% token savings) |
| Search Resilience | Retry and fallback strategies when web_search fails |
| Schema Rules | Output validation requirements for structured data |
| Path Aliases | Compact path notation ($PY, $DB, $WS, etc.) for prompt efficiency |
Resolved output format: - **Key**: Value (markdown list items)
Session loading: In cron/sub-agent sessions, OpenClaw's MINIMAL_BOOTSTRAP_ALLOWLIST loads only AGENTS.md + TOOLS.md. SOUL.md and MEMORY.md are skipped to reduce prompt size.
Tool TOOL
Purpose: Declares which tools the agent can use, with usage notes.
Schema
| Field | Type | Description |
|---|---|---|
id | STRING | Unique identifier (e.g., tool-web-search) |
name | STRING | Tool name as the agent sees it |
available | BOOLEAN | Whether the tool is currently enabled (default: true) |
notes | STRING | Usage notes, restrictions, or tips |
Default nodes: 21 tools covering all standard OpenClaw capabilities.
Set available = false to disable a tool for a specific agent (e.g., disable sessions_spawn for a standalone agent that shouldn't delegate).
Resolved output format: - **Name**: Notes (markdown list items)
Skill
Purpose: The skill graph — 316 skills across 27 clusters representing capabilities an agent can be asked to perform.
Schema
| Field | Type | Description |
|---|---|---|
id | STRING | Unique identifier (e.g., skill-kubernetes-ops) |
name | STRING | Human-readable skill name |
cluster | STRING | Skill cluster (e.g., devops-sre, financial, python) |
description | STRING | What this skill does |
tags | STRING[] | Searchable tags |
authorization_required | BOOLEAN | Whether this skill needs explicit user approval |
scope | STRING | Scope category (default: general) |
model_hint | STRING | Suggested model for this skill |
embedding_hint | STRING | Semantic search hint text |
skill_path | STRING | Path to skill definition file (if installed) |
clawhub_install | STRING | ClawHub install command (if available) |
Skills are queried via Cypher — they're not loaded into workspace stubs:
# Find skills by cluster cypher-shell "MATCH (s:Skill)-[:IN_CLUSTER]->(c:SkillCluster {name:'devops-sre'}) RETURN s.name, s.description" # Traverse relationships (2 hops) cypher-shell "MATCH (s:Skill {name:'cloudflare'})-[:RELATED_TO*1..2]-(t:Skill) RETURN DISTINCT t.name, t.cluster"
GRAPH Directive Specification
Format & Rules
<!-- GRAPH: <cypher-query> -->
- Must be the first line of the workspace file
- Must be a single line — no multi-line Cypher
- Must start with
<!-- GRAPH:and end with--> - The Cypher query is extracted and executed against Neo4j via the
neo4jdriver
Return column conventions
The output format is determined by the column names returned by your Cypher query:
| Return columns | Output format | Used by |
|---|---|---|
section + content | ## Section\n\nContent | SOUL.md |
domain + content | ## Domain\n\nContent | MEMORY.md |
key + value | - **Key**: Value | AGENTS.md |
name + notes | - **Name**: Notes | TOOLS.md |
| Other columns | JSON (debug/dev use) | Custom queries |
Caching
Resolved results are cached with a three-layer strategy:
- Disk stub cache —
workspaceFileCachekeyed by file mtime (if file hasn't changed, skip re-read) - Graph query cache —
graphQueryCachewith adaptive TTL:60s × log₁₀(hit_count + 10) - In-flight deduplication —
graphQueryInFlightprevents thundering herd when multiple files resolve simultaneously
In practice, the Cypher query is executed at most once per 60 seconds per unique query. Subsequent reads return cached markdown instantly.
Workspace Loading Flow
Interactive sessions
Session Start ├── Load SOUL.md → GRAPH directive → Soul nodes → system prompt ├── Load MEMORY.md → GRAPH directive → Memory nodes → system prompt ├── Load AGENTS.md → GRAPH directive → AgentConfig nodes → system prompt └── Load TOOLS.md → GRAPH directive → Tool nodes → system prompt
All four files are loaded. The agent receives the full context.
Cron / sub-agent sessions
Session Start (minimal bootstrap) ├── Load AGENTS.md → GRAPH directive → AgentConfig nodes → system prompt └── Load TOOLS.md → GRAPH directive → Tool nodes → system prompt
Only AGENTS.md and TOOLS.md are loaded (MINIMAL_BOOTSTRAP_ALLOWLIST). This reduces prompt size for automated tasks that don't need identity or memory context.
Customizing Your Workspace
Option A — Edit nodes with Cypher
Best for quick changes to individual nodes.
# Update your agent's identity cypher-shell "MATCH (s:Soul {id:'soul-openclaw-identity'}) SET s.content = 'Name: Aria | Role: DevOps agent | Emoji: 🛡️'" # Fill in the Principal memory (who you are) cypher-shell "MATCH (m:OCMemory {id:'mem-openclaw-principal'}) SET m.content = 'Name: Alice | Timezone: America/Chicago | Channel: signal'" # Fill in Infrastructure memory cypher-shell "MATCH (m:OCMemory {id:'mem-openclaw-infrastructure'}) SET m.content = 'Neo4j: bolt://localhost:7687 | Python: /opt/anaconda3/bin/python3'" # Add a new memory domain cypher-shell "CREATE (m:OCMemory { id: 'mem-openclaw-api-keys', workspace: 'openclaw', domain: 'API Keys', content: 'Brave Search: \$BRAVE_API_KEY | xAI: \$XAI_API_KEY', timestamp: '2026-01-01' })"
Option B — Re-run seed with customizations
Best for reproducible, version-controlled workspace definitions.
# Edit the seed script's workspace defaults, then re-run
python3 seed.py --workspace myagentThen update your stubs to point to your workspace:
# In each stub, change workspace = 'openclaw' to workspace = 'myagent' sed -i '' "s/workspace = 'openclaw'/workspace = 'myagent'/g" ~/.openclaw/workspace/*.md
Option C — Multiple workspaces
For running several agents with different personalities or capabilities. See Fleet Management in the Admin Guide.
Keeping customizations across upgrades
The seed script uses MERGE (not CREATE) — it is safe to re-run without losing customizations. Just pull the latest version and re-run:
cd openclaw-graph && git pull python3 seed.py
See Upgrading in the Admin Guide for the full workflow.
Default Node IDs
| ID | Type | Section / Domain / Key |
|---|---|---|
soul-openclaw-prime-directive | Soul | Prime Directive |
soul-openclaw-identity | Soul | Identity |
soul-openclaw-safety | Soul | Safety |
soul-openclaw-heartbeat | Soul | Heartbeat Protocol |
mem-openclaw-principal | Memory | Principal |
mem-openclaw-infrastructure | Memory | Infrastructure |
agentcfg-openclaw-every-session | Config | Every Session |
agentcfg-openclaw-delegation | Config | Delegation |
agentcfg-openclaw-safety | Config | Safety |
agentcfg-openclaw-heartbeats | Config | Heartbeats |
agentcfg-openclaw-memory | Config | Memory |
agentcfg-openclaw-toon | Config | Token Optimization — TOON |
agentcfg-openclaw-search-resilience | Config | Search Resilience |
agentcfg-openclaw-schema-rules | Config | Schema Rules |
agentcfg-openclaw-path-aliases | Config | Path Aliases |
Plus 21 Tool nodes (tool-web-search, tool-sessions-spawn, etc.).
Import Your Existing Workspace
Already running OpenClaw with flat SOUL.md, MEMORY.md, USER.md, TOOLS.md, AGENTS.md files? The seed script loads them all into Neo4j:
# From your openclaw-graph directory python3 seed.py
The seed script creates all workspace nodes in Neo4j. Deploy GRAPH stubs to replace your flat files:
Options
# Preview what will be imported — no DB writes python3 seed.py --dry-run # Deploy GRAPH stubs (backs up originals to *.bak) cp workspace-stubs/*.md ~/.openclaw/workspace/
File → Node mapping
| File | Node type | How sections map |
|---|---|---|
SOUL.md | Soul | ## Heading → section field, body → content |
MEMORY.md | Memory | ## Heading → domain field, body → content |
USER.md | Memory | ## Heading → User: <Heading> domain — queryable separately |
TOOLS.md | Tool | ## Heading → name field, body → notes |
AGENTS.md | AgentConfig | ## Heading → key field, body → value |
Format tip: Structure your markdown files with ## headings — each heading becomes one graph node. Files with no ## headings are imported as a single node titled Main.
FAQ
Do I need to change my prompts or workflows?
No. The agent sees the same markdown content it always did. The graph database is transparent — it's a storage and delivery mechanism, not a new interface.
What happens if Neo4j is down?
The GRAPH directive returns empty content. Your agent will start with blank workspace sections. Start Neo4j and verify with cypher-shell "RETURN 1".
How do I import my existing flat workspace files?
Run python3 seed.py from your openclaw-graph directory. It creates all workspace nodes in Neo4j. Then deploy GRAPH stubs with cp workspace-stubs/*.md ~/.openclaw/workspace/. See the Import Your Files section above.
Can I mix flat files and graph stubs?
Yes. Any workspace file without a <!-- GRAPH: ... --> directive on line 1 is loaded as a normal flat file. You can migrate one file at a time.
How do I see what my agent actually receives?
Run the same Cypher query the stub uses:
# See exactly what SOUL.md resolves to cypher-shell "MATCH (s:Soul) WHERE s.workspace = 'openclaw' RETURN s.section AS section, s.content AS content ORDER BY s.priority" # See what AGENTS.md resolves to cypher-shell "MATCH (a:AgentConfig) WHERE a.workspace = 'openclaw' RETURN a.key AS section, a.value AS content ORDER BY a.id"
What's the performance impact?
Negligible. All Neo4j queries are sub-millisecond (<1ms for typical workspace queries, ~2ms for full skill scan). See Performance Reference in the Admin Guide.
Can I query the skill graph from my agent's prompts?
Yes. Agents can run Cypher queries as part of their task execution:
cypher-shell "MATCH (s:Skill)-[:IN_CLUSTER]->(c:SkillCluster {name:'devops-sre'})
RETURN s.name, s.description"This is how cron jobs and automated workflows discover relevant skills at runtime.