@contextableai/openclaw-memory-rebac 0.3.8 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -15
- package/dist/authorization.d.ts +20 -0
- package/dist/authorization.js +60 -0
- package/dist/backend.d.ts +18 -0
- package/dist/backends/evermemos.d.ts +84 -0
- package/dist/backends/evermemos.defaults.json +10 -0
- package/dist/backends/evermemos.js +404 -0
- package/dist/backends/registry.js +2 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +15 -1
- package/dist/index.js +142 -1
- package/docker/docker-compose.evermemos.yml +21 -0
- package/docker/{docker-compose.yml → docker-compose.graphiti.yml} +1 -1
- package/docker/evermemos/.env.example +85 -0
- package/docker/evermemos/Dockerfile +133 -0
- package/docker/evermemos/docker-compose.yml +52 -0
- package/docker/evermemos/trace_overlay.py +128 -0
- package/docker/spicedb/docker-compose.yml +4 -4
- package/package.json +6 -4
- package/schema.zed +6 -2
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# EverMemOS Configuration
|
|
3
|
+
# =============================================================================
|
|
4
|
+
#
|
|
5
|
+
# Copy this file to .env and fill in your API keys.
|
|
6
|
+
# Database connection settings are pre-configured for Docker — no changes needed.
|
|
7
|
+
#
|
|
8
|
+
# cp .env.example .env
|
|
9
|
+
#
|
|
10
|
+
# SECURITY: Never commit .env to version control.
|
|
11
|
+
# =============================================================================
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# ===================
|
|
15
|
+
# LLM Configuration (required)
|
|
16
|
+
# ===================
|
|
17
|
+
# Used for memory extraction (MemCell pipeline).
|
|
18
|
+
|
|
19
|
+
LLM_PROVIDER=openai
|
|
20
|
+
LLM_MODEL=gpt-4o-mini
|
|
21
|
+
LLM_BASE_URL=https://api.openai.com/v1
|
|
22
|
+
LLM_API_KEY=sk-your-key-here
|
|
23
|
+
LLM_TEMPERATURE=0.3
|
|
24
|
+
LLM_MAX_TOKENS=32768
|
|
25
|
+
|
|
26
|
+
# Alternative: OpenRouter (uncomment and set key)
|
|
27
|
+
# LLM_MODEL=x-ai/grok-4-fast
|
|
28
|
+
# LLM_BASE_URL=https://openrouter.ai/api/v1
|
|
29
|
+
# LLM_API_KEY=sk-or-v1-your-key-here
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ===================
|
|
33
|
+
# Vectorize / Embedding (required)
|
|
34
|
+
# ===================
|
|
35
|
+
# Used for semantic search. Supports vLLM (self-hosted) or DeepInfra (commercial).
|
|
36
|
+
|
|
37
|
+
VECTORIZE_PROVIDER=deepinfra
|
|
38
|
+
VECTORIZE_API_KEY=your-deepinfra-key-here
|
|
39
|
+
VECTORIZE_BASE_URL=https://api.deepinfra.com/v1/openai
|
|
40
|
+
VECTORIZE_MODEL=Qwen/Qwen3-Embedding-4B
|
|
41
|
+
VECTORIZE_DIMENSIONS=1024
|
|
42
|
+
VECTORIZE_TIMEOUT=30
|
|
43
|
+
VECTORIZE_MAX_RETRIES=3
|
|
44
|
+
VECTORIZE_BATCH_SIZE=10
|
|
45
|
+
VECTORIZE_MAX_CONCURRENT=5
|
|
46
|
+
VECTORIZE_ENCODING_FORMAT=float
|
|
47
|
+
|
|
48
|
+
# Alternative: self-hosted vLLM (uncomment)
|
|
49
|
+
# VECTORIZE_PROVIDER=vllm
|
|
50
|
+
# VECTORIZE_API_KEY=EMPTY
|
|
51
|
+
# VECTORIZE_BASE_URL=http://host.docker.internal:8000/v1
|
|
52
|
+
|
|
53
|
+
# Optional: fallback vectorize provider
|
|
54
|
+
# VECTORIZE_FALLBACK_PROVIDER=deepinfra
|
|
55
|
+
# VECTORIZE_FALLBACK_API_KEY=your-fallback-key
|
|
56
|
+
# VECTORIZE_FALLBACK_BASE_URL=https://api.deepinfra.com/v1/openai
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ===================
|
|
60
|
+
# Rerank (required)
|
|
61
|
+
# ===================
|
|
62
|
+
# Used for search result reranking. Supports vLLM or DeepInfra.
|
|
63
|
+
|
|
64
|
+
RERANK_PROVIDER=deepinfra
|
|
65
|
+
RERANK_API_KEY=your-deepinfra-key-here
|
|
66
|
+
RERANK_BASE_URL=https://api.deepinfra.com/v1/inference
|
|
67
|
+
RERANK_MODEL=Qwen/Qwen3-Reranker-4B
|
|
68
|
+
RERANK_TIMEOUT=30
|
|
69
|
+
RERANK_MAX_RETRIES=3
|
|
70
|
+
RERANK_BATCH_SIZE=10
|
|
71
|
+
RERANK_MAX_CONCURRENT=5
|
|
72
|
+
|
|
73
|
+
# Optional: fallback rerank provider
|
|
74
|
+
# RERANK_FALLBACK_PROVIDER=none
|
|
75
|
+
# RERANK_FALLBACK_API_KEY=
|
|
76
|
+
# RERANK_FALLBACK_BASE_URL=
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# ===================
|
|
80
|
+
# Application Settings
|
|
81
|
+
# ===================
|
|
82
|
+
|
|
83
|
+
LOG_LEVEL=INFO
|
|
84
|
+
ENV=dev
|
|
85
|
+
MEMORY_LANGUAGE=en
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
###############################################################################
|
|
2
|
+
# EverMemOS All-in-One Container
|
|
3
|
+
#
|
|
4
|
+
# Bundles all required services into a single container:
|
|
5
|
+
# - MongoDB 7.0 (port 27017)
|
|
6
|
+
# - Elasticsearch 8 (port 9200, internal only)
|
|
7
|
+
# - Milvus standalone (port 19530, with embedded etcd)
|
|
8
|
+
# - Redis 7 (port 6379, internal only)
|
|
9
|
+
# - EverMemOS API (port 1995, exposed)
|
|
10
|
+
#
|
|
11
|
+
# Managed by supervisord. All data stored under /data.
|
|
12
|
+
#
|
|
13
|
+
# Pinned to EverMemOS commit SHA for reproducibility.
|
|
14
|
+
# Update EVERMEMOS_COMMIT to upgrade.
|
|
15
|
+
#
|
|
16
|
+
# Build:
|
|
17
|
+
# docker compose build evermemos
|
|
18
|
+
#
|
|
19
|
+
# Override version at build time:
|
|
20
|
+
# docker compose build --build-arg EVERMEMOS_COMMIT=<sha> evermemos
|
|
21
|
+
###############################################################################
|
|
22
|
+
|
|
23
|
+
# Stage 1: Extract Milvus binaries from official image
|
|
24
|
+
FROM milvusdb/milvus:v2.5.2 AS milvus-source
|
|
25
|
+
|
|
26
|
+
# Stage 2: All-in-one container
|
|
27
|
+
FROM ubuntu:22.04
|
|
28
|
+
|
|
29
|
+
ARG EVERMEMOS_COMMIT=3c9a2d02460748310c7707048f7ed6b6bb078ab4
|
|
30
|
+
ARG TARGETARCH
|
|
31
|
+
|
|
32
|
+
ENV DEBIAN_FRONTEND=noninteractive
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
# Base packages + supervisord
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
38
|
+
supervisor curl wget gnupg git ca-certificates \
|
|
39
|
+
python3 python3-pip python3-venv \
|
|
40
|
+
redis-server \
|
|
41
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
42
|
+
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
# MongoDB 7.0
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
RUN curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | \
|
|
47
|
+
gpg --dearmor -o /usr/share/keyrings/mongodb-server-7.0.gpg && \
|
|
48
|
+
echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] \
|
|
49
|
+
https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" \
|
|
50
|
+
> /etc/apt/sources.list.d/mongodb-org-7.0.list && \
|
|
51
|
+
apt-get update && \
|
|
52
|
+
apt-get install -y --no-install-recommends mongodb-org && \
|
|
53
|
+
rm -rf /var/lib/apt/lists/*
|
|
54
|
+
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
# Elasticsearch 8.11 (no security, single-node)
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
RUN curl -fsSL https://artifacts.elastic.co/GPG-KEY-elasticsearch | \
|
|
59
|
+
gpg --dearmor -o /usr/share/keyrings/elasticsearch.gpg && \
|
|
60
|
+
echo "deb [signed-by=/usr/share/keyrings/elasticsearch.gpg] \
|
|
61
|
+
https://artifacts.elastic.co/packages/8.x/apt stable main" \
|
|
62
|
+
> /etc/apt/sources.list.d/elasticsearch.list && \
|
|
63
|
+
apt-get update && \
|
|
64
|
+
apt-get install -y --no-install-recommends elasticsearch && \
|
|
65
|
+
rm -rf /var/lib/apt/lists/*
|
|
66
|
+
|
|
67
|
+
# ES config: single-node, no security, limit heap
|
|
68
|
+
RUN sed -i 's/xpack.security.enabled: true/xpack.security.enabled: false/' /etc/elasticsearch/elasticsearch.yml && \
|
|
69
|
+
sed -i '/cluster.initial_master_nodes/d' /etc/elasticsearch/elasticsearch.yml && \
|
|
70
|
+
echo "discovery.type: single-node" >> /etc/elasticsearch/elasticsearch.yml && \
|
|
71
|
+
echo "network.host: 127.0.0.1" >> /etc/elasticsearch/elasticsearch.yml && \
|
|
72
|
+
echo "-Xms512m" > /etc/elasticsearch/jvm.options.d/heap.options && \
|
|
73
|
+
echo "-Xmx512m" >> /etc/elasticsearch/jvm.options.d/heap.options
|
|
74
|
+
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
# Milvus standalone (copied from official Docker image)
|
|
77
|
+
# ---------------------------------------------------------------------------
|
|
78
|
+
COPY --from=milvus-source /milvus /opt/milvus
|
|
79
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
80
|
+
libgomp1 libtbb2 libopenblas-dev libaio1 && \
|
|
81
|
+
rm -rf /var/lib/apt/lists/*
|
|
82
|
+
|
|
83
|
+
# Milvus config for embedded etcd + local storage (no minio)
|
|
84
|
+
RUN mkdir -p /data/milvus /opt/milvus/configs && \
|
|
85
|
+
printf '%s\n' \
|
|
86
|
+
'listen-client-urls: http://0.0.0.0:2379' \
|
|
87
|
+
'advertise-client-urls: http://0.0.0.0:2379' \
|
|
88
|
+
'quota-backend-bytes: 4294967296' \
|
|
89
|
+
'auto-compaction-mode: revision' \
|
|
90
|
+
"auto-compaction-retention: '1000'" \
|
|
91
|
+
> /opt/milvus/configs/embedEtcd.yaml
|
|
92
|
+
|
|
93
|
+
ENV LD_LIBRARY_PATH=/opt/milvus/lib:$LD_LIBRARY_PATH
|
|
94
|
+
ENV PATH=/opt/milvus/bin:$PATH
|
|
95
|
+
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
# EverMemOS (Python app from source)
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
RUN pip install --no-cache-dir uv
|
|
100
|
+
|
|
101
|
+
WORKDIR /app
|
|
102
|
+
RUN git clone https://github.com/EverMind-AI/EverMemOS.git . && \
|
|
103
|
+
git checkout "$EVERMEMOS_COMMIT" && \
|
|
104
|
+
uv sync --no-dev
|
|
105
|
+
|
|
106
|
+
# ---------------------------------------------------------------------------
|
|
107
|
+
# Data directories
|
|
108
|
+
# ---------------------------------------------------------------------------
|
|
109
|
+
RUN mkdir -p /data/mongodb /data/elasticsearch /data/milvus /data/redis
|
|
110
|
+
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
# Supervisord configuration
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
COPY supervisord.conf /etc/supervisor/conf.d/evermemos.conf
|
|
115
|
+
COPY entrypoint.sh /entrypoint.sh
|
|
116
|
+
RUN chmod +x /entrypoint.sh
|
|
117
|
+
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
# Trace overlay: read-only endpoint for message_id → derived memory ObjectIds
|
|
120
|
+
# Same pattern as Graphiti's startup.py overlay — extends our image, not upstream.
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
COPY trace_overlay.py /app/trace_overlay.py
|
|
123
|
+
RUN echo 'from trace_overlay import router as _trace_router; app.include_router(_trace_router)' >> /app/src/app.py
|
|
124
|
+
|
|
125
|
+
# ---------------------------------------------------------------------------
|
|
126
|
+
# Healthcheck — verify the EverMemOS API responds
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
HEALTHCHECK --interval=15s --timeout=10s --retries=10 --start-period=60s \
|
|
129
|
+
CMD curl -sf http://localhost:1995/health || exit 1
|
|
130
|
+
|
|
131
|
+
EXPOSE 1995
|
|
132
|
+
|
|
133
|
+
CMD ["/entrypoint.sh"]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
###############################################################################
|
|
2
|
+
# openclaw-memory-rebac — EverMemOS memory backend (all-in-one container)
|
|
3
|
+
#
|
|
4
|
+
# Single container bundles: MongoDB, Elasticsearch, Milvus, Redis, EverMemOS.
|
|
5
|
+
# Managed by supervisord internally.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# cp .env.example .env # configure LLM, vectorize, rerank API keys
|
|
9
|
+
# docker compose up -d # builds image on first run (~5 min)
|
|
10
|
+
#
|
|
11
|
+
# EverMemOS endpoint: http://localhost:1995
|
|
12
|
+
#
|
|
13
|
+
# Resource requirements: ~4 GB RAM (Elasticsearch + Milvus are memory-heavy)
|
|
14
|
+
###############################################################################
|
|
15
|
+
|
|
16
|
+
name: openclaw-evermemos
|
|
17
|
+
|
|
18
|
+
services:
|
|
19
|
+
evermemos:
|
|
20
|
+
build:
|
|
21
|
+
context: .
|
|
22
|
+
args:
|
|
23
|
+
EVERMEMOS_COMMIT: 3c9a2d02460748310c7707048f7ed6b6bb078ab4
|
|
24
|
+
restart: unless-stopped
|
|
25
|
+
ports:
|
|
26
|
+
- "1995:1995"
|
|
27
|
+
env_file:
|
|
28
|
+
- .env
|
|
29
|
+
environment:
|
|
30
|
+
# All backing services run inside the container on localhost
|
|
31
|
+
MONGODB_HOST: 127.0.0.1
|
|
32
|
+
MONGODB_PORT: "27017"
|
|
33
|
+
MONGODB_USERNAME: ""
|
|
34
|
+
MONGODB_PASSWORD: ""
|
|
35
|
+
MONGODB_DATABASE: memsys
|
|
36
|
+
MONGODB_URI_PARAMS: "socketTimeoutMS=15000"
|
|
37
|
+
ES_HOSTS: http://127.0.0.1:9200
|
|
38
|
+
MILVUS_HOST: 127.0.0.1
|
|
39
|
+
MILVUS_PORT: "19530"
|
|
40
|
+
REDIS_HOST: 127.0.0.1
|
|
41
|
+
REDIS_PORT: "6379"
|
|
42
|
+
volumes:
|
|
43
|
+
- evermemos_data:/data
|
|
44
|
+
healthcheck:
|
|
45
|
+
test: ["CMD", "curl", "-sf", "http://localhost:1995/health"]
|
|
46
|
+
interval: 15s
|
|
47
|
+
timeout: 10s
|
|
48
|
+
retries: 10
|
|
49
|
+
start_period: 60s
|
|
50
|
+
|
|
51
|
+
volumes:
|
|
52
|
+
evermemos_data:
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Read-only tracing endpoint: message_id → derived memory ObjectIds.
|
|
3
|
+
|
|
4
|
+
Mounted on the EverMemOS FastAPI app at startup via Dockerfile append.
|
|
5
|
+
This is NOT a monkeypatch — it adds a new read-only endpoint to our Docker
|
|
6
|
+
image, following the same pattern as the Graphiti startup.py overlay.
|
|
7
|
+
|
|
8
|
+
Endpoint:
|
|
9
|
+
GET /api/v1/memories/trace/{message_id}
|
|
10
|
+
|
|
11
|
+
Returns the MongoDB ObjectIds of all derived memories (episodic, foresight,
|
|
12
|
+
event_log) produced from a given ingestion message. Used by the
|
|
13
|
+
openclaw-memory-rebac plugin to write SpiceDB fragment relationships
|
|
14
|
+
against the actual IDs that appear in search results.
|
|
15
|
+
|
|
16
|
+
Response:
|
|
17
|
+
{
|
|
18
|
+
"message_id": "uuid-123",
|
|
19
|
+
"status": "complete|processing|not_found",
|
|
20
|
+
"memcell_ids": ["ObjectId-A"],
|
|
21
|
+
"derived_memories": {
|
|
22
|
+
"episodic_memory": ["ObjectId-B", ...],
|
|
23
|
+
"foresight": ["ObjectId-D", ...],
|
|
24
|
+
"event_log": ["ObjectId-E", ...],
|
|
25
|
+
},
|
|
26
|
+
"all_ids": ["ObjectId-B", "ObjectId-D", "ObjectId-E", ...]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
Uses pymongo (synchronous) since motor is not installed in EverMemOS.
|
|
30
|
+
Blocking MongoDB calls are wrapped in asyncio.to_thread for FastAPI compat.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
import asyncio
|
|
34
|
+
import os
|
|
35
|
+
from pymongo import MongoClient
|
|
36
|
+
from fastapi import APIRouter
|
|
37
|
+
|
|
38
|
+
router = APIRouter(tags=["trace"])
|
|
39
|
+
|
|
40
|
+
_db = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _get_db():
|
|
44
|
+
global _db
|
|
45
|
+
if _db is None:
|
|
46
|
+
host = os.environ.get("MONGODB_HOST", "127.0.0.1")
|
|
47
|
+
port = os.environ.get("MONGODB_PORT", "27017")
|
|
48
|
+
username = os.environ.get("MONGODB_USERNAME", "")
|
|
49
|
+
password = os.environ.get("MONGODB_PASSWORD", "")
|
|
50
|
+
db_name = os.environ.get("MONGODB_DATABASE", "memsys")
|
|
51
|
+
if username and password:
|
|
52
|
+
mongo_uri = f"mongodb://{username}:{password}@{host}:{port}"
|
|
53
|
+
else:
|
|
54
|
+
mongo_uri = f"mongodb://{host}:{port}"
|
|
55
|
+
client = MongoClient(mongo_uri)
|
|
56
|
+
_db = client[db_name]
|
|
57
|
+
return _db
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _trace_sync(message_id: str) -> dict:
|
|
61
|
+
"""Synchronous MongoDB trace — run via asyncio.to_thread."""
|
|
62
|
+
db = _get_db()
|
|
63
|
+
|
|
64
|
+
# Step 1: Check if message_id exists in memory_request_logs
|
|
65
|
+
log_entry = db.memory_request_logs.find_one({"message_id": message_id})
|
|
66
|
+
if not log_entry:
|
|
67
|
+
return {
|
|
68
|
+
"message_id": message_id,
|
|
69
|
+
"status": "not_found",
|
|
70
|
+
"memcell_ids": [],
|
|
71
|
+
"derived_memories": {},
|
|
72
|
+
"all_ids": [],
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# Step 2: Find memcell that contains this message_id in its original_data
|
|
76
|
+
# The message_id is stored at: original_data[*].messages[*].extend.message_id
|
|
77
|
+
memcell = db.memcells.find_one(
|
|
78
|
+
{"original_data.messages.extend.message_id": message_id}
|
|
79
|
+
)
|
|
80
|
+
if not memcell:
|
|
81
|
+
# Log exists but memcell not yet created — still in boundary detection
|
|
82
|
+
return {
|
|
83
|
+
"message_id": message_id,
|
|
84
|
+
"status": "processing",
|
|
85
|
+
"memcell_ids": [],
|
|
86
|
+
"derived_memories": {},
|
|
87
|
+
"all_ids": [],
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
memcell_id = str(memcell["_id"])
|
|
91
|
+
|
|
92
|
+
# Step 3: Query derived memory collections
|
|
93
|
+
derived = {
|
|
94
|
+
"episodic_memory": [],
|
|
95
|
+
"foresight": [],
|
|
96
|
+
"event_log": [],
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
for doc in db.episodic_memories.find(
|
|
100
|
+
{"memcell_event_id_list": memcell_id}, {"_id": 1}
|
|
101
|
+
):
|
|
102
|
+
derived["episodic_memory"].append(str(doc["_id"]))
|
|
103
|
+
|
|
104
|
+
for doc in db.foresight_records.find(
|
|
105
|
+
{"parent_id": memcell_id, "parent_type": "memcell"}, {"_id": 1}
|
|
106
|
+
):
|
|
107
|
+
derived["foresight"].append(str(doc["_id"]))
|
|
108
|
+
|
|
109
|
+
for doc in db.event_log_records.find(
|
|
110
|
+
{"parent_id": memcell_id, "parent_type": "memcell"}, {"_id": 1}
|
|
111
|
+
):
|
|
112
|
+
derived["event_log"].append(str(doc["_id"]))
|
|
113
|
+
|
|
114
|
+
all_ids = [id_ for ids in derived.values() for id_ in ids]
|
|
115
|
+
status = "complete" if all_ids else "processing"
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
"message_id": message_id,
|
|
119
|
+
"status": status,
|
|
120
|
+
"memcell_ids": [memcell_id],
|
|
121
|
+
"derived_memories": derived,
|
|
122
|
+
"all_ids": all_ids,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@router.get("/api/v1/memories/trace/{message_id}")
|
|
127
|
+
async def trace_message(message_id: str):
|
|
128
|
+
return await asyncio.to_thread(_trace_sync, message_id)
|
|
@@ -28,7 +28,7 @@ services:
|
|
|
28
28
|
POSTGRES_PASSWORD: ${SPICEDB_POSTGRES_PASSWORD:-spicedb}
|
|
29
29
|
POSTGRES_DB: spicedb
|
|
30
30
|
volumes:
|
|
31
|
-
-
|
|
31
|
+
- spicedb_postgres_data:/var/lib/postgresql/data
|
|
32
32
|
healthcheck:
|
|
33
33
|
test: ["CMD-SHELL", "pg_isready -U spicedb"]
|
|
34
34
|
interval: 5s
|
|
@@ -56,8 +56,8 @@ services:
|
|
|
56
56
|
command: serve
|
|
57
57
|
restart: unless-stopped
|
|
58
58
|
ports:
|
|
59
|
-
- "
|
|
60
|
-
- "
|
|
59
|
+
- "${SPICEDB_GRPC_PORT:-50051}:50051" # gRPC
|
|
60
|
+
- "${SPICEDB_HTTP_PORT:-8180}:8080" # HTTP metrics / healthz
|
|
61
61
|
environment:
|
|
62
62
|
SPICEDB_GRPC_PRESHARED_KEY: ${SPICEDB_PRESHARED_KEY:-dev_token}
|
|
63
63
|
SPICEDB_DATASTORE_ENGINE: postgres
|
|
@@ -76,4 +76,4 @@ services:
|
|
|
76
76
|
start_period: 10s
|
|
77
77
|
|
|
78
78
|
volumes:
|
|
79
|
-
|
|
79
|
+
spicedb_postgres_data:
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contextableai/openclaw-memory-rebac",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "OpenClaw two-layer memory plugin: SpiceDB ReBAC authorization + Graphiti
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "OpenClaw two-layer memory plugin: SpiceDB ReBAC authorization + pluggable storage (Graphiti, EverMemOS)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -34,13 +34,15 @@
|
|
|
34
34
|
"openclaw": "*"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
|
-
"build": "rm -rf dist && tsc -p tsconfig.build.json && cp plugin.defaults.json dist/ && mkdir -p dist/backends && cp backends/graphiti.defaults.json dist/backends/",
|
|
37
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json && cp plugin.defaults.json dist/ && mkdir -p dist/backends && cp backends/graphiti.defaults.json backends/evermemos.defaults.json dist/backends/",
|
|
38
38
|
"prepublishOnly": "npm run build",
|
|
39
39
|
"cli": "tsx bin/rebac-mem.ts",
|
|
40
40
|
"typecheck": "tsc 2>&1 | grep -v '../openclaw/' | grep -c 'error TS' | xargs -I{} test {} -eq 0",
|
|
41
41
|
"test": "vitest run",
|
|
42
42
|
"test:watch": "vitest",
|
|
43
|
-
"test:e2e": "OPENCLAW_LIVE_TEST=1 vitest run --config vitest.e2e.config.ts"
|
|
43
|
+
"test:e2e": "OPENCLAW_LIVE_TEST=1 vitest run --config vitest.e2e.config.ts",
|
|
44
|
+
"test:e2e:backend": "OPENCLAW_LIVE_TEST=1 vitest run --config vitest.e2e.config.ts e2e-backend.test.ts",
|
|
45
|
+
"test:e2e:evermemos": "OPENCLAW_LIVE_TEST=1 E2E_BACKEND=evermemos vitest run --config vitest.e2e.config.ts e2e-evermemos.test.ts"
|
|
44
46
|
},
|
|
45
47
|
"dependencies": {
|
|
46
48
|
"@authzed/authzed-node": "1.6.1",
|
package/schema.zed
CHANGED
|
@@ -10,8 +10,10 @@ definition agent {
|
|
|
10
10
|
|
|
11
11
|
definition group {
|
|
12
12
|
relation member: person | agent
|
|
13
|
-
|
|
14
|
-
permission
|
|
13
|
+
relation owner: person | agent
|
|
14
|
+
permission access = member + owner
|
|
15
|
+
permission contribute = member + owner
|
|
16
|
+
permission admin = owner
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
definition memory_fragment {
|
|
@@ -24,4 +26,6 @@ definition memory_fragment {
|
|
|
24
26
|
permission view = involves + shared_by + source_group->access + involves->represents
|
|
25
27
|
// Can delete if: you shared it (owner-level control)
|
|
26
28
|
permission delete = shared_by
|
|
29
|
+
// Can share if: you shared it, or you are an admin of the source group
|
|
30
|
+
permission share = shared_by + source_group->admin
|
|
27
31
|
}
|