Tools
Memory Rebac
OpenClaw two-layer memory plugin: SpiceDB ReBAC authorization + Graphiti knowledge graph
Install
npm install @contextableai/openclaw-memory-rebac
Configuration Example
{
"spicedb": {
"token": "${SPICEDB_TOKEN}"
},
"subjectId": "${OPENCLAW_AGENT_ID}"
}
README
# @contextableai/openclaw-memory-rebac
Two-layer memory plugin for OpenClaw: **SpiceDB** for authorization, **pluggable backends** for knowledge storage.
Agents remember conversations as structured knowledge. SpiceDB enforces who can read and write which memories โ authorization lives at the data layer, not in prompts. The backend is swappable: start with Graphiti's knowledge graph today, add new storage engines tomorrow.
## Architecture
```
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ OpenClaw Agent โ
โ โ
โ memory_recall โโโบ SpiceDB โโโบ Backend Search โ
โ memory_store โโโบ SpiceDB โโโบ Backend Write โ
โ memory_forget โโโบ SpiceDB โโโบ Backend Delete โ
โ auto-recall โโโบ SpiceDB โโโบ Backend Search โ
โ auto-capture โโโบ SpiceDB โโโบ Backend Write โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โโโโโโผโโโโโ โโโโโโโผโโโโโโ
โ SpiceDB โ โ Backend โ
โ (authz) โ โ (storage) โ
โโโโโโโโโโโ โโโโโโโโโโโโโ
```
**SpiceDB** determines which `group_id`s a subject (agent or person) can access. The **backend** stores and searches memories scoped to those groups. Authorization is enforced before any read or write reaches the backend.
### Why Two Layers?
Most memory systems bundle authorization with storage โ you get dataset isolation, but it's tied to the storage engine's auth model. That creates conflicts when you need external authorization (like SpiceDB) or want to swap backends without re-implementing access control.
openclaw-memory-rebac separates these concerns:
- **SpiceDB** owns the authorization model (relationships, permissions, consistency)
- **Backends** own the storage model (indexing, search, extraction)
- The plugin orchestrates both โ authorization check first, then backend operation
This means you can change your storage engine without touching authorization, and vice versa.
## Backends
### Graphiti (default)
[Graphiti](https://github.com/getzep/graphiti) builds a knowledge graph from conversations. It extracts entities, facts, and relationships, storing them in Neo4j for structured retrieval.
- **Storage**: Neo4j graph database
- **Transport**: Direct REST API to Graphiti FastAPI server
- **Extraction**: LLM-powered entity and relationship extraction (~300 embedding calls per episode)
- **Search**: Dual-mode โ searches both nodes (entities) and facts (relationships) in parallel
- **Docker image**: Custom image (`docker/graphiti/`) with per-component LLM/embedder/reranker configuration, BGE reranker support, and runtime patches for local-model compatibility
- **Best for**: Rich entity-relationship extraction, structured knowledge
## Installation
```bash
openclaw plugins install @contextableai/openclaw-memory-rebac
```
Or with npm:
```bash
npm install @contextableai/openclaw-memory-rebac
```
Then restart the gateway. On first start, the plugin automatically:
- Writes the SpiceDB authorization schema (if not already present)
- Creates group membership for the configured agent in the default group
### Prerequisites
- [Docker](https://docs.docker.com/get-docker/) and Docker Compose
- A running LLM endpoint (Graphiti uses an LLM for entity extraction and embeddings)
### 1. Start Infrastructure
```bash
cd docker
cp graphiti/.env.example graphiti/.env
# Edit graphiti/.env โ set your LLM endpoint and API key
docker compose up -d
```
This starts the full stack:
- **Neo4j** on port 7687 (graph database, browser on port 7474)
- **Graphiti** on port 8000 (FastAPI REST server)
- **PostgreSQL** on port 5432 (persistent datastore for SpiceDB)
- **SpiceDB** on port 50051 (authorization engine)
### 2. Restart the Gateway
```bash
openclaw gateway restart
```
The plugin auto-initializes on startup โ no manual `schema-write` or `add-member` needed for basic use.
### 3. (Optional) Add More Group Members
```bash
rebac-mem add-member family mom --type person
rebac-mem add-member family dad --type person
```
## Tools
The plugin registers four tools available to the agent:
### memory_recall
Search memories across all authorized groups. Returns entities and facts the current subject is permitted to see.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `query` | string | *required* | Search query |
| `limit` | number | 10 | Max results |
| `scope` | string | `"all"` | `"session"`, `"long-term"`, or `"all"` |
Searches both nodes and facts across all authorized groups in parallel, then deduplicates and ranks by recency.
### memory_store
Save information to the backend. The storage engine handles extraction and indexing.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `content` | string | *required* | Information to remember |
| `source_description` | string | `"conversation"` | Context about the source |
| `involves` | string[] | `[]` | Person/agent IDs involved |
| `group_id` | string | configured default | Target group for this memory |
| `longTerm` | boolean | `true` | `false` stores to the current session group |
Write authorization is enforced before storing:
- **Own session groups** auto-create membership (the agent gets exclusive access)
- **All other groups** require `contribute` permission in SpiceDB
### memory_forget
Delete a memory fragment. Requires `delete` permission (only the subject who stored the memory can delete it).
| Parameter | Type | Description |
|-----------|------|-------------|
| `episode_id` | string | Fragment UUID to delete |
### memory_status
Check the health of the backend and SpiceDB services. No parameters.
## Automatic Behaviors
### Auto-Recall
When enabled (default: `true`), the plugin searches relevant memories before each agent turn and injects them into the conversation context as `<relevant-memories>` blocks.
- Searches up to 5 long-term memories and 3 session memories per turn
- Deduplicates session results against long-term results
- Only triggers when the user prompt is at least 5 characters
### Auto-Capture
When enabled (default: `true`), the plugin captures the last N messages from each completed agent turn and stores them as a batch episode.
- Captures up to `maxCaptureMessages` messages (default: 10)
- Stores to the current session group by default
- Skips messages shorter than 5 characters and injected context blocks
- Uses custom extraction instructions for entity/fact extraction
## Authorization Model
The SpiceDB schema defines four object types:
```
definition person {}
definition agent {
relation owner: person
permission act_as = owner
}
definition group {
relation member: person | agent
permission access = member
permission contribute = member
}
definition memory_fragment {
relation source_group: group
relation involves: person | agent
relation shared_by: person | agent
permission view = involves + shared_by + source_group->access
permission delete = shared_by
}
```
### Groups
Groups organize memories and control access. A subject must be a **member** of a group to read (`access`) or write (`contribute`) to it.
Membership is managed via the CLI (`rebac-mem add-member`) or programmatically via `ensureGroupMembership()`.
### Memory Fragments
Each stored memory creates a `memory_fragment` with three relationships:
- **source_group** โ which group the memory belongs to
- **shared_by** โ who stored the memory (can delete it)
- **involves** โ people/agents mentioned in the memory (can view it)
View permission is granted to anyone who is directly involved, shared the memory, or has access to the source group. Delete permission is restricted to the subject who shared (stored) the memory.
### Session Groups
Session groups (`session-<id>`) provide per-conversation memory isolation:
- The agent that creates a session automatically gets exclusive membership
- Other agents cannot read or write to foreign session groups without explicit membership
- Session memories are searchable within the session scope and are deduplicated against long-term memories
## Configuration Reference
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `backend` | string | `graphiti` | Storage backend (`graphiti`) |
| `spicedb.endpoint` | string | `localhost:50051` | SpiceDB gRPC endpoint |
| `spicedb.token` | string | *required* | SpiceDB pre-shared key (supports `${ENV_VAR}`) |
| `spicedb.insecure` | boolean | `true` | Allow insecure gRPC (for localhost dev) |
| `graphiti.endpoint` | string | `http://localhost:8000` | Graphiti REST server URL |
| `graphiti.defaultGroupId` | string | `main` | Default group for memory storage |
| `graphiti.uuidPollIntervalMs` | integer | `3000` | Polling interval for resolving episode UUIDs (ms) |
| `graphiti.uuidPollMaxAttempts` | integer | `60` | Max polling attempts (total timeout = interval x attempts) |
| `graphiti.requestTimeoutMs` | integer | `30000` | HTTP request timeout for Graphiti REST calls (ms) |
| `subjectType` | string | `agent` | SpiceDB subject type (`agent` or `person`) |
| `subjectId` | string | `default` | SpiceDB subject ID (supports `${ENV_VAR}`) |
| `autoCapture` | boolean | `true` | Auto-capture conversations |
| `autoRecall` | boolean | `true` | Auto-inject relevant memories |
| `customInstructions` | string | *(see below)* | Custom extraction instructions |
| `maxCaptureMessages` | integer | `10` | Max messages per auto-capture batch (1-50) |
### Default Custom Instructions
When not overridden, the plugin uses these extraction instructions:
```
Extract key facts about:
- Identity: names, roles, titles, contact info
- Preferences: likes, dislikes, preferred tools/methods
- Goals: objectives, plans, deadlines
- Relationships: connections between people, teams, organizations
- Decisions: choices made, reasoning,
... (truncated)
tools
Comments
Sign in to leave a comment