supaclaw 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.
@@ -0,0 +1,219 @@
1
+ -- OpenClaw Memory - Vector Search Functions
2
+ -- Run this in your Supabase SQL editor after 001_initial.sql
3
+
4
+ -- Function: Vector similarity search for memories
5
+ -- Returns memories ranked by cosine similarity to the query embedding
6
+ CREATE OR REPLACE FUNCTION match_memories(
7
+ query_embedding VECTOR(1536),
8
+ match_threshold FLOAT DEFAULT 0.7,
9
+ match_count INT DEFAULT 10,
10
+ p_agent_id TEXT DEFAULT NULL,
11
+ p_user_id TEXT DEFAULT NULL,
12
+ p_category TEXT DEFAULT NULL,
13
+ p_min_importance FLOAT DEFAULT NULL
14
+ )
15
+ RETURNS TABLE (
16
+ id UUID,
17
+ agent_id TEXT,
18
+ user_id TEXT,
19
+ category TEXT,
20
+ content TEXT,
21
+ importance FLOAT,
22
+ source_session_id UUID,
23
+ created_at TIMESTAMPTZ,
24
+ updated_at TIMESTAMPTZ,
25
+ expires_at TIMESTAMPTZ,
26
+ embedding VECTOR(1536),
27
+ metadata JSONB,
28
+ similarity FLOAT
29
+ )
30
+ LANGUAGE plpgsql
31
+ AS $$
32
+ BEGIN
33
+ RETURN QUERY
34
+ SELECT
35
+ m.id,
36
+ m.agent_id,
37
+ m.user_id,
38
+ m.category,
39
+ m.content,
40
+ m.importance,
41
+ m.source_session_id,
42
+ m.created_at,
43
+ m.updated_at,
44
+ m.expires_at,
45
+ m.embedding,
46
+ m.metadata,
47
+ 1 - (m.embedding <=> query_embedding) AS similarity
48
+ FROM memories m
49
+ WHERE
50
+ (p_agent_id IS NULL OR m.agent_id = p_agent_id)
51
+ AND (p_user_id IS NULL OR m.user_id = p_user_id OR m.user_id IS NULL)
52
+ AND (p_category IS NULL OR m.category = p_category)
53
+ AND (p_min_importance IS NULL OR m.importance >= p_min_importance)
54
+ AND (m.expires_at IS NULL OR m.expires_at > NOW())
55
+ AND m.embedding IS NOT NULL
56
+ AND 1 - (m.embedding <=> query_embedding) > match_threshold
57
+ ORDER BY m.embedding <=> query_embedding
58
+ LIMIT match_count;
59
+ END;
60
+ $$;
61
+
62
+ -- Function: Hybrid search combining vector similarity and keyword matching
63
+ -- Returns memories ranked by weighted combination of semantic similarity and keyword relevance
64
+ CREATE OR REPLACE FUNCTION hybrid_search_memories(
65
+ query_embedding VECTOR(1536),
66
+ query_text TEXT,
67
+ vector_weight FLOAT DEFAULT 0.7,
68
+ keyword_weight FLOAT DEFAULT 0.3,
69
+ match_count INT DEFAULT 10,
70
+ p_agent_id TEXT DEFAULT NULL,
71
+ p_user_id TEXT DEFAULT NULL,
72
+ p_category TEXT DEFAULT NULL,
73
+ p_min_importance FLOAT DEFAULT NULL
74
+ )
75
+ RETURNS TABLE (
76
+ id UUID,
77
+ agent_id TEXT,
78
+ user_id TEXT,
79
+ category TEXT,
80
+ content TEXT,
81
+ importance FLOAT,
82
+ source_session_id UUID,
83
+ created_at TIMESTAMPTZ,
84
+ updated_at TIMESTAMPTZ,
85
+ expires_at TIMESTAMPTZ,
86
+ embedding VECTOR(1536),
87
+ metadata JSONB,
88
+ score FLOAT
89
+ )
90
+ LANGUAGE plpgsql
91
+ AS $$
92
+ BEGIN
93
+ RETURN QUERY
94
+ WITH vector_scores AS (
95
+ SELECT
96
+ m.id,
97
+ 1 - (m.embedding <=> query_embedding) AS vector_similarity
98
+ FROM memories m
99
+ WHERE
100
+ (p_agent_id IS NULL OR m.agent_id = p_agent_id)
101
+ AND (p_user_id IS NULL OR m.user_id = p_user_id OR m.user_id IS NULL)
102
+ AND (p_category IS NULL OR m.category = p_category)
103
+ AND (p_min_importance IS NULL OR m.importance >= p_min_importance)
104
+ AND (m.expires_at IS NULL OR m.expires_at > NOW())
105
+ AND m.embedding IS NOT NULL
106
+ ),
107
+ keyword_scores AS (
108
+ SELECT
109
+ m.id,
110
+ ts_rank(to_tsvector('english', m.content), plainto_tsquery('english', query_text)) AS keyword_relevance
111
+ FROM memories m
112
+ WHERE
113
+ (p_agent_id IS NULL OR m.agent_id = p_agent_id)
114
+ AND (p_user_id IS NULL OR m.user_id = p_user_id OR m.user_id IS NULL)
115
+ AND (p_category IS NULL OR m.category = p_category)
116
+ AND (p_min_importance IS NULL OR m.importance >= p_min_importance)
117
+ AND (m.expires_at IS NULL OR m.expires_at > NOW())
118
+ AND to_tsvector('english', m.content) @@ plainto_tsquery('english', query_text)
119
+ ),
120
+ combined_scores AS (
121
+ SELECT
122
+ COALESCE(v.id, k.id) AS memory_id,
123
+ (COALESCE(v.vector_similarity, 0) * vector_weight +
124
+ COALESCE(k.keyword_relevance, 0) * keyword_weight) AS combined_score
125
+ FROM vector_scores v
126
+ FULL OUTER JOIN keyword_scores k ON v.id = k.id
127
+ )
128
+ SELECT
129
+ m.id,
130
+ m.agent_id,
131
+ m.user_id,
132
+ m.category,
133
+ m.content,
134
+ m.importance,
135
+ m.source_session_id,
136
+ m.created_at,
137
+ m.updated_at,
138
+ m.expires_at,
139
+ m.embedding,
140
+ m.metadata,
141
+ cs.combined_score AS score
142
+ FROM memories m
143
+ JOIN combined_scores cs ON m.id = cs.memory_id
144
+ ORDER BY cs.combined_score DESC
145
+ LIMIT match_count;
146
+ END;
147
+ $$;
148
+
149
+ -- Function: Find similar memories based on an existing memory
150
+ -- Useful for finding related context or detecting duplicates
151
+ CREATE OR REPLACE FUNCTION find_similar_memories(
152
+ memory_id UUID,
153
+ match_threshold FLOAT DEFAULT 0.8,
154
+ match_count INT DEFAULT 5
155
+ )
156
+ RETURNS TABLE (
157
+ id UUID,
158
+ agent_id TEXT,
159
+ user_id TEXT,
160
+ category TEXT,
161
+ content TEXT,
162
+ importance FLOAT,
163
+ source_session_id UUID,
164
+ created_at TIMESTAMPTZ,
165
+ updated_at TIMESTAMPTZ,
166
+ expires_at TIMESTAMPTZ,
167
+ embedding VECTOR(1536),
168
+ metadata JSONB,
169
+ similarity FLOAT
170
+ )
171
+ LANGUAGE plpgsql
172
+ AS $$
173
+ DECLARE
174
+ source_embedding VECTOR(1536);
175
+ source_agent_id TEXT;
176
+ BEGIN
177
+ -- Get the embedding of the source memory
178
+ SELECT m.embedding, m.agent_id INTO source_embedding, source_agent_id
179
+ FROM memories m
180
+ WHERE m.id = memory_id;
181
+
182
+ IF source_embedding IS NULL THEN
183
+ RAISE EXCEPTION 'Memory not found or has no embedding';
184
+ END IF;
185
+
186
+ RETURN QUERY
187
+ SELECT
188
+ m.id,
189
+ m.agent_id,
190
+ m.user_id,
191
+ m.category,
192
+ m.content,
193
+ m.importance,
194
+ m.source_session_id,
195
+ m.created_at,
196
+ m.updated_at,
197
+ m.expires_at,
198
+ m.embedding,
199
+ m.metadata,
200
+ 1 - (m.embedding <=> source_embedding) AS similarity
201
+ FROM memories m
202
+ WHERE
203
+ m.id != memory_id
204
+ AND m.agent_id = source_agent_id
205
+ AND m.embedding IS NOT NULL
206
+ AND 1 - (m.embedding <=> source_embedding) > match_threshold
207
+ ORDER BY m.embedding <=> source_embedding
208
+ LIMIT match_count;
209
+ END;
210
+ $$;
211
+
212
+ -- Create full-text search index for keyword search optimization
213
+ CREATE INDEX IF NOT EXISTS memories_content_fts_idx ON memories
214
+ USING GIN (to_tsvector('english', content));
215
+
216
+ -- Comments for documentation
217
+ COMMENT ON FUNCTION match_memories IS 'Performs semantic search on memories using vector similarity (cosine distance)';
218
+ COMMENT ON FUNCTION hybrid_search_memories IS 'Combines vector similarity and keyword matching with configurable weights';
219
+ COMMENT ON FUNCTION find_similar_memories IS 'Finds memories similar to a given memory, useful for deduplication and context expansion';
@@ -0,0 +1,143 @@
1
+ -- Migration 003: Entity Relationships
2
+ -- Tracks connections between entities (e.g., "Han works_at MetalBear")
3
+
4
+ -- Create entity_relationships table
5
+ CREATE TABLE IF NOT EXISTS entity_relationships (
6
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
7
+ agent_id TEXT NOT NULL,
8
+ source_entity_id UUID REFERENCES entities(id) ON DELETE CASCADE,
9
+ target_entity_id UUID REFERENCES entities(id) ON DELETE CASCADE,
10
+ relationship_type TEXT NOT NULL, -- 'works_at', 'knows', 'created', 'located_in', etc.
11
+ properties JSONB DEFAULT '{}', -- Additional context (since, strength, etc.)
12
+ first_seen_at TIMESTAMPTZ DEFAULT NOW(),
13
+ last_seen_at TIMESTAMPTZ DEFAULT NOW(),
14
+ mention_count INT DEFAULT 1,
15
+ confidence FLOAT DEFAULT 0.5, -- 0-1 confidence in this relationship
16
+ source_session_id UUID REFERENCES sessions(id),
17
+ metadata JSONB DEFAULT '{}'
18
+ );
19
+
20
+ -- Indexes for efficient lookups
21
+ CREATE INDEX IF NOT EXISTS entity_relationships_source_idx
22
+ ON entity_relationships(source_entity_id);
23
+
24
+ CREATE INDEX IF NOT EXISTS entity_relationships_target_idx
25
+ ON entity_relationships(target_entity_id);
26
+
27
+ CREATE INDEX IF NOT EXISTS entity_relationships_type_idx
28
+ ON entity_relationships(relationship_type);
29
+
30
+ -- Prevent duplicate relationships
31
+ CREATE UNIQUE INDEX IF NOT EXISTS entity_relationships_unique_idx
32
+ ON entity_relationships(agent_id, source_entity_id, target_entity_id, relationship_type);
33
+
34
+ -- Function to increment relationship mention count
35
+ CREATE OR REPLACE FUNCTION increment_relationship_mentions(rel_id UUID)
36
+ RETURNS entity_relationships AS $$
37
+ DECLARE
38
+ result entity_relationships;
39
+ BEGIN
40
+ UPDATE entity_relationships
41
+ SET mention_count = mention_count + 1,
42
+ last_seen_at = NOW()
43
+ WHERE id = rel_id
44
+ RETURNING * INTO result;
45
+
46
+ RETURN result;
47
+ END;
48
+ $$ LANGUAGE plpgsql;
49
+
50
+ -- Function to find related entities (graph traversal)
51
+ CREATE OR REPLACE FUNCTION find_related_entities(
52
+ entity_id UUID,
53
+ max_depth INT DEFAULT 2,
54
+ min_confidence FLOAT DEFAULT 0.5
55
+ )
56
+ RETURNS TABLE (
57
+ entity_id UUID,
58
+ entity_name TEXT,
59
+ entity_type TEXT,
60
+ relationship_path TEXT[],
61
+ total_confidence FLOAT,
62
+ depth INT
63
+ ) AS $$
64
+ WITH RECURSIVE entity_graph AS (
65
+ -- Base case: direct relationships
66
+ SELECT
67
+ e.id as entity_id,
68
+ e.name as entity_name,
69
+ e.entity_type,
70
+ ARRAY[r.relationship_type] as relationship_path,
71
+ r.confidence as total_confidence,
72
+ 1 as depth
73
+ FROM entity_relationships r
74
+ JOIN entities e ON e.id = r.target_entity_id
75
+ WHERE r.source_entity_id = entity_id
76
+ AND r.confidence >= min_confidence
77
+
78
+ UNION
79
+
80
+ -- Recursive case: indirect relationships
81
+ SELECT
82
+ e.id,
83
+ e.name,
84
+ e.entity_type,
85
+ eg.relationship_path || r.relationship_type,
86
+ eg.total_confidence * r.confidence,
87
+ eg.depth + 1
88
+ FROM entity_graph eg
89
+ JOIN entity_relationships r ON r.source_entity_id = eg.entity_id
90
+ JOIN entities e ON e.id = r.target_entity_id
91
+ WHERE eg.depth < max_depth
92
+ AND r.confidence >= min_confidence
93
+ AND NOT e.id = ANY(SELECT unnest(eg.relationship_path::UUID[])) -- Prevent cycles
94
+ )
95
+ SELECT * FROM entity_graph
96
+ ORDER BY total_confidence DESC, depth ASC;
97
+ $$ LANGUAGE sql;
98
+
99
+ -- Function to get entity network stats
100
+ CREATE OR REPLACE FUNCTION get_entity_network_stats(agent TEXT)
101
+ RETURNS TABLE (
102
+ total_entities BIGINT,
103
+ total_relationships BIGINT,
104
+ avg_connections_per_entity FLOAT,
105
+ most_connected_entity_id UUID,
106
+ most_connected_entity_name TEXT,
107
+ connection_count BIGINT
108
+ ) AS $$
109
+ WITH entity_connections AS (
110
+ SELECT
111
+ source_entity_id as entity_id,
112
+ COUNT(*) as outgoing_count
113
+ FROM entity_relationships
114
+ WHERE agent_id = agent
115
+ GROUP BY source_entity_id
116
+ ),
117
+ most_connected AS (
118
+ SELECT
119
+ ec.entity_id,
120
+ e.name,
121
+ ec.outgoing_count
122
+ FROM entity_connections ec
123
+ JOIN entities e ON e.id = ec.entity_id
124
+ ORDER BY ec.outgoing_count DESC
125
+ LIMIT 1
126
+ )
127
+ SELECT
128
+ (SELECT COUNT(*) FROM entities WHERE agent_id = agent)::BIGINT as total_entities,
129
+ (SELECT COUNT(*) FROM entity_relationships WHERE agent_id = agent)::BIGINT as total_relationships,
130
+ (SELECT AVG(outgoing_count) FROM entity_connections)::FLOAT as avg_connections_per_entity,
131
+ mc.entity_id as most_connected_entity_id,
132
+ mc.name as most_connected_entity_name,
133
+ mc.outgoing_count::BIGINT as connection_count
134
+ FROM most_connected mc;
135
+ $$ LANGUAGE sql;
136
+
137
+ -- Comments for documentation
138
+ COMMENT ON TABLE entity_relationships IS 'Tracks relationships between entities (people, places, things)';
139
+ COMMENT ON COLUMN entity_relationships.relationship_type IS 'Type of relationship: works_at, knows, created, located_in, etc.';
140
+ COMMENT ON COLUMN entity_relationships.confidence IS 'Confidence score (0-1) in this relationship';
141
+ COMMENT ON FUNCTION increment_relationship_mentions IS 'Increments mention count and updates last_seen_at for a relationship';
142
+ COMMENT ON FUNCTION find_related_entities IS 'Graph traversal to find entities connected through relationships';
143
+ COMMENT ON FUNCTION get_entity_network_stats IS 'Statistics about the entity relationship network';
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "supaclaw",
3
+ "version": "1.0.0",
4
+ "description": "Persistent memory for AI agents using Supabase - sessions, memories, semantic search",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "supaclaw": "dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist/**/*",
12
+ "migrations/**/*",
13
+ "README.md",
14
+ "LICENSE",
15
+ "SCHEMA.md"
16
+ ],
17
+ "sideEffects": false,
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "dev": "tsc --watch",
21
+ "test": "vitest",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "keywords": [
25
+ "ai",
26
+ "agents",
27
+ "memory",
28
+ "supabase",
29
+ "postgres",
30
+ "vector-search",
31
+ "semantic-search",
32
+ "llm",
33
+ "context-management",
34
+ "openclaw",
35
+ "clawdbot",
36
+ "ai-memory",
37
+ "conversation-history",
38
+ "chat-history",
39
+ "context-window",
40
+ "rag"
41
+ ],
42
+ "author": "Han Kim",
43
+ "license": "MIT",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/Arephan/supaclaw.git"
47
+ },
48
+ "bugs": {
49
+ "url": "https://github.com/Arephan/supaclaw/issues"
50
+ },
51
+ "homepage": "https://github.com/Arephan/supaclaw#readme",
52
+ "dependencies": {
53
+ "@supabase/supabase-js": "^2.39.0",
54
+ "commander": "^14.0.3",
55
+ "openai": "^6.17.0"
56
+ },
57
+ "devDependencies": {
58
+ "@types/commander": "^2.12.0",
59
+ "@types/node": "^20.10.0",
60
+ "typescript": "^5.3.0",
61
+ "vitest": "^1.0.0"
62
+ },
63
+ "engines": {
64
+ "node": ">=18.0.0"
65
+ }
66
+ }