@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.
@@ -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
- - graphiti_spicedb_postgres_data:/var/lib/postgresql/data
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
- - "127.0.0.1:${SPICEDB_GRPC_PORT:-50051}:50051" # gRPC
60
- - "127.0.0.1:${SPICEDB_HTTP_PORT:-8080}:8080" # HTTP metrics / healthz
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
- graphiti_spicedb_postgres_data:
79
+ spicedb_postgres_data:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@contextableai/openclaw-memory-rebac",
3
- "version": "0.3.8",
4
- "description": "OpenClaw two-layer memory plugin: SpiceDB ReBAC authorization + Graphiti knowledge graph",
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
- permission access = member
14
- permission contribute = member
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
  }