@christopherlittle51/postclaw 1.0.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.
Files changed (46) hide show
  1. package/.env.example +4 -0
  2. package/LICENSE +15 -0
  3. package/README.md +731 -0
  4. package/dist/index.d.ts +36 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +506 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/schemas/validation.d.ts +81 -0
  9. package/dist/schemas/validation.d.ts.map +1 -0
  10. package/dist/schemas/validation.js +27 -0
  11. package/dist/schemas/validation.js.map +1 -0
  12. package/dist/scripts/bootstrap_persona.d.ts +20 -0
  13. package/dist/scripts/bootstrap_persona.d.ts.map +1 -0
  14. package/dist/scripts/bootstrap_persona.js +150 -0
  15. package/dist/scripts/bootstrap_persona.js.map +1 -0
  16. package/dist/scripts/bootstrap_tools.d.ts +2 -0
  17. package/dist/scripts/bootstrap_tools.d.ts.map +1 -0
  18. package/dist/scripts/bootstrap_tools.js +67 -0
  19. package/dist/scripts/bootstrap_tools.js.map +1 -0
  20. package/dist/scripts/setup-db.d.ts +28 -0
  21. package/dist/scripts/setup-db.d.ts.map +1 -0
  22. package/dist/scripts/setup-db.js +539 -0
  23. package/dist/scripts/setup-db.js.map +1 -0
  24. package/dist/scripts/sleep_cycle.d.ts +32 -0
  25. package/dist/scripts/sleep_cycle.d.ts.map +1 -0
  26. package/dist/scripts/sleep_cycle.js +452 -0
  27. package/dist/scripts/sleep_cycle.js.map +1 -0
  28. package/dist/services/db.d.ts +28 -0
  29. package/dist/services/db.d.ts.map +1 -0
  30. package/dist/services/db.js +93 -0
  31. package/dist/services/db.js.map +1 -0
  32. package/dist/services/memoryService.d.ts +75 -0
  33. package/dist/services/memoryService.d.ts.map +1 -0
  34. package/dist/services/memoryService.js +391 -0
  35. package/dist/services/memoryService.js.map +1 -0
  36. package/dist/test-db.d.ts +8 -0
  37. package/dist/test-db.d.ts.map +1 -0
  38. package/dist/test-db.js +94 -0
  39. package/dist/test-db.js.map +1 -0
  40. package/dist/test-memory.d.ts +2 -0
  41. package/dist/test-memory.d.ts.map +1 -0
  42. package/dist/test-memory.js +79 -0
  43. package/dist/test-memory.js.map +1 -0
  44. package/openclaw.plugin.json +37 -0
  45. package/package.json +49 -0
  46. package/schemas/db.sql +371 -0
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const db_js_1 = require("./services/db.js");
4
+ const memoryService_js_1 = require("./services/memoryService.js");
5
+ const TEST_AGENT_ID = "test-agent";
6
+ async function main() {
7
+ console.log("=== Testing new memory_semantic columns ===\n");
8
+ console.log(` Target: ${db_js_1.POSTCLAW_DB_URL}`);
9
+ (0, db_js_1.setEmbeddingConfig)("http://127.0.0.1:1234", "text-embedding-nomic-embed-text-v2-moe");
10
+ try {
11
+ const testContent = `This is a test fact created at ${Date.now()}`;
12
+ console.log("[TEST 1] Creating a memory with additional options...");
13
+ const { id, status } = await (0, memoryService_js_1.storeMemory)(TEST_AGENT_ID, testContent, "private", {
14
+ category: "test_category",
15
+ volatility: "high",
16
+ token_count: 42,
17
+ confidence: 0.99,
18
+ tier: "session",
19
+ usefulness_score: 5.5,
20
+ metadata: { test_key: "test_value" }
21
+ });
22
+ if (id && status === "stored") {
23
+ console.log(` ✅ Stored memory successfully: ${id}`);
24
+ }
25
+ else {
26
+ console.error(` ❌ Failed to store memory: ${status}`);
27
+ process.exit(1);
28
+ }
29
+ console.log("\n[TEST 2] Verifying columns in database...");
30
+ let rows = await (0, db_js_1.getSql)().begin(async (tx) => {
31
+ await tx `SELECT set_config('app.current_agent_id', ${TEST_AGENT_ID}, true)`;
32
+ return tx `
33
+ SELECT category, volatility, token_count, confidence, tier, usefulness_score, metadata, access_count, last_accessed_at, injection_count
34
+ FROM memory_semantic
35
+ WHERE id = ${id}
36
+ `;
37
+ });
38
+ let memory = rows[0];
39
+ console.log(" Memory state after creation:");
40
+ console.log(memory);
41
+ console.log("\n[TEST 3] Searching for the memory to trigger access tracking...");
42
+ const searchResult = await (0, memoryService_js_1.searchPostgres)(TEST_AGENT_ID, testContent);
43
+ if (searchResult && searchResult.includes(testContent)) {
44
+ console.log(` ✅ Search returned the memory.`);
45
+ }
46
+ else {
47
+ console.error(` ❌ Search did not return the memory.`);
48
+ process.exit(1);
49
+ }
50
+ console.log("\n[TEST 4] Verifying access_count and last_accessed_at were updated...");
51
+ rows = await (0, db_js_1.getSql)().begin(async (tx) => {
52
+ await tx `SELECT set_config('app.current_agent_id', ${TEST_AGENT_ID}, true)`;
53
+ return tx `
54
+ SELECT access_count, injection_count, last_accessed_at
55
+ FROM memory_semantic
56
+ WHERE id = ${id}
57
+ `;
58
+ });
59
+ memory = rows[0];
60
+ console.log(" Memory state after search:");
61
+ console.log(memory);
62
+ if (memory.access_count === 1 && memory.last_accessed_at !== null) {
63
+ console.log(` ✅ Tracking columns updated successfully.`);
64
+ }
65
+ else {
66
+ console.error(` ❌ Tracking columns not updated properly.`);
67
+ process.exit(1);
68
+ }
69
+ console.log("\n=== All tests passed ===");
70
+ }
71
+ catch (err) {
72
+ console.error(" ❌ Test FAILED:", err);
73
+ }
74
+ finally {
75
+ await (0, db_js_1.getSql)().end();
76
+ }
77
+ }
78
+ main();
79
+ //# sourceMappingURL=test-memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-memory.js","sourceRoot":"","sources":["../test-memory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ {
2
+ "id": "postclaw",
3
+ "name": "PostClaw",
4
+ "version": "1.0.0",
5
+ "kind": "memory",
6
+ "description": "PostgreSQL-backed RAG, memory, and persona management",
7
+ "configSchema": {
8
+ "type": "object",
9
+ "additionalProperties": false,
10
+ "properties": {
11
+ "dbUrl": {
12
+ "type": "string",
13
+ "description": "PostgreSQL connection string (with pgvector extension enabled)"
14
+ },
15
+ "adminDbUrl": {
16
+ "type": "string",
17
+ "description": "PostgreSQL superuser connection string (used by setup command only)"
18
+ },
19
+ "sleepIntervalHours": {
20
+ "type": "number",
21
+ "description": "Hours between background sleep cycles (default: 6, set to 0 to disable)"
22
+ }
23
+ }
24
+ },
25
+ "uiHints": {
26
+ "dbUrl": {
27
+ "label": "Database URL",
28
+ "placeholder": "postgres://user:pass@localhost:5432/memorydb",
29
+ "sensitive": true
30
+ },
31
+ "adminDbUrl": {
32
+ "label": "Admin Database URL",
33
+ "placeholder": "postgres://postgres:pass@localhost:5432/postgres",
34
+ "sensitive": true
35
+ }
36
+ }
37
+ }
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@christopherlittle51/postclaw",
3
+ "version": "1.0.0",
4
+ "description": "OpenClaw native plugin for PostgreSQL-backed RAG, memory, and persona management",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist/",
9
+ "schemas/db.sql",
10
+ "openclaw.plugin.json",
11
+ "README.md",
12
+ "LICENSE",
13
+ ".env.example"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "dev": "tsc --watch",
18
+ "check": "tsc --noEmit",
19
+ "start": "node dist/index.js",
20
+ "test:db": "node dist/test-db.js",
21
+ "prepublishOnly": "npm run build"
22
+ },
23
+ "keywords": [
24
+ "openclaw",
25
+ "plugin",
26
+ "postgres",
27
+ "rag",
28
+ "memory",
29
+ "pgvector"
30
+ ],
31
+ "author": "Christopher Little",
32
+ "license": "ISC",
33
+ "type": "commonjs",
34
+ "openclaw": {
35
+ "extensions": [
36
+ "./dist/index.js"
37
+ ]
38
+ },
39
+ "dependencies": {
40
+ "@sinclair/typebox": "^0.34.48",
41
+ "dotenv": "^17.3.1",
42
+ "postgres": "^3.4.8",
43
+ "zod": "^4.3.6"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^25.3.3",
47
+ "typescript": "^5.9.3"
48
+ }
49
+ }
package/schemas/db.sql ADDED
@@ -0,0 +1,371 @@
1
+ CREATE USER openclaw WITH LOGIN PASSWORD '6599';
2
+
3
+ GRANT CONNECT ON DATABASE memorydb TO openclaw;
4
+
5
+ -- 1. Core Extensions
6
+ CREATE EXTENSION IF NOT EXISTS vector;
7
+ CREATE EXTENSION IF NOT EXISTS pgcrypto;
8
+
9
+ -- 2. Enumerated Types
10
+ CREATE TYPE access_scope_enum AS ENUM ('private', 'shared', 'global');
11
+ CREATE TYPE memory_tier_enum AS ENUM ('volatile', 'session', 'daily', 'stable', 'permanent');
12
+ CREATE TYPE volatility_enum AS ENUM ('low', 'medium', 'high');
13
+
14
+ -- ==========================================
15
+ -- LAYER 0: IDENTITY & TRIGGERS (NEW)
16
+ -- ==========================================
17
+
18
+ -- Trigger to automatically handle updated_at
19
+ CREATE OR REPLACE FUNCTION update_modified_column()
20
+ RETURNS TRIGGER AS $$
21
+ BEGIN
22
+ NEW.updated_at = CURRENT_TIMESTAMP;
23
+ RETURN NEW;
24
+ END;
25
+ $$ language 'plpgsql';
26
+
27
+ -- Function to enforce global record ownership while allowing superceding memories
28
+ CREATE OR REPLACE FUNCTION enforce_immutable_ownership()
29
+ RETURNS TRIGGER AS $$
30
+ BEGIN
31
+ -- Prevent identity spoofing or stealing global memories
32
+ IF NEW.agent_id <> OLD.agent_id THEN
33
+ RAISE EXCEPTION 'Epistemic violation: Cannot change the original author (agent_id) of a memory.';
34
+ END IF;
35
+ RETURN NEW;
36
+ END;
37
+ $$ LANGUAGE plpgsql;
38
+
39
+ -- Core Agents Table (Prevents orphaned memories)
40
+ CREATE TABLE agents (
41
+ id VARCHAR(100) PRIMARY KEY,
42
+ name VARCHAR(255) NOT NULL,
43
+ default_embedding_model VARCHAR(100) DEFAULT 'nomic-embed-text-v2-moe',
44
+ embedding_dimensions INT DEFAULT 768,
45
+ created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
46
+ is_active BOOLEAN DEFAULT true
47
+ );
48
+
49
+ -- ==========================================
50
+ -- LAYER 1: THE CONSTITUTION & PROTOCOLS
51
+ -- ==========================================
52
+
53
+ CREATE TABLE agent_persona (
54
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
55
+ agent_id VARCHAR(100) NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
56
+ access_scope access_scope_enum DEFAULT 'private',
57
+ category VARCHAR(50) NOT NULL,
58
+ content TEXT NOT NULL,
59
+ is_always_active BOOLEAN DEFAULT false,
60
+
61
+ -- Removed hardcoded dimensions to support model swapping
62
+ embedding vector,
63
+ created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
64
+ UNIQUE(agent_id, category)
65
+ );
66
+
67
+ CREATE TABLE context_environment (
68
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
69
+ agent_id VARCHAR(100) NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
70
+ access_scope access_scope_enum DEFAULT 'private',
71
+ tool_name VARCHAR(100) NOT NULL,
72
+ context_data TEXT NOT NULL,
73
+ embedding vector,
74
+ created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
75
+ UNIQUE(agent_id, tool_name) -- NEW: Prevents duplicate tool definitions
76
+ );
77
+
78
+ -- ==========================================
79
+ -- LAYER 2: THE ACTIVE BRAIN (With Drift Protection)
80
+ -- ==========================================
81
+
82
+ CREATE TABLE memory_semantic (
83
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
84
+ agent_id VARCHAR(100) NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
85
+ access_scope access_scope_enum DEFAULT 'private',
86
+
87
+ content TEXT NOT NULL,
88
+ content_hash VARCHAR(64) NOT NULL,
89
+ category VARCHAR(50),
90
+
91
+ source_uri VARCHAR(512),
92
+ volatility volatility_enum DEFAULT 'low',
93
+ is_pointer BOOLEAN DEFAULT false,
94
+
95
+ -- AI Operations
96
+ embedding vector,
97
+ embedding_model VARCHAR(100) NOT NULL, -- Required to group vectors for HNSW
98
+ token_count INT NOT NULL DEFAULT 0,
99
+
100
+ -- FTS switched to 'simple' dictionary to preserve exact code/JSON tokens
101
+ fts_vector tsvector GENERATED ALWAYS AS (to_tsvector('simple'::regconfig, content)) STORED,
102
+
103
+ confidence REAL DEFAULT 0.5,
104
+ tier memory_tier_enum DEFAULT 'daily',
105
+ usefulness_score REAL DEFAULT 0.0,
106
+
107
+ injection_count INT DEFAULT 0,
108
+ access_count INT DEFAULT 0,
109
+ last_injected_at TIMESTAMPTZ,
110
+ last_accessed_at TIMESTAMPTZ,
111
+
112
+ created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
113
+ updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
114
+ expires_at TIMESTAMPTZ,
115
+ is_archived BOOLEAN DEFAULT false,
116
+
117
+ -- AI Metadata & Reasoning Trace
118
+ metadata JSONB DEFAULT '{}'::jsonb,
119
+ superseded_by UUID REFERENCES memory_semantic(id) ON DELETE SET NULL,
120
+
121
+ UNIQUE(agent_id, content_hash)
122
+ );
123
+
124
+ CREATE TRIGGER update_memory_semantic_modtime
125
+ BEFORE UPDATE ON memory_semantic
126
+ FOR EACH ROW EXECUTE FUNCTION update_modified_column();
127
+
128
+ CREATE TRIGGER trg_immutable_memory_owner
129
+ BEFORE UPDATE ON memory_semantic
130
+ FOR EACH ROW EXECUTE FUNCTION enforce_immutable_ownership();
131
+
132
+ CREATE TABLE memory_episodic (
133
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
134
+ agent_id VARCHAR(100) NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
135
+ session_id VARCHAR(100) NOT NULL,
136
+
137
+ event_summary TEXT NOT NULL,
138
+ event_type VARCHAR(50),
139
+ source_uri VARCHAR(512),
140
+
141
+ embedding vector,
142
+ embedding_model VARCHAR(100) NOT NULL,
143
+ token_count INT NOT NULL DEFAULT 0,
144
+
145
+ fts_vector tsvector GENERATED ALWAYS AS (to_tsvector('simple'::regconfig, event_summary)) STORED,
146
+
147
+ usefulness_score REAL DEFAULT 0.0,
148
+ metadata JSONB DEFAULT '{}'::jsonb,
149
+
150
+ created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
151
+ is_archived BOOLEAN DEFAULT false
152
+ );
153
+
154
+ -- ==========================================
155
+ -- LAYER 3: ADVANCED FACULTIES
156
+ -- ==========================================
157
+
158
+ CREATE TABLE conversation_checkpoints (
159
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
160
+ agent_id VARCHAR(100) NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
161
+ session_id VARCHAR(100) NOT NULL,
162
+ summary TEXT NOT NULL,
163
+ active_tasks JSONB,
164
+ token_count INT,
165
+ created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
166
+ );
167
+
168
+ CREATE TABLE entity_edges (
169
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
170
+ agent_id VARCHAR(100) NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
171
+
172
+ -- Removed ON DELETE CASCADE to prevent RAG amnesia.
173
+ -- Relies on application-level soft deletion handling.
174
+ source_memory_id UUID REFERENCES memory_semantic(id),
175
+ target_memory_id UUID REFERENCES memory_semantic(id),
176
+ relationship_type VARCHAR(100) NOT NULL,
177
+ weight REAL DEFAULT 1.0,
178
+ created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
179
+
180
+ -- Prevent self-referential graph loops
181
+ CHECK (source_memory_id != target_memory_id),
182
+ UNIQUE(source_memory_id, target_memory_id, relationship_type)
183
+ );
184
+
185
+ -- ==========================================
186
+ -- INDICES & PERFORMANCE
187
+ -- ==========================================
188
+
189
+ -- Standard and File Watcher Indices
190
+ CREATE INDEX idx_mem_sem_agent_tier ON memory_semantic(agent_id, tier, is_archived);
191
+ CREATE INDEX idx_mem_sem_hash ON memory_semantic(content_hash);
192
+ CREATE INDEX idx_mem_epi_session ON memory_episodic(agent_id, session_id, is_archived);
193
+ CREATE INDEX idx_mem_sem_source_uri ON memory_semantic(source_uri) WHERE source_uri IS NOT NULL;
194
+ CREATE INDEX idx_mem_sem_metadata ON memory_semantic USING gin (metadata);
195
+ CREATE INDEX idx_mem_epi_metadata ON memory_episodic USING gin (metadata);
196
+ CREATE INDEX idx_mem_sem_expires ON memory_semantic(expires_at) WHERE expires_at IS NOT NULL;
197
+
198
+ -- Search Indices
199
+ CREATE INDEX ON memory_semantic USING gin (fts_vector);
200
+ CREATE INDEX ON memory_episodic USING gin (fts_vector);
201
+
202
+ -- Partial HNSW Vector Indices (Segmented by model and cast to specific dimensions)
203
+ CREATE INDEX idx_mem_sem_vector_nomic ON memory_semantic USING hnsw ((embedding::vector(768)) vector_cosine_ops) WHERE embedding_model = 'nomic-embed-text-v2-moe';
204
+ CREATE INDEX idx_mem_sem_vector_openai ON memory_semantic USING hnsw ((embedding::vector(1536)) vector_cosine_ops) WHERE embedding_model = 'text-embedding-3-small';
205
+
206
+ -- GraphRAG Traversal Indices
207
+ CREATE INDEX idx_edges_source ON entity_edges(source_memory_id);
208
+ CREATE INDEX idx_edges_target ON entity_edges(target_memory_id);
209
+
210
+ -- Causal Chain Index (Allows fast lookups of "Which memories were superseded by X?")
211
+ CREATE INDEX idx_mem_sem_superseded ON memory_semantic(superseded_by) WHERE superseded_by IS NOT NULL;
212
+
213
+ -- ==========================================
214
+ -- ADMIN USER SETUP
215
+ -- ==========================================
216
+
217
+ -- Create a role for your backend admin/system jobs
218
+ CREATE ROLE system_admin WITH LOGIN PASSWORD 'snack-trailing-gathering-effective-spiritual-balsamic';
219
+
220
+ -- Grant this role the power to ignore RLS
221
+ ALTER ROLE system_admin BYPASSRLS;
222
+
223
+ -- (Give them standard read/write permissions as well)
224
+ GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO system_admin;
225
+
226
+ -- ==========================================
227
+ -- ROW-LEVEL SECURITY (RLS)
228
+ -- ==========================================
229
+
230
+ -- Enable RLS across ALL relevant tables
231
+ ALTER TABLE memory_semantic ENABLE ROW LEVEL SECURITY;
232
+ ALTER TABLE memory_episodic ENABLE ROW LEVEL SECURITY;
233
+ ALTER TABLE agent_persona ENABLE ROW LEVEL SECURITY;
234
+ ALTER TABLE context_environment ENABLE ROW LEVEL SECURITY;
235
+ ALTER TABLE entity_edges ENABLE ROW LEVEL SECURITY;
236
+
237
+ -- 1. Read Policy: Can read own memories + shared/global memories
238
+ CREATE POLICY semantic_read ON memory_semantic FOR SELECT
239
+ USING (agent_id = current_setting('app.current_agent_id', true) OR access_scope IN ('shared', 'global'));
240
+
241
+ -- 2. Insert Policy: Can ONLY insert memories as yourself
242
+ CREATE POLICY semantic_insert ON memory_semantic FOR INSERT
243
+ WITH CHECK (agent_id = current_setting('app.current_agent_id', true));
244
+
245
+ -- 3. Update Policy: Can ONLY update YOUR memories. You cannot alter someone else's global memory.
246
+ CREATE POLICY semantic_update ON memory_semantic FOR UPDATE
247
+ USING (
248
+ -- You can target the row if you own it, OR if it's a global fact
249
+ agent_id = current_setting('app.current_agent_id', true)
250
+ OR access_scope = 'global'
251
+ )
252
+ WITH CHECK (
253
+ -- If you update a global memory, you CANNOT downgrade it to private.
254
+ -- It must remain global for the rest of the hive mind.
255
+ (agent_id = current_setting('app.current_agent_id', true))
256
+ OR
257
+ (access_scope = 'global')
258
+ );
259
+
260
+ -- 4. Delete Policy: Can ONLY delete YOUR memories.
261
+ CREATE POLICY semantic_delete ON memory_semantic FOR DELETE
262
+ USING (agent_id = current_setting('app.current_agent_id', true));
263
+
264
+ -- ==========================================
265
+ -- AGENT PERSONA POLICIES
266
+ -- ==========================================
267
+ CREATE POLICY persona_read ON agent_persona FOR SELECT
268
+ USING (agent_id = current_setting('app.current_agent_id', true) OR access_scope IN ('shared', 'global'));
269
+
270
+ CREATE POLICY persona_insert ON agent_persona FOR INSERT
271
+ WITH CHECK (agent_id = current_setting('app.current_agent_id', true));
272
+
273
+ CREATE POLICY persona_update ON agent_persona FOR UPDATE
274
+ USING (agent_id = current_setting('app.current_agent_id', true) OR access_scope = 'global')
275
+ WITH CHECK (agent_id = current_setting('app.current_agent_id', true) OR access_scope = 'global');
276
+
277
+ CREATE POLICY persona_delete ON agent_persona FOR DELETE
278
+ USING (agent_id = current_setting('app.current_agent_id', true));
279
+
280
+ -- ==========================================
281
+ -- CONTEXT ENVIRONMENT (TOOLS) POLICIES
282
+ -- ==========================================
283
+ CREATE POLICY context_read ON context_environment FOR SELECT
284
+ USING (agent_id = current_setting('app.current_agent_id', true) OR access_scope IN ('shared', 'global'));
285
+
286
+ CREATE POLICY context_insert ON context_environment FOR INSERT
287
+ WITH CHECK (agent_id = current_setting('app.current_agent_id', true));
288
+
289
+ CREATE POLICY context_update ON context_environment FOR UPDATE
290
+ USING (agent_id = current_setting('app.current_agent_id', true) OR access_scope = 'global')
291
+ WITH CHECK (agent_id = current_setting('app.current_agent_id', true) OR access_scope = 'global');
292
+
293
+ CREATE POLICY context_delete ON context_environment FOR DELETE
294
+ USING (agent_id = current_setting('app.current_agent_id', true));
295
+
296
+ -- * CRITICAL SECURITY NOTE: Just like semantic_memory, you must apply the
297
+ -- immutable ownership trigger here so agents can't steal global tools/personas during an UPDATE.
298
+ CREATE TRIGGER trg_immutable_persona_owner BEFORE UPDATE ON agent_persona
299
+ FOR EACH ROW EXECUTE FUNCTION enforce_immutable_ownership();
300
+ CREATE TRIGGER trg_immutable_context_owner BEFORE UPDATE ON context_environment
301
+ FOR EACH ROW EXECUTE FUNCTION enforce_immutable_ownership();
302
+
303
+ -- Ensure RLS is active on Checkpoints (we missed this in the original ALTER TABLE block)
304
+ ALTER TABLE conversation_checkpoints ENABLE ROW LEVEL SECURITY;
305
+
306
+ -- ==========================================
307
+ -- EPISODIC MEMORY POLICIES
308
+ -- ==========================================
309
+ CREATE POLICY episodic_read ON memory_episodic FOR SELECT
310
+ USING (agent_id = current_setting('app.current_agent_id', true));
311
+
312
+ CREATE POLICY episodic_insert ON memory_episodic FOR INSERT
313
+ WITH CHECK (agent_id = current_setting('app.current_agent_id', true));
314
+
315
+ CREATE POLICY episodic_update ON memory_episodic FOR UPDATE
316
+ USING (agent_id = current_setting('app.current_agent_id', true))
317
+ WITH CHECK (agent_id = current_setting('app.current_agent_id', true));
318
+
319
+ CREATE POLICY episodic_delete ON memory_episodic FOR DELETE
320
+ USING (agent_id = current_setting('app.current_agent_id', true));
321
+
322
+ -- ==========================================
323
+ -- CONVERSATION CHECKPOINTS POLICIES
324
+ -- ==========================================
325
+ CREATE POLICY checkpoint_read ON conversation_checkpoints FOR SELECT
326
+ USING (agent_id = current_setting('app.current_agent_id', true));
327
+
328
+ CREATE POLICY checkpoint_insert ON conversation_checkpoints FOR INSERT
329
+ WITH CHECK (agent_id = current_setting('app.current_agent_id', true));
330
+
331
+ CREATE POLICY checkpoint_update ON conversation_checkpoints FOR UPDATE
332
+ USING (agent_id = current_setting('app.current_agent_id', true))
333
+ WITH CHECK (agent_id = current_setting('app.current_agent_id', true));
334
+
335
+ CREATE POLICY checkpoint_delete ON conversation_checkpoints FOR DELETE
336
+ USING (agent_id = current_setting('app.current_agent_id', true));
337
+
338
+ -- ==========================================
339
+ -- KNOWLEDGE GRAPH EDGES POLICIES
340
+ -- ==========================================
341
+
342
+ -- READ: You can see an edge if you drew it yourself, OR if it connects
343
+ -- to at least one memory node that is shared/global.
344
+ CREATE POLICY edge_read ON entity_edges FOR SELECT
345
+ USING (
346
+ agent_id = current_setting('app.current_agent_id', true)
347
+ OR EXISTS (
348
+ SELECT 1 FROM memory_semantic ms
349
+ WHERE ms.id IN (source_memory_id, target_memory_id)
350
+ AND ms.access_scope IN ('shared', 'global')
351
+ )
352
+ );
353
+
354
+ -- INSERT: You can only draw edges as yourself
355
+ CREATE POLICY edge_insert ON entity_edges FOR INSERT
356
+ WITH CHECK (agent_id = current_setting('app.current_agent_id', true));
357
+
358
+ -- UPDATE: Edges are simple metadata (weights, relationship type).
359
+ -- Only the agent who mapped the relationship should be able to update its weight.
360
+ CREATE POLICY edge_update ON entity_edges FOR UPDATE
361
+ USING (agent_id = current_setting('app.current_agent_id', true))
362
+ WITH CHECK (agent_id = current_setting('app.current_agent_id', true));
363
+
364
+ -- DELETE: You can only delete edges you authored
365
+ CREATE POLICY edge_delete ON entity_edges FOR DELETE
366
+ USING (agent_id = current_setting('app.current_agent_id', true));
367
+
368
+ -- Move this to the BOTTOM of init.sql (after all tables are created)
369
+ GRANT USAGE ON SCHEMA public TO openclaw;
370
+ GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO openclaw;
371
+ GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO openclaw;