@persistio/openclaw-plugin 0.1.4 → 0.1.5

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 CHANGED
@@ -26,6 +26,7 @@ Then register it in your OpenClaw config:
26
26
  "config": {
27
27
  "baseURL": "https://api.persistio.ai",
28
28
  "apiKey": "your-vault-api-key",
29
+ "recallMinSimilarity": 0.3,
29
30
  "send": {
30
31
  "roles": {
31
32
  "user": "enabled",
@@ -48,6 +49,7 @@ Then register it in your OpenClaw config:
48
49
  | `apiKey` | string | ✅ | — | Vault API key |
49
50
  | `tokenBudget` | number | | `2000` | Max tokens to inject into the system prompt |
50
51
  | `recallTopK` | number | | `10` | Number of memories to retrieve per recall |
52
+ | `recallMinSimilarity` | number from `0` to `1` | | Persistio server default | Optional semantic recall quality floor |
51
53
  | `recallTimeout` | number | | `5000` | HTTP timeout for recall requests (ms) |
52
54
  | `send.roles.user` | `"enabled"` or `"disabled"` | | `"enabled"` | Send user messages to Persistio ingest |
53
55
  | `send.roles.agent` | `"enabled"` or `"disabled"` | | `"enabled"` | Send agent/assistant messages to Persistio ingest |
package/dist/client.d.ts CHANGED
@@ -3,6 +3,7 @@ export interface PersistioConfig {
3
3
  apiKey: string;
4
4
  tokenBudget: number;
5
5
  recallTopK: number;
6
+ recallMinSimilarity?: number;
6
7
  recallTimeout: number;
7
8
  send: PersistioSendConfig;
8
9
  }
@@ -22,7 +23,11 @@ export interface PersistioMemory {
22
23
  categories: string[];
23
24
  confidence: number;
24
25
  }
26
+ export interface GetMemoryOptions {
27
+ includePending?: boolean;
28
+ }
25
29
  export interface RecallBundle {
30
+ global_user_rules?: string[];
26
31
  user_rules: string[];
27
32
  user_preferences: string[];
28
33
  task_patterns: string[];
@@ -35,16 +40,18 @@ export interface RecallBundle {
35
40
  }
36
41
  export interface RecallBundleResponse {
37
42
  bundle: RecallBundle;
43
+ related_bundle?: RecallBundle;
38
44
  }
39
45
  export declare class PersistioClient {
40
46
  private readonly baseURL;
41
47
  private readonly apiKey;
42
48
  private readonly recallTopK;
49
+ private readonly recallMinSimilarity?;
43
50
  private readonly recallTimeout;
44
51
  constructor(config: PersistioConfig);
45
52
  private headers;
46
53
  recall(query: string): Promise<PersistioMemory[]>;
47
- recallBundle(query: string, topK?: number): Promise<RecallBundle>;
54
+ recallBundle(query: string, topK?: number): Promise<RecallBundleResponse>;
48
55
  ingest(sessionId: string, chunks: Array<{
49
56
  role: string;
50
57
  content: string;
@@ -52,5 +59,6 @@ export declare class PersistioClient {
52
59
  }>): Promise<void>;
53
60
  addMemory(data: string, subject: string): Promise<void>;
54
61
  deleteMemory(id: string): Promise<void>;
62
+ getMemory(id: string, options?: GetMemoryOptions): Promise<PersistioMemory | null>;
55
63
  listMemories(): Promise<PersistioMemory[]>;
56
64
  }
package/dist/client.js CHANGED
@@ -2,11 +2,13 @@ export class PersistioClient {
2
2
  baseURL;
3
3
  apiKey;
4
4
  recallTopK;
5
+ recallMinSimilarity;
5
6
  recallTimeout;
6
7
  constructor(config) {
7
8
  this.baseURL = config.baseURL.replace(/\/$/, '');
8
9
  this.apiKey = config.apiKey;
9
10
  this.recallTopK = config.recallTopK;
11
+ this.recallMinSimilarity = config.recallMinSimilarity;
10
12
  this.recallTimeout = config.recallTimeout;
11
13
  }
12
14
  headers() {
@@ -16,10 +18,14 @@ export class PersistioClient {
16
18
  };
17
19
  }
18
20
  async recall(query) {
21
+ const body = { query, top_k: this.recallTopK, include_pending: true };
22
+ if (typeof this.recallMinSimilarity === 'number') {
23
+ body.min_similarity = this.recallMinSimilarity;
24
+ }
19
25
  const res = await fetch(`${this.baseURL}/v1/recall`, {
20
26
  method: 'POST',
21
27
  headers: this.headers(),
22
- body: JSON.stringify({ query, top_k: this.recallTopK }),
28
+ body: JSON.stringify(body),
23
29
  signal: AbortSignal.timeout(this.recallTimeout),
24
30
  });
25
31
  if (!res.ok)
@@ -28,16 +34,20 @@ export class PersistioClient {
28
34
  return data.memories ?? [];
29
35
  }
30
36
  async recallBundle(query, topK) {
37
+ const body = { query, top_k: topK ?? this.recallTopK, include_pending: true };
38
+ if (typeof this.recallMinSimilarity === 'number') {
39
+ body.min_similarity = this.recallMinSimilarity;
40
+ }
31
41
  const res = await fetch(`${this.baseURL}/v1/recall?format=bundle`, {
32
42
  method: 'POST',
33
43
  headers: this.headers(),
34
- body: JSON.stringify({ query, top_k: topK ?? this.recallTopK }),
44
+ body: JSON.stringify(body),
35
45
  signal: AbortSignal.timeout(this.recallTimeout),
36
46
  });
37
47
  if (!res.ok)
38
48
  throw new Error(`Persistio recallBundle failed: ${res.status}`);
39
49
  const data = await res.json();
40
- return data.bundle;
50
+ return data;
41
51
  }
42
52
  async ingest(sessionId, chunks) {
43
53
  if (chunks.length === 0)
@@ -67,6 +77,17 @@ export class PersistioClient {
67
77
  if (!res.ok)
68
78
  throw new Error(`Persistio deleteMemory failed: ${res.status}`);
69
79
  }
80
+ async getMemory(id, options = {}) {
81
+ const query = options.includePending ? '?include_pending=true' : '';
82
+ const res = await fetch(`${this.baseURL}/v1/memories/${id}${query}`, {
83
+ headers: this.headers(),
84
+ });
85
+ if (res.status === 404)
86
+ return null;
87
+ if (!res.ok)
88
+ throw new Error(`Persistio getMemory failed: ${res.status}`);
89
+ return await res.json();
90
+ }
70
91
  async listMemories() {
71
92
  const res = await fetch(`${this.baseURL}/v1/memories`, {
72
93
  headers: this.headers(),
package/dist/index.js CHANGED
@@ -25,6 +25,11 @@ function resolveSendConfig(raw) {
25
25
  },
26
26
  };
27
27
  }
28
+ function resolveRecallMinSimilarity(value) {
29
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1
30
+ ? value
31
+ : undefined;
32
+ }
28
33
  function resolveConfig(raw) {
29
34
  const c = (raw ?? {});
30
35
  return {
@@ -32,6 +37,7 @@ function resolveConfig(raw) {
32
37
  apiKey: typeof c['apiKey'] === 'string' ? c['apiKey'] : '',
33
38
  tokenBudget: typeof c['tokenBudget'] === 'number' ? c['tokenBudget'] : 2000,
34
39
  recallTopK: typeof c['recallTopK'] === 'number' ? c['recallTopK'] : 10,
40
+ recallMinSimilarity: resolveRecallMinSimilarity(c['recallMinSimilarity']),
35
41
  recallTimeout: typeof c['recallTimeout'] === 'number' ? c['recallTimeout'] : 5000,
36
42
  send: resolveSendConfig(c),
37
43
  };
@@ -102,7 +108,7 @@ function buildRecallQuery(event) {
102
108
  parts.push(`[task: ${taskType}]`);
103
109
  return truncate(parts.join('\n'), 600);
104
110
  }
105
- function buildMemoryBlock(bundle, budget) {
111
+ function buildMemoryBlock(bundle, budget, relatedBundle) {
106
112
  const sections = [
107
113
  { title: 'Behavioural rules', items: bundle.user_rules },
108
114
  { title: 'Preferences', items: bundle.user_preferences },
@@ -114,6 +120,9 @@ function buildMemoryBlock(bundle, budget) {
114
120
  { title: 'System facts', items: bundle.system_facts },
115
121
  { title: 'Domain knowledge', items: bundle.domain_knowledge },
116
122
  ];
123
+ if (relatedBundle) {
124
+ sections.push({ title: 'Related behavioural rules', items: relatedBundle.user_rules }, { title: 'Related preferences', items: relatedBundle.user_preferences }, { title: 'Related task patterns', items: relatedBundle.task_patterns }, { title: 'Related workflows', items: relatedBundle.workflows }, { title: 'Related project', items: relatedBundle.project }, { title: 'Related constraints', items: relatedBundle.constraints }, { title: 'Related decisions', items: relatedBundle.decisions }, { title: 'Related system facts', items: relatedBundle.system_facts }, { title: 'Related domain knowledge', items: relatedBundle.domain_knowledge });
125
+ }
117
126
  const intro = 'Use the following as prior context and preferences. If they conflict with current instructions, follow the current instructions.';
118
127
  const lines = [intro];
119
128
  let used = estimateTokens(intro);
@@ -317,8 +326,7 @@ function createMemorySearchManager(config) {
317
326
  if (!memoryId) {
318
327
  throw new Error(`Unsupported Persistio memory path: ${params.relPath}`);
319
328
  }
320
- const memories = await client.listMemories();
321
- const memory = memories.find((item) => item.id === memoryId);
329
+ const memory = await client.getMemory(memoryId, { includePending: true });
322
330
  if (!memory) {
323
331
  throw new Error(`Persistio memory not found: ${memoryId}`);
324
332
  }
@@ -389,8 +397,8 @@ export default definePluginEntry({
389
397
  api.on('before_prompt_build', async (event) => {
390
398
  try {
391
399
  const query = buildRecallQuery(event);
392
- const bundle = await client.recallBundle(query);
393
- const block = buildMemoryBlock(bundle, cfg.tokenBudget);
400
+ const recall = await client.recallBundle(query);
401
+ const block = buildMemoryBlock(recall.bundle, cfg.tokenBudget, recall.related_bundle);
394
402
  if (!block)
395
403
  return;
396
404
  return { appendSystemContext: block };
@@ -2,7 +2,7 @@
2
2
  "id": "openclaw-persistio",
3
3
  "name": "Persistio Memory",
4
4
  "description": "Persistent semantic memory for OpenClaw via Persistio",
5
- "version": "0.1.4",
5
+ "version": "0.1.5",
6
6
  "kind": "memory",
7
7
  "activation": {
8
8
  "onStartup": true
@@ -31,6 +31,11 @@
31
31
  "recallTopK": {
32
32
  "type": "number"
33
33
  },
34
+ "recallMinSimilarity": {
35
+ "type": "number",
36
+ "minimum": 0,
37
+ "maximum": 1
38
+ },
34
39
  "recallTimeout": {
35
40
  "type": "number"
36
41
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@persistio/openclaw-plugin",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "OpenClaw plugin for Persistio \u2014 persistent semantic memory for AI agents",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -41,7 +41,8 @@
41
41
  }
42
42
  },
43
43
  "scripts": {
44
- "build": "tsc"
44
+ "build": "tsc",
45
+ "test": "node test/config-schema.test.mjs"
45
46
  },
46
47
  "dependencies": {
47
48
  "@sinclair/typebox": "^0.34.0"
package/src/client.ts CHANGED
@@ -3,6 +3,7 @@ export interface PersistioConfig {
3
3
  apiKey: string;
4
4
  tokenBudget: number;
5
5
  recallTopK: number;
6
+ recallMinSimilarity?: number;
6
7
  recallTimeout: number;
7
8
  send: PersistioSendConfig;
8
9
  }
@@ -26,7 +27,12 @@ export interface PersistioMemory {
26
27
  confidence: number;
27
28
  }
28
29
 
30
+ export interface GetMemoryOptions {
31
+ includePending?: boolean;
32
+ }
33
+
29
34
  export interface RecallBundle {
35
+ global_user_rules?: string[];
30
36
  user_rules: string[];
31
37
  user_preferences: string[];
32
38
  task_patterns: string[];
@@ -40,18 +46,21 @@ export interface RecallBundle {
40
46
 
41
47
  export interface RecallBundleResponse {
42
48
  bundle: RecallBundle;
49
+ related_bundle?: RecallBundle;
43
50
  }
44
51
 
45
52
  export class PersistioClient {
46
53
  private readonly baseURL: string;
47
54
  private readonly apiKey: string;
48
55
  private readonly recallTopK: number;
56
+ private readonly recallMinSimilarity?: number;
49
57
  private readonly recallTimeout: number;
50
58
 
51
59
  constructor(config: PersistioConfig) {
52
60
  this.baseURL = config.baseURL.replace(/\/$/, '');
53
61
  this.apiKey = config.apiKey;
54
62
  this.recallTopK = config.recallTopK;
63
+ this.recallMinSimilarity = config.recallMinSimilarity;
55
64
  this.recallTimeout = config.recallTimeout;
56
65
  }
57
66
 
@@ -63,10 +72,15 @@ export class PersistioClient {
63
72
  }
64
73
 
65
74
  async recall(query: string): Promise<PersistioMemory[]> {
75
+ const body: Record<string, unknown> = { query, top_k: this.recallTopK, include_pending: true };
76
+ if (typeof this.recallMinSimilarity === 'number') {
77
+ body.min_similarity = this.recallMinSimilarity;
78
+ }
79
+
66
80
  const res = await fetch(`${this.baseURL}/v1/recall`, {
67
81
  method: 'POST',
68
82
  headers: this.headers(),
69
- body: JSON.stringify({ query, top_k: this.recallTopK }),
83
+ body: JSON.stringify(body),
70
84
  signal: AbortSignal.timeout(this.recallTimeout),
71
85
  });
72
86
  if (!res.ok) throw new Error(`Persistio recall failed: ${res.status}`);
@@ -74,16 +88,21 @@ export class PersistioClient {
74
88
  return data.memories ?? [];
75
89
  }
76
90
 
77
- async recallBundle(query: string, topK?: number): Promise<RecallBundle> {
91
+ async recallBundle(query: string, topK?: number): Promise<RecallBundleResponse> {
92
+ const body: Record<string, unknown> = { query, top_k: topK ?? this.recallTopK, include_pending: true };
93
+ if (typeof this.recallMinSimilarity === 'number') {
94
+ body.min_similarity = this.recallMinSimilarity;
95
+ }
96
+
78
97
  const res = await fetch(`${this.baseURL}/v1/recall?format=bundle`, {
79
98
  method: 'POST',
80
99
  headers: this.headers(),
81
- body: JSON.stringify({ query, top_k: topK ?? this.recallTopK }),
100
+ body: JSON.stringify(body),
82
101
  signal: AbortSignal.timeout(this.recallTimeout),
83
102
  });
84
103
  if (!res.ok) throw new Error(`Persistio recallBundle failed: ${res.status}`);
85
104
  const data = await res.json() as RecallBundleResponse;
86
- return data.bundle;
105
+ return data;
87
106
  }
88
107
 
89
108
  async ingest(sessionId: string, chunks: Array<{ role: string; content: string; timestamp: string }>): Promise<void> {
@@ -113,6 +132,16 @@ export class PersistioClient {
113
132
  if (!res.ok) throw new Error(`Persistio deleteMemory failed: ${res.status}`);
114
133
  }
115
134
 
135
+ async getMemory(id: string, options: GetMemoryOptions = {}): Promise<PersistioMemory | null> {
136
+ const query = options.includePending ? '?include_pending=true' : '';
137
+ const res = await fetch(`${this.baseURL}/v1/memories/${id}${query}`, {
138
+ headers: this.headers(),
139
+ });
140
+ if (res.status === 404) return null;
141
+ if (!res.ok) throw new Error(`Persistio getMemory failed: ${res.status}`);
142
+ return await res.json() as PersistioMemory;
143
+ }
144
+
116
145
  async listMemories(): Promise<PersistioMemory[]> {
117
146
  const res = await fetch(`${this.baseURL}/v1/memories`, {
118
147
  headers: this.headers(),
package/src/index.ts CHANGED
@@ -43,6 +43,12 @@ function resolveSendConfig(raw: Record<string, unknown>): PersistioConfig['send'
43
43
  };
44
44
  }
45
45
 
46
+ function resolveRecallMinSimilarity(value: unknown): number | undefined {
47
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1
48
+ ? value
49
+ : undefined;
50
+ }
51
+
46
52
  function resolveConfig(raw: unknown): PersistioConfig {
47
53
  const c = (raw ?? {}) as Record<string, unknown>;
48
54
  return {
@@ -50,6 +56,7 @@ function resolveConfig(raw: unknown): PersistioConfig {
50
56
  apiKey: typeof c['apiKey'] === 'string' ? c['apiKey'] : '',
51
57
  tokenBudget: typeof c['tokenBudget'] === 'number' ? c['tokenBudget'] : 2000,
52
58
  recallTopK: typeof c['recallTopK'] === 'number' ? c['recallTopK'] : 10,
59
+ recallMinSimilarity: resolveRecallMinSimilarity(c['recallMinSimilarity']),
53
60
  recallTimeout: typeof c['recallTimeout'] === 'number' ? c['recallTimeout'] : 5000,
54
61
  send: resolveSendConfig(c),
55
62
  };
@@ -126,7 +133,7 @@ function buildRecallQuery(event: { prompt?: string; messages?: unknown[] }): str
126
133
  return truncate(parts.join('\n'), 600);
127
134
  }
128
135
 
129
- function buildMemoryBlock(bundle: RecallBundle, budget: number): string {
136
+ function buildMemoryBlock(bundle: RecallBundle, budget: number, relatedBundle?: RecallBundle): string {
130
137
  const sections: Array<{ title: string; items: string[] }> = [
131
138
  { title: 'Behavioural rules', items: bundle.user_rules },
132
139
  { title: 'Preferences', items: bundle.user_preferences },
@@ -138,6 +145,19 @@ function buildMemoryBlock(bundle: RecallBundle, budget: number): string {
138
145
  { title: 'System facts', items: bundle.system_facts },
139
146
  { title: 'Domain knowledge', items: bundle.domain_knowledge },
140
147
  ];
148
+ if (relatedBundle) {
149
+ sections.push(
150
+ { title: 'Related behavioural rules', items: relatedBundle.user_rules },
151
+ { title: 'Related preferences', items: relatedBundle.user_preferences },
152
+ { title: 'Related task patterns', items: relatedBundle.task_patterns },
153
+ { title: 'Related workflows', items: relatedBundle.workflows },
154
+ { title: 'Related project', items: relatedBundle.project },
155
+ { title: 'Related constraints', items: relatedBundle.constraints },
156
+ { title: 'Related decisions', items: relatedBundle.decisions },
157
+ { title: 'Related system facts', items: relatedBundle.system_facts },
158
+ { title: 'Related domain knowledge', items: relatedBundle.domain_knowledge },
159
+ );
160
+ }
141
161
 
142
162
  const intro = 'Use the following as prior context and preferences. If they conflict with current instructions, follow the current instructions.';
143
163
  const lines: string[] = [intro];
@@ -383,8 +403,7 @@ function createMemorySearchManager(config: PersistioConfig): MemorySearchManager
383
403
  throw new Error(`Unsupported Persistio memory path: ${params.relPath}`);
384
404
  }
385
405
 
386
- const memories = await client.listMemories();
387
- const memory = memories.find((item) => item.id === memoryId);
406
+ const memory = await client.getMemory(memoryId, { includePending: true });
388
407
  if (!memory) {
389
408
  throw new Error(`Persistio memory not found: ${memoryId}`);
390
409
  }
@@ -466,8 +485,8 @@ export default definePluginEntry({
466
485
  api.on('before_prompt_build', async (event) => {
467
486
  try {
468
487
  const query = buildRecallQuery(event);
469
- const bundle = await client.recallBundle(query);
470
- const block = buildMemoryBlock(bundle, cfg.tokenBudget);
488
+ const recall = await client.recallBundle(query);
489
+ const block = buildMemoryBlock(recall.bundle, cfg.tokenBudget, recall.related_bundle);
471
490
  if (!block) return;
472
491
  return { appendSystemContext: block };
473
492
  } catch (err) {