@totalreclaw/totalreclaw 1.6.0 → 3.0.6
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/CLAWHUB.md +134 -0
- package/README.md +407 -64
- package/SKILL.md +1032 -0
- package/api-client.ts +5 -5
- package/claims-helper.ts +686 -0
- package/config.ts +211 -0
- package/consolidation.ts +141 -33
- package/contradiction-sync.ts +1389 -0
- package/crypto.ts +63 -261
- package/digest-sync.ts +516 -0
- package/embedding.ts +69 -46
- package/extractor.ts +1307 -84
- package/hot-cache-wrapper.ts +1 -1
- package/import-adapters/gemini-adapter.ts +243 -0
- package/import-adapters/index.ts +3 -0
- package/import-adapters/types.ts +1 -1
- package/index.ts +1887 -323
- package/llm-client.ts +106 -53
- package/lsh.ts +21 -210
- package/package.json +20 -7
- package/pin.ts +502 -0
- package/reranker.ts +96 -124
- package/skill.json +213 -0
- package/subgraph-search.ts +112 -5
- package/subgraph-store.ts +559 -275
- package/consolidation.test.ts +0 -356
- package/extractor-dedup.test.ts +0 -168
- package/import-adapters/import-adapters.test.ts +0 -1123
- package/lsh.test.ts +0 -463
- package/pocv2-e2e-test.ts +0 -917
- package/porter-stemmer.d.ts +0 -4
- package/reranker.test.ts +0 -594
- package/semantic-dedup.test.ts +0 -392
- package/setup.sh +0 -19
- package/store-dedup-wiring.test.ts +0 -186
package/reranker.ts
CHANGED
|
@@ -4,18 +4,46 @@
|
|
|
4
4
|
* Replaces the naive `textScore` word-overlap scorer with a proper ranking
|
|
5
5
|
* pipeline:
|
|
6
6
|
* 1. Okapi BM25 — term frequency / inverse document frequency
|
|
7
|
-
* 2. Cosine similarity — between query and fact embeddings
|
|
7
|
+
* 2. Cosine similarity — between query and fact embeddings (WASM-backed)
|
|
8
8
|
* 3. Importance — normalized importance score (0-1)
|
|
9
9
|
* 4. Recency — time-decay with 1-week half-life
|
|
10
10
|
* 5. Weighted RRF (Reciprocal Rank Fusion) — combines all ranking lists
|
|
11
11
|
* 6. MMR (Maximal Marginal Relevance) — promotes diversity in results
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* Cosine similarity delegates to the Rust WASM core for performance.
|
|
14
|
+
* All other functions are pure TypeScript. This module runs CLIENT-SIDE
|
|
15
|
+
* after decrypting candidates from the server.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Cosine Similarity
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Compute cosine similarity between two vectors.
|
|
24
|
+
*
|
|
25
|
+
* Returns dot(a, b) / (||a|| * ||b||).
|
|
26
|
+
* Returns 0 if either vector has zero magnitude (avoids division by zero).
|
|
27
|
+
*/
|
|
28
|
+
export function cosineSimilarity(a: number[], b: number[]): number {
|
|
29
|
+
if (a.length === 0 || b.length === 0) return 0;
|
|
30
|
+
|
|
31
|
+
const len = Math.min(a.length, b.length);
|
|
32
|
+
let dot = 0;
|
|
33
|
+
let normA = 0;
|
|
34
|
+
let normB = 0;
|
|
35
|
+
|
|
36
|
+
for (let i = 0; i < len; i++) {
|
|
37
|
+
dot += a[i] * b[i];
|
|
38
|
+
normA += a[i] * a[i];
|
|
39
|
+
normB += b[i] * b[i];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
43
|
+
if (denom === 0) return 0;
|
|
44
|
+
|
|
45
|
+
return dot / denom;
|
|
46
|
+
}
|
|
19
47
|
|
|
20
48
|
// ---------------------------------------------------------------------------
|
|
21
49
|
// Tokenization
|
|
@@ -30,8 +58,8 @@ import { stemmer } from 'porter-stemmer';
|
|
|
30
58
|
* 3. Split on whitespace
|
|
31
59
|
* 4. Filter tokens shorter than 2 characters
|
|
32
60
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
61
|
+
* Removes common English stop words to improve BM25 signal — stop words
|
|
62
|
+
* have low IDF and add noise.
|
|
35
63
|
*/
|
|
36
64
|
const STOP_WORDS = new Set([
|
|
37
65
|
'a', 'an', 'and', 'are', 'as', 'at', 'be', 'but', 'by', 'do', 'for',
|
|
@@ -54,9 +82,7 @@ export function tokenize(text: string, removeStopWords: boolean = true): string[
|
|
|
54
82
|
tokens = tokens.filter((t) => !STOP_WORDS.has(t));
|
|
55
83
|
}
|
|
56
84
|
|
|
57
|
-
|
|
58
|
-
// This ensures BM25 matches "gaming" with "games" (both stem to "game").
|
|
59
|
-
return tokens.map((t) => stemmer(t));
|
|
85
|
+
return tokens;
|
|
60
86
|
}
|
|
61
87
|
|
|
62
88
|
// ---------------------------------------------------------------------------
|
|
@@ -66,17 +92,6 @@ export function tokenize(text: string, removeStopWords: boolean = true): string[
|
|
|
66
92
|
/**
|
|
67
93
|
* Compute the Okapi BM25 score for a single document against a query.
|
|
68
94
|
*
|
|
69
|
-
* Formula:
|
|
70
|
-
* score = SUM_i IDF(qi) * (f(qi, D) * (k1 + 1)) / (f(qi, D) + k1 * (1 - b + b * |D| / avgdl))
|
|
71
|
-
*
|
|
72
|
-
* where:
|
|
73
|
-
* IDF(qi) = ln((N - n(qi) + 0.5) / (n(qi) + 0.5) + 1)
|
|
74
|
-
* f(qi, D) = frequency of term qi in document D
|
|
75
|
-
* |D| = length of document D (in tokens)
|
|
76
|
-
* avgdl = average document length across the corpus
|
|
77
|
-
* N = total number of documents
|
|
78
|
-
* n(qi) = number of documents containing term qi
|
|
79
|
-
*
|
|
80
95
|
* @param queryTerms - Tokenized query terms
|
|
81
96
|
* @param docTerms - Tokenized document terms
|
|
82
97
|
* @param avgDocLen - Average document length (in tokens) across the candidate corpus
|
|
@@ -112,7 +127,6 @@ export function bm25Score(
|
|
|
112
127
|
const nqi = termDocFreqs.get(qi) ?? 0;
|
|
113
128
|
|
|
114
129
|
// IDF with Robertson-Walker floor: ln((N - n + 0.5) / (n + 0.5) + 1)
|
|
115
|
-
// The +1 inside ln ensures IDF is always >= 0 even when n > N/2.
|
|
116
130
|
const idf = Math.log((docCount - nqi + 0.5) / (nqi + 0.5) + 1);
|
|
117
131
|
|
|
118
132
|
// TF saturation with length normalization.
|
|
@@ -124,36 +138,6 @@ export function bm25Score(
|
|
|
124
138
|
return score;
|
|
125
139
|
}
|
|
126
140
|
|
|
127
|
-
// ---------------------------------------------------------------------------
|
|
128
|
-
// Cosine Similarity
|
|
129
|
-
// ---------------------------------------------------------------------------
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Compute cosine similarity between two vectors.
|
|
133
|
-
*
|
|
134
|
-
* Returns dot(a, b) / (||a|| * ||b||).
|
|
135
|
-
* Returns 0 if either vector has zero magnitude (avoids division by zero).
|
|
136
|
-
*/
|
|
137
|
-
export function cosineSimilarity(a: number[], b: number[]): number {
|
|
138
|
-
if (a.length === 0 || b.length === 0) return 0;
|
|
139
|
-
|
|
140
|
-
const len = Math.min(a.length, b.length);
|
|
141
|
-
let dot = 0;
|
|
142
|
-
let normA = 0;
|
|
143
|
-
let normB = 0;
|
|
144
|
-
|
|
145
|
-
for (let i = 0; i < len; i++) {
|
|
146
|
-
dot += a[i] * b[i];
|
|
147
|
-
normA += a[i] * a[i];
|
|
148
|
-
normB += b[i] * b[i];
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
152
|
-
if (denom === 0) return 0;
|
|
153
|
-
|
|
154
|
-
return dot / denom;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
141
|
// ---------------------------------------------------------------------------
|
|
158
142
|
// Reciprocal Rank Fusion (RRF)
|
|
159
143
|
// ---------------------------------------------------------------------------
|
|
@@ -165,18 +149,6 @@ export interface RankedItem {
|
|
|
165
149
|
|
|
166
150
|
/**
|
|
167
151
|
* Fuse multiple ranking lists using Reciprocal Rank Fusion.
|
|
168
|
-
*
|
|
169
|
-
* For each document d appearing in any ranking list:
|
|
170
|
-
* rrfScore(d) = SUM_i 1 / (k + rank_i(d))
|
|
171
|
-
*
|
|
172
|
-
* where rank_i(d) is the 1-based rank of document d in the i-th list.
|
|
173
|
-
* Documents not present in a list are not penalized (they simply receive
|
|
174
|
-
* no contribution from that list).
|
|
175
|
-
*
|
|
176
|
-
* @param rankings - Array of ranking lists, each sorted by score descending.
|
|
177
|
-
* Each item has an `id` and a `score`.
|
|
178
|
-
* @param k - RRF smoothing constant (default 60, per the original paper).
|
|
179
|
-
* @returns - Fused ranking sorted by RRF score descending.
|
|
180
152
|
*/
|
|
181
153
|
export function rrfFuse(
|
|
182
154
|
rankings: RankedItem[][],
|
|
@@ -187,7 +159,7 @@ export function rrfFuse(
|
|
|
187
159
|
for (const ranking of rankings) {
|
|
188
160
|
for (let rank = 0; rank < ranking.length; rank++) {
|
|
189
161
|
const item = ranking[rank];
|
|
190
|
-
const contribution = 1 / (k + rank + 1);
|
|
162
|
+
const contribution = 1 / (k + rank + 1);
|
|
191
163
|
fusedScores.set(item.id, (fusedScores.get(item.id) ?? 0) + contribution);
|
|
192
164
|
}
|
|
193
165
|
}
|
|
@@ -207,14 +179,6 @@ export function rrfFuse(
|
|
|
207
179
|
|
|
208
180
|
/**
|
|
209
181
|
* Fuse multiple ranking lists using Weighted Reciprocal Rank Fusion.
|
|
210
|
-
*
|
|
211
|
-
* Like standard RRF, but each ranking list's contribution is multiplied by
|
|
212
|
-
* its weight, allowing callers to emphasize or de-emphasize specific signals.
|
|
213
|
-
*
|
|
214
|
-
* @param rankings - Array of ranking lists, each sorted by score descending.
|
|
215
|
-
* @param weights - Weight for each ranking list (same length as rankings).
|
|
216
|
-
* @param k - RRF smoothing constant (default 60).
|
|
217
|
-
* @returns - Fused ranking sorted by weighted RRF score descending.
|
|
218
182
|
*/
|
|
219
183
|
export function weightedRrfFuse(
|
|
220
184
|
rankings: RankedItem[][],
|
|
@@ -279,7 +243,7 @@ export const INTENT_WEIGHTS: Record<QueryIntent, RankingWeights> = {
|
|
|
279
243
|
|
|
280
244
|
/**
|
|
281
245
|
* Classify a query into one of three intent types using lightweight heuristics.
|
|
282
|
-
* Temporal is checked first so "What did we discuss yesterday?"
|
|
246
|
+
* Temporal is checked first so "What did we discuss yesterday?" -> temporal.
|
|
283
247
|
*/
|
|
284
248
|
export function detectQueryIntent(query: string): QueryIntent {
|
|
285
249
|
if (TEMPORAL_KEYWORDS.test(query)) return 'temporal';
|
|
@@ -293,11 +257,49 @@ export interface RerankerCandidate {
|
|
|
293
257
|
embedding?: number[];
|
|
294
258
|
importance?: number; // 0-1 normalized importance score
|
|
295
259
|
createdAt?: number; // Unix timestamp (seconds) when fact was created
|
|
260
|
+
/**
|
|
261
|
+
* Memory Taxonomy v1 provenance tag. Plugin v3.0.0+ surfaces this when a
|
|
262
|
+
* candidate was decrypted from a v1 blob. When present and
|
|
263
|
+
* `applySourceWeights: true` is passed to rerank(), the final RRF score
|
|
264
|
+
* is multiplied by the Retrieval v2 Tier 1 source weight from core.
|
|
265
|
+
*/
|
|
266
|
+
source?: string;
|
|
296
267
|
}
|
|
297
268
|
|
|
298
269
|
export interface RerankerResult extends RerankerCandidate {
|
|
299
270
|
rrfScore: number;
|
|
300
271
|
cosineSimilarity?: number;
|
|
272
|
+
/** Source weight multiplier applied (1.0 = no weighting). */
|
|
273
|
+
sourceWeight?: number;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
// Source-weight lookup (Retrieval v2 Tier 1)
|
|
278
|
+
//
|
|
279
|
+
// Mirrors the table in `rust/totalreclaw-core/src/reranker.rs` exactly so
|
|
280
|
+
// the TypeScript reranker produces the same ordering as core rerankWithConfig
|
|
281
|
+
// when `applySourceWeights: true` is passed.
|
|
282
|
+
//
|
|
283
|
+
// NOTE: this is duplicated here (vs calling core via WASM) because the
|
|
284
|
+
// plugin's local reranker handles RRF + MMR on the client side with rich
|
|
285
|
+
// candidate metadata. The core `rerankWithConfig` is the canonical source
|
|
286
|
+
// of truth and will be used directly by MCP/Python adapters.
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
const SOURCE_WEIGHTS: Record<string, number> = {
|
|
290
|
+
'user': 1.0,
|
|
291
|
+
'user-inferred': 0.9,
|
|
292
|
+
'derived': 0.7,
|
|
293
|
+
'external': 0.7,
|
|
294
|
+
'assistant': 0.55,
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const LEGACY_FALLBACK_WEIGHT = 0.85;
|
|
298
|
+
|
|
299
|
+
export function getSourceWeight(source: string | undefined): number {
|
|
300
|
+
if (!source) return LEGACY_FALLBACK_WEIGHT;
|
|
301
|
+
const w = SOURCE_WEIGHTS[source.toLowerCase()];
|
|
302
|
+
return w ?? 0.85; // unknown source → moderate penalty
|
|
301
303
|
}
|
|
302
304
|
|
|
303
305
|
// ---------------------------------------------------------------------------
|
|
@@ -306,14 +308,6 @@ export interface RerankerResult extends RerankerCandidate {
|
|
|
306
308
|
|
|
307
309
|
/**
|
|
308
310
|
* Compute a recency score with a 1-week half-life.
|
|
309
|
-
*
|
|
310
|
-
* Score = 1 / (1 + hours_since_creation / 168)
|
|
311
|
-
*
|
|
312
|
-
* A fact created just now scores ~1.0, one week ago scores 0.5,
|
|
313
|
-
* two weeks ago scores ~0.33, etc.
|
|
314
|
-
*
|
|
315
|
-
* @param createdAt - Unix timestamp in seconds
|
|
316
|
-
* @returns - Recency score in (0, 1]
|
|
317
311
|
*/
|
|
318
312
|
function recencyScore(createdAt: number): number {
|
|
319
313
|
const nowSeconds = Date.now() / 1000;
|
|
@@ -327,21 +321,6 @@ function recencyScore(createdAt: number): number {
|
|
|
327
321
|
|
|
328
322
|
/**
|
|
329
323
|
* Apply Maximal Marginal Relevance to promote diversity in results.
|
|
330
|
-
*
|
|
331
|
-
* MMR re-orders a ranked list of candidates so that highly similar candidates
|
|
332
|
-
* are spread out. The algorithm greedily selects the candidate that maximizes:
|
|
333
|
-
*
|
|
334
|
-
* MMR(d) = lambda * relevance(d) - (1 - lambda) * max_sim(d, selected)
|
|
335
|
-
*
|
|
336
|
-
* where:
|
|
337
|
-
* - relevance(d) = position-based score (1.0 for first, linearly decreasing)
|
|
338
|
-
* - max_sim(d, selected) = max cosine similarity between d and any already
|
|
339
|
-
* selected candidate (0 if no embeddings available)
|
|
340
|
-
*
|
|
341
|
-
* @param candidates - Candidates in relevance order (best first)
|
|
342
|
-
* @param lambda - Trade-off between relevance and diversity (default 0.7)
|
|
343
|
-
* @param topK - Number of results to return (default 8)
|
|
344
|
-
* @returns - Re-ordered candidates with diversity
|
|
345
324
|
*/
|
|
346
325
|
export function applyMMR(
|
|
347
326
|
candidates: RerankerCandidate[],
|
|
@@ -402,31 +381,12 @@ export function applyMMR(
|
|
|
402
381
|
* Re-rank decrypted candidates using BM25 + Cosine + Importance + Recency
|
|
403
382
|
* with Weighted RRF fusion and MMR diversity.
|
|
404
383
|
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
* 6. Score each candidate by recency
|
|
412
|
-
* 7. Fuse all 4 rankings with weighted RRF
|
|
413
|
-
* 8. Apply MMR for diversity
|
|
414
|
-
* 9. Return top-k candidates sorted by fused score
|
|
415
|
-
*
|
|
416
|
-
* Backward compatibility:
|
|
417
|
-
* - Candidates without embeddings get cosine score = 0 and are excluded
|
|
418
|
-
* from the cosine ranking list. They can still rank well via other signals.
|
|
419
|
-
* - If NO candidates have embeddings, cosine ranking is omitted.
|
|
420
|
-
* - Candidates without importance get neutral score (0.5).
|
|
421
|
-
* - Candidates without createdAt get neutral recency score (0.5).
|
|
422
|
-
* - topK defaults to 8, weights default to equal (0.25 each).
|
|
423
|
-
*
|
|
424
|
-
* @param query - The user's search query (plaintext)
|
|
425
|
-
* @param queryEmbedding - Embedding vector for the query
|
|
426
|
-
* @param candidates - Decrypted candidates with text and optional embeddings
|
|
427
|
-
* @param topK - Number of results to return (default 8)
|
|
428
|
-
* @param weights - Optional partial ranking weights (merged with defaults)
|
|
429
|
-
* @returns - Top-k candidates sorted by fused score, with scores attached
|
|
384
|
+
* When `applySourceWeights` is true, the final RRF score for each candidate
|
|
385
|
+
* is multiplied by a Retrieval v2 Tier 1 source weight based on the
|
|
386
|
+
* candidate's `source` field (user=1.0, user-inferred=0.9, derived/external=0.7,
|
|
387
|
+
* assistant=0.55). Candidates without a `source` field use the legacy
|
|
388
|
+
* fallback weight (0.85). This is the flag equivalent of core
|
|
389
|
+
* `rerankWithConfig(.., apply_source_weights=true)`.
|
|
430
390
|
*/
|
|
431
391
|
export function rerank(
|
|
432
392
|
query: string,
|
|
@@ -434,6 +394,7 @@ export function rerank(
|
|
|
434
394
|
candidates: RerankerCandidate[],
|
|
435
395
|
topK: number = 8,
|
|
436
396
|
weights?: Partial<RankingWeights>,
|
|
397
|
+
applySourceWeights: boolean = false,
|
|
437
398
|
): RerankerResult[] {
|
|
438
399
|
if (candidates.length === 0) return [];
|
|
439
400
|
|
|
@@ -448,7 +409,6 @@ export function rerank(
|
|
|
448
409
|
const docCount = candidates.length;
|
|
449
410
|
let totalDocLen = 0;
|
|
450
411
|
|
|
451
|
-
// Count how many documents contain each term.
|
|
452
412
|
const termDocFreqs = new Map<string, number>();
|
|
453
413
|
for (const terms of candidateTerms) {
|
|
454
414
|
totalDocLen += terms.length;
|
|
@@ -521,14 +481,26 @@ export function rerank(
|
|
|
521
481
|
for (const item of fused) {
|
|
522
482
|
const candidate = candidateMap.get(item.id);
|
|
523
483
|
if (candidate) {
|
|
484
|
+
const sourceWeight = applySourceWeights
|
|
485
|
+
? getSourceWeight(candidate.source)
|
|
486
|
+
: 1.0;
|
|
524
487
|
rrfResults.push({
|
|
525
488
|
...candidate,
|
|
526
|
-
rrfScore: item.score,
|
|
489
|
+
rrfScore: item.score * sourceWeight,
|
|
527
490
|
cosineSimilarity: cosineScores.get(item.id),
|
|
491
|
+
sourceWeight: applySourceWeights ? sourceWeight : undefined,
|
|
528
492
|
});
|
|
529
493
|
}
|
|
530
494
|
}
|
|
531
495
|
|
|
496
|
+
// When source weights are applied the RRF-scaled scores may no longer be in
|
|
497
|
+
// descending order (weighted=0.55 assistant could slip below a weighted=1.0
|
|
498
|
+
// user fact that was originally ranked lower). Re-sort so the top-K picked
|
|
499
|
+
// by MMR is meaningful.
|
|
500
|
+
if (applySourceWeights) {
|
|
501
|
+
rrfResults.sort((a, b) => b.rrfScore - a.rrfScore);
|
|
502
|
+
}
|
|
503
|
+
|
|
532
504
|
// --- Step 9: Apply MMR for diversity, then return top-k ---
|
|
533
505
|
const mmrResults = applyMMR(rrfResults, 0.7, topK);
|
|
534
506
|
|
package/skill.json
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "totalreclaw",
|
|
3
|
+
"version": "1.6.1",
|
|
4
|
+
"description": "End-to-end encrypted memory for AI agents — portable, yours forever. XChaCha20-Poly1305 E2EE: server never sees plaintext.",
|
|
5
|
+
"author": "TotalReclaw Team",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/p-diogo/totalreclaw",
|
|
8
|
+
"repository": "https://github.com/p-diogo/totalreclaw",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"memory",
|
|
11
|
+
"e2ee",
|
|
12
|
+
"e2e-encryption",
|
|
13
|
+
"encryption",
|
|
14
|
+
"privacy",
|
|
15
|
+
"agent-memory",
|
|
16
|
+
"persistent-context"
|
|
17
|
+
],
|
|
18
|
+
"openclaw": {
|
|
19
|
+
"minVersion": "0.1.0",
|
|
20
|
+
"maxVersion": "1.0.0",
|
|
21
|
+
"requires": {
|
|
22
|
+
"env": [],
|
|
23
|
+
"bins": []
|
|
24
|
+
},
|
|
25
|
+
"emoji": "🧠",
|
|
26
|
+
"os": ["macos", "linux", "windows"],
|
|
27
|
+
"hooks": {
|
|
28
|
+
"before_agent_start": {
|
|
29
|
+
"priority": 10,
|
|
30
|
+
"description": "Retrieve relevant memories before agent processes message"
|
|
31
|
+
},
|
|
32
|
+
"agent_end": {
|
|
33
|
+
"priority": 90,
|
|
34
|
+
"description": "Extract and store facts after agent completes turn"
|
|
35
|
+
},
|
|
36
|
+
"pre_compaction": {
|
|
37
|
+
"priority": 5,
|
|
38
|
+
"description": "Full memory flush before context compaction"
|
|
39
|
+
},
|
|
40
|
+
"before_reset": {
|
|
41
|
+
"priority": 5,
|
|
42
|
+
"description": "Full memory flush before conversation reset"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"tools": [
|
|
46
|
+
{
|
|
47
|
+
"name": "totalreclaw_remember",
|
|
48
|
+
"description": "Store a new fact or preference in long-term memory",
|
|
49
|
+
"parameters": {
|
|
50
|
+
"text": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"required": true,
|
|
53
|
+
"description": "The fact or information to remember"
|
|
54
|
+
},
|
|
55
|
+
"type": {
|
|
56
|
+
"type": "string",
|
|
57
|
+
"required": false,
|
|
58
|
+
"enum": ["fact", "preference", "decision", "episodic", "goal"],
|
|
59
|
+
"default": "fact",
|
|
60
|
+
"description": "Type of memory"
|
|
61
|
+
},
|
|
62
|
+
"importance": {
|
|
63
|
+
"type": "integer",
|
|
64
|
+
"required": false,
|
|
65
|
+
"min": 1,
|
|
66
|
+
"max": 10,
|
|
67
|
+
"description": "Importance score 1-10. Default: auto-detected by LLM"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"name": "totalreclaw_recall",
|
|
73
|
+
"description": "Search and retrieve relevant memories from long-term storage",
|
|
74
|
+
"parameters": {
|
|
75
|
+
"query": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"required": true,
|
|
78
|
+
"description": "Natural language query to search memories"
|
|
79
|
+
},
|
|
80
|
+
"k": {
|
|
81
|
+
"type": "integer",
|
|
82
|
+
"required": false,
|
|
83
|
+
"default": 8,
|
|
84
|
+
"max": 20,
|
|
85
|
+
"description": "Number of results to return"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"name": "totalreclaw_forget",
|
|
91
|
+
"description": "Delete a specific fact from memory",
|
|
92
|
+
"parameters": {
|
|
93
|
+
"factId": {
|
|
94
|
+
"type": "string",
|
|
95
|
+
"required": true,
|
|
96
|
+
"description": "UUID of the fact to delete"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"name": "totalreclaw_export",
|
|
102
|
+
"description": "Export all stored memories in plaintext format",
|
|
103
|
+
"parameters": {
|
|
104
|
+
"format": {
|
|
105
|
+
"type": "string",
|
|
106
|
+
"required": false,
|
|
107
|
+
"enum": ["json", "markdown"],
|
|
108
|
+
"default": "json",
|
|
109
|
+
"description": "Export format"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"name": "totalreclaw_status",
|
|
115
|
+
"description": "Check billing and subscription status, including quota usage and upgrade options",
|
|
116
|
+
"parameters": {}
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"name": "totalreclaw_upgrade",
|
|
120
|
+
"description": "Get a checkout URL to upgrade to TotalReclaw Pro (unlimited memories on Gnosis mainnet)",
|
|
121
|
+
"parameters": {}
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"name": "totalreclaw_import_from",
|
|
125
|
+
"description": "Import memories from other AI memory tools (Mem0, MCP Memory Server) into TotalReclaw",
|
|
126
|
+
"parameters": {
|
|
127
|
+
"source": {
|
|
128
|
+
"type": "string",
|
|
129
|
+
"required": true,
|
|
130
|
+
"enum": ["mem0", "mcp-memory"],
|
|
131
|
+
"description": "Source system to import from"
|
|
132
|
+
},
|
|
133
|
+
"api_key": {
|
|
134
|
+
"type": "string",
|
|
135
|
+
"required": false,
|
|
136
|
+
"description": "API key for the source (Mem0). Used once, never stored."
|
|
137
|
+
},
|
|
138
|
+
"source_user_id": {
|
|
139
|
+
"type": "string",
|
|
140
|
+
"required": false,
|
|
141
|
+
"description": "User or agent ID in the source system"
|
|
142
|
+
},
|
|
143
|
+
"content": {
|
|
144
|
+
"type": "string",
|
|
145
|
+
"required": false,
|
|
146
|
+
"description": "File content (JSON, JSONL, or CSV) for file-based sources"
|
|
147
|
+
},
|
|
148
|
+
"file_path": {
|
|
149
|
+
"type": "string",
|
|
150
|
+
"required": false,
|
|
151
|
+
"description": "Path to a file on disk for file-based sources"
|
|
152
|
+
},
|
|
153
|
+
"namespace": {
|
|
154
|
+
"type": "string",
|
|
155
|
+
"required": false,
|
|
156
|
+
"default": "imported",
|
|
157
|
+
"description": "Target namespace in TotalReclaw"
|
|
158
|
+
},
|
|
159
|
+
"dry_run": {
|
|
160
|
+
"type": "boolean",
|
|
161
|
+
"required": false,
|
|
162
|
+
"default": false,
|
|
163
|
+
"description": "Preview without importing"
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"name": "totalreclaw_consolidate",
|
|
169
|
+
"description": "Scan all stored memories and merge near-duplicates, keeping the most important/recent version",
|
|
170
|
+
"parameters": {
|
|
171
|
+
"dry_run": {
|
|
172
|
+
"type": "boolean",
|
|
173
|
+
"required": false,
|
|
174
|
+
"default": false,
|
|
175
|
+
"description": "Preview consolidation without deleting"
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
],
|
|
180
|
+
"config": {
|
|
181
|
+
"serverUrl": {
|
|
182
|
+
"type": "string",
|
|
183
|
+
"default": "https://api.totalreclaw.xyz",
|
|
184
|
+
"description": "TotalReclaw server URL (only change for self-hosted mode)"
|
|
185
|
+
},
|
|
186
|
+
"autoExtractEveryTurns": {
|
|
187
|
+
"type": "number",
|
|
188
|
+
"default": 3,
|
|
189
|
+
"description": "Number of turns between automatic extractions"
|
|
190
|
+
},
|
|
191
|
+
"minImportanceForAutoStore": {
|
|
192
|
+
"type": "number",
|
|
193
|
+
"default": 6,
|
|
194
|
+
"description": "Minimum importance (1-10) to auto-store memories"
|
|
195
|
+
},
|
|
196
|
+
"maxMemoriesInContext": {
|
|
197
|
+
"type": "number",
|
|
198
|
+
"default": 8,
|
|
199
|
+
"description": "Maximum memories to inject into context"
|
|
200
|
+
},
|
|
201
|
+
"forgetThreshold": {
|
|
202
|
+
"type": "number",
|
|
203
|
+
"default": 0.3,
|
|
204
|
+
"description": "Decay score threshold for eviction"
|
|
205
|
+
},
|
|
206
|
+
"rerankerModel": {
|
|
207
|
+
"type": "string",
|
|
208
|
+
"default": "BAAI/bge-reranker-base",
|
|
209
|
+
"description": "ONNX reranker model for result reranking"
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|