@ubundi/openclaw-cortex 0.2.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.
- package/README.md +157 -0
- package/dist/client.d.ts +48 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +73 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +68 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +27 -0
- package/dist/config.js.map +1 -0
- package/dist/hooks/capture.d.ts +24 -0
- package/dist/hooks/capture.d.ts.map +1 -0
- package/dist/hooks/capture.js +66 -0
- package/dist/hooks/capture.js.map +1 -0
- package/dist/hooks/recall.d.ts +28 -0
- package/dist/hooks/recall.d.ts.map +1 -0
- package/dist/hooks/recall.js +68 -0
- package/dist/hooks/recall.js.map +1 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +94 -0
- package/dist/index.js.map +1 -0
- package/dist/services/reflect.d.ts +19 -0
- package/dist/services/reflect.d.ts.map +1 -0
- package/dist/services/reflect.js +38 -0
- package/dist/services/reflect.js.map +1 -0
- package/dist/sync/daily-logs.d.ts +21 -0
- package/dist/sync/daily-logs.d.ts.map +1 -0
- package/dist/sync/daily-logs.js +43 -0
- package/dist/sync/daily-logs.js.map +1 -0
- package/dist/sync/memory-md.d.ts +25 -0
- package/dist/sync/memory-md.d.ts.map +1 -0
- package/dist/sync/memory-md.js +67 -0
- package/dist/sync/memory-md.js.map +1 -0
- package/dist/sync/transcripts.d.ts +21 -0
- package/dist/sync/transcripts.d.ts.map +1 -0
- package/dist/sync/transcripts.js +51 -0
- package/dist/sync/transcripts.js.map +1 -0
- package/dist/sync/watcher.d.ts +28 -0
- package/dist/sync/watcher.d.ts.map +1 -0
- package/dist/sync/watcher.js +85 -0
- package/dist/sync/watcher.js.map +1 -0
- package/dist/utils/format.d.ts +3 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +7 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/metrics.d.ts +19 -0
- package/dist/utils/metrics.d.ts.map +1 -0
- package/dist/utils/metrics.js +45 -0
- package/dist/utils/metrics.js.map +1 -0
- package/dist/utils/retry-queue.d.ts +27 -0
- package/dist/utils/retry-queue.d.ts.map +1 -0
- package/dist/utils/retry-queue.js +67 -0
- package/dist/utils/retry-queue.js.map +1 -0
- package/dist/utils/transcript-cleaner.d.ts +24 -0
- package/dist/utils/transcript-cleaner.d.ts.map +1 -0
- package/dist/utils/transcript-cleaner.js +96 -0
- package/dist/utils/transcript-cleaner.js.map +1 -0
- package/openclaw.plugin.json +95 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# @ubundi/openclaw-cortex
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
OpenClaw plugin for [Cortex](https://github.com/ubundi/cortex) long-term memory. Gives your agent persistent memory that survives across sessions — who you are, what your project does, decisions you made weeks ago, and how things changed over time.
|
|
6
|
+
|
|
7
|
+
- **Auto-Recall** — injects relevant memories before every agent turn via `before_agent_start` hook
|
|
8
|
+
- **Auto-Capture** — extracts facts from conversations via `agent_end` hook
|
|
9
|
+
- **File Sync** — watches `MEMORY.md`, daily logs, and session transcripts for background ingestion
|
|
10
|
+
- **Periodic Reflect** — consolidates memories, resolves SUPERSEDES chains, detects contradictions
|
|
11
|
+
- **Resilience** — retry queue with exponential backoff, cold-start detection, latency metrics
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
openclaw plugins install @ubundi/openclaw-cortex
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or link locally for development:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
openclaw plugins install -l ./path/to/openclaw-cortex
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Configuration
|
|
26
|
+
|
|
27
|
+
Add to your `openclaw.json`:
|
|
28
|
+
|
|
29
|
+
```json5
|
|
30
|
+
{
|
|
31
|
+
plugins: {
|
|
32
|
+
entries: {
|
|
33
|
+
"@ubundi/openclaw-cortex": {
|
|
34
|
+
enabled: true,
|
|
35
|
+
config: {
|
|
36
|
+
apiKey: "sk-cortex-...",
|
|
37
|
+
baseUrl: "https://q5p64iw9c9.execute-api.us-east-1.amazonaws.com/prod",
|
|
38
|
+
autoRecall: true,
|
|
39
|
+
autoCapture: true,
|
|
40
|
+
recallTopK: 5,
|
|
41
|
+
recallTimeoutMs: 500,
|
|
42
|
+
recallMode: "fast",
|
|
43
|
+
fileSync: true,
|
|
44
|
+
transcriptSync: true,
|
|
45
|
+
reflectIntervalMs: 3600000
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
slots: {
|
|
50
|
+
memory: "@ubundi/openclaw-cortex"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Environment variables are supported via `${VAR_NAME}` syntax:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"apiKey": "${CORTEX_API_KEY}",
|
|
61
|
+
"baseUrl": "${CORTEX_BASE_URL}"
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Config Options
|
|
66
|
+
|
|
67
|
+
| Option | Type | Default | Description |
|
|
68
|
+
|---|---|---|---|
|
|
69
|
+
| `apiKey` | string | _required_ | Cortex API key |
|
|
70
|
+
| `baseUrl` | string | `https://q5p64iw9c9...` | Cortex API base URL |
|
|
71
|
+
| `autoRecall` | boolean | `true` | Inject memories before each agent turn |
|
|
72
|
+
| `autoCapture` | boolean | `true` | Extract facts after agent responses |
|
|
73
|
+
| `recallTopK` | number | `5` | Number of memories to retrieve per turn |
|
|
74
|
+
| `recallTimeoutMs` | number | `500` | Max time to wait for recall (ms) |
|
|
75
|
+
| `recallMode` | string | `"fast"` | Retrieval depth: `fast`, `balanced`, or `full` |
|
|
76
|
+
| `fileSync` | boolean | `true` | Watch MEMORY.md and daily logs |
|
|
77
|
+
| `transcriptSync` | boolean | `true` | Watch and ingest session transcripts |
|
|
78
|
+
| `reflectIntervalMs` | number | `3600000` | Memory consolidation interval (ms). `0` to disable |
|
|
79
|
+
|
|
80
|
+
### Recall Modes
|
|
81
|
+
|
|
82
|
+
| Mode | What it does | Server-side latency |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `fast` | BM25 + semantic search only | ~80-150ms |
|
|
85
|
+
| `balanced` | Adds light reranking | ~150-300ms |
|
|
86
|
+
| `full` | Adds graph traversal + full reranker | ~300-600ms |
|
|
87
|
+
|
|
88
|
+
Use `fast` (default) for auto-recall where latency matters. Use `full` for explicit recall via SKILL.md where depth matters more than speed.
|
|
89
|
+
|
|
90
|
+
## How It Works
|
|
91
|
+
|
|
92
|
+
### Auto-Recall
|
|
93
|
+
|
|
94
|
+
Before every agent turn, the plugin queries Cortex's `/v1/retrieve` endpoint and prepends results to the agent's context:
|
|
95
|
+
|
|
96
|
+
```xml
|
|
97
|
+
<cortex_memories>
|
|
98
|
+
- [0.95] User prefers TypeScript over JavaScript
|
|
99
|
+
- [0.87] Project uses PostgreSQL with pgvector
|
|
100
|
+
</cortex_memories>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
If the request exceeds `recallTimeoutMs`, the agent proceeds without memories (silent degradation). After 3 consecutive failures, recall is disabled for 30 seconds (cold-start detection) to avoid hammering a cold ECS task.
|
|
104
|
+
|
|
105
|
+
### Auto-Capture
|
|
106
|
+
|
|
107
|
+
After each successful agent turn, the plugin extracts the last 20 messages and sends them to Cortex's `/v1/ingest/conversation` endpoint. A heuristic skips trivial exchanges (short messages, system-only turns).
|
|
108
|
+
|
|
109
|
+
Capture is fire-and-forget — it never blocks the agent. Failed ingestions are queued for retry with exponential backoff (up to 5 retries).
|
|
110
|
+
|
|
111
|
+
### File Sync
|
|
112
|
+
|
|
113
|
+
The plugin watches OpenClaw's memory files and ingests changes into Cortex:
|
|
114
|
+
|
|
115
|
+
- **MEMORY.md** — Line-level diff with 2-second debounce. Only added lines are ingested.
|
|
116
|
+
- **memory/\*.md** (daily logs) — Offset-based append detection. New content is ingested as it's written.
|
|
117
|
+
- **sessions/\*.jsonl** (transcripts) — Strips system prompts, tool JSON, and base64 images. Cleans dialogue into conversation format and batch ingests with session-scoped IDs.
|
|
118
|
+
|
|
119
|
+
Failed file sync operations are queued for retry, so transient network failures don't cause data loss.
|
|
120
|
+
|
|
121
|
+
### Periodic Reflect
|
|
122
|
+
|
|
123
|
+
Every `reflectIntervalMs` (default: 1 hour), the plugin calls Cortex's `/v1/reflect` endpoint to consolidate memories:
|
|
124
|
+
|
|
125
|
+
- Merges duplicate facts ingested across sessions
|
|
126
|
+
- Marks stale facts as superseded (SUPERSEDES chains)
|
|
127
|
+
- Detects contradictions between facts
|
|
128
|
+
- Tracks belief drift over time
|
|
129
|
+
|
|
130
|
+
Set `reflectIntervalMs: 0` to disable.
|
|
131
|
+
|
|
132
|
+
### Observability
|
|
133
|
+
|
|
134
|
+
On shutdown, the plugin logs recall latency percentiles:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
Cortex recall latency (847 samples): p50=120ms p95=340ms p99=480ms
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Use this to tune `recallTimeoutMs` and `recallMode` for your deployment.
|
|
141
|
+
|
|
142
|
+
## Compatibility with SKILL.md
|
|
143
|
+
|
|
144
|
+
If both this plugin and the Cortex SKILL.md are active, the `<cortex_memories>` tag in the prepended context signals to the skill that recall has already happened — the agent can skip manual `curl` calls.
|
|
145
|
+
|
|
146
|
+
## Development
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
npm install
|
|
150
|
+
npm run build # TypeScript → dist/
|
|
151
|
+
npm test # Run vitest (52 tests)
|
|
152
|
+
npm run test:watch # Watch mode
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface RetrieveResult {
|
|
2
|
+
node_id: string;
|
|
3
|
+
type: string;
|
|
4
|
+
content: string;
|
|
5
|
+
score: number;
|
|
6
|
+
source?: string;
|
|
7
|
+
confidence?: number;
|
|
8
|
+
metadata?: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
export interface RetrieveResponse {
|
|
11
|
+
results: RetrieveResult[];
|
|
12
|
+
}
|
|
13
|
+
export interface IngestFact {
|
|
14
|
+
core: string;
|
|
15
|
+
fact_type: string;
|
|
16
|
+
occurred_at: string | null;
|
|
17
|
+
entity_refs: string[];
|
|
18
|
+
speaker: string;
|
|
19
|
+
}
|
|
20
|
+
export interface IngestEntity {
|
|
21
|
+
name: string;
|
|
22
|
+
type: string;
|
|
23
|
+
aliases: string[];
|
|
24
|
+
}
|
|
25
|
+
export interface IngestResponse {
|
|
26
|
+
nodes_created: number;
|
|
27
|
+
edges_created: number;
|
|
28
|
+
facts: IngestFact[];
|
|
29
|
+
entities: IngestEntity[];
|
|
30
|
+
}
|
|
31
|
+
export interface ReflectResponse {
|
|
32
|
+
synthesized_count: number;
|
|
33
|
+
superseded_count: number;
|
|
34
|
+
}
|
|
35
|
+
export interface ConversationMessage {
|
|
36
|
+
role: string;
|
|
37
|
+
content: string;
|
|
38
|
+
}
|
|
39
|
+
export declare class CortexClient {
|
|
40
|
+
private baseUrl;
|
|
41
|
+
private apiKey;
|
|
42
|
+
constructor(baseUrl: string, apiKey: string);
|
|
43
|
+
retrieve(query: string, topK: number, mode: "fast" | "full", timeoutMs: number): Promise<RetrieveResponse>;
|
|
44
|
+
ingest(text: string, sessionId?: string): Promise<IngestResponse>;
|
|
45
|
+
ingestConversation(messages: ConversationMessage[], sessionId?: string): Promise<IngestResponse>;
|
|
46
|
+
reflect(sessionId?: string): Promise<ReflectResponse>;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,YAAY;IAErB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,MAAM;gBADN,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM;IAGlB,QAAQ,CACZ,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,gBAAgB,CAAC;IAyBtB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAiBjE,kBAAkB,CACtB,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,cAAc,CAAC;IAiBpB,OAAO,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;CAgB5D"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export class CortexClient {
|
|
2
|
+
baseUrl;
|
|
3
|
+
apiKey;
|
|
4
|
+
constructor(baseUrl, apiKey) {
|
|
5
|
+
this.baseUrl = baseUrl;
|
|
6
|
+
this.apiKey = apiKey;
|
|
7
|
+
}
|
|
8
|
+
async retrieve(query, topK, mode, timeoutMs) {
|
|
9
|
+
const controller = new AbortController();
|
|
10
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
11
|
+
try {
|
|
12
|
+
const res = await fetch(`${this.baseUrl}/v1/retrieve`, {
|
|
13
|
+
method: "POST",
|
|
14
|
+
headers: {
|
|
15
|
+
"x-api-key": this.apiKey,
|
|
16
|
+
"Content-Type": "application/json",
|
|
17
|
+
},
|
|
18
|
+
body: JSON.stringify({ query, top_k: topK, mode }),
|
|
19
|
+
signal: controller.signal,
|
|
20
|
+
});
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
throw new Error(`Cortex retrieve failed: ${res.status}`);
|
|
23
|
+
}
|
|
24
|
+
return (await res.json());
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
clearTimeout(timeout);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async ingest(text, sessionId) {
|
|
31
|
+
const res = await fetch(`${this.baseUrl}/v1/ingest`, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: {
|
|
34
|
+
"x-api-key": this.apiKey,
|
|
35
|
+
"Content-Type": "application/json",
|
|
36
|
+
},
|
|
37
|
+
body: JSON.stringify({ text, session_id: sessionId }),
|
|
38
|
+
});
|
|
39
|
+
if (!res.ok) {
|
|
40
|
+
throw new Error(`Cortex ingest failed: ${res.status}`);
|
|
41
|
+
}
|
|
42
|
+
return (await res.json());
|
|
43
|
+
}
|
|
44
|
+
async ingestConversation(messages, sessionId) {
|
|
45
|
+
const res = await fetch(`${this.baseUrl}/v1/ingest/conversation`, {
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers: {
|
|
48
|
+
"x-api-key": this.apiKey,
|
|
49
|
+
"Content-Type": "application/json",
|
|
50
|
+
},
|
|
51
|
+
body: JSON.stringify({ messages, session_id: sessionId }),
|
|
52
|
+
});
|
|
53
|
+
if (!res.ok) {
|
|
54
|
+
throw new Error(`Cortex ingest/conversation failed: ${res.status}`);
|
|
55
|
+
}
|
|
56
|
+
return (await res.json());
|
|
57
|
+
}
|
|
58
|
+
async reflect(sessionId) {
|
|
59
|
+
const res = await fetch(`${this.baseUrl}/v1/reflect`, {
|
|
60
|
+
method: "POST",
|
|
61
|
+
headers: {
|
|
62
|
+
"x-api-key": this.apiKey,
|
|
63
|
+
"Content-Type": "application/json",
|
|
64
|
+
},
|
|
65
|
+
body: JSON.stringify({ session_id: sessionId }),
|
|
66
|
+
});
|
|
67
|
+
if (!res.ok) {
|
|
68
|
+
throw new Error(`Cortex reflect failed: ${res.status}`);
|
|
69
|
+
}
|
|
70
|
+
return (await res.json());
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const RecallMode: z.ZodEnum<["fast", "balanced", "full"]>;
|
|
3
|
+
export type RecallMode = z.infer<typeof RecallMode>;
|
|
4
|
+
export declare const CortexConfigSchema: z.ZodObject<{
|
|
5
|
+
apiKey: z.ZodString;
|
|
6
|
+
baseUrl: z.ZodDefault<z.ZodString>;
|
|
7
|
+
autoRecall: z.ZodDefault<z.ZodBoolean>;
|
|
8
|
+
autoCapture: z.ZodDefault<z.ZodBoolean>;
|
|
9
|
+
recallTopK: z.ZodDefault<z.ZodNumber>;
|
|
10
|
+
recallTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
11
|
+
recallMode: z.ZodDefault<z.ZodEnum<["fast", "balanced", "full"]>>;
|
|
12
|
+
fileSync: z.ZodDefault<z.ZodBoolean>;
|
|
13
|
+
transcriptSync: z.ZodDefault<z.ZodBoolean>;
|
|
14
|
+
reflectIntervalMs: z.ZodDefault<z.ZodNumber>;
|
|
15
|
+
}, "strip", z.ZodTypeAny, {
|
|
16
|
+
apiKey: string;
|
|
17
|
+
baseUrl: string;
|
|
18
|
+
autoRecall: boolean;
|
|
19
|
+
autoCapture: boolean;
|
|
20
|
+
recallTopK: number;
|
|
21
|
+
recallTimeoutMs: number;
|
|
22
|
+
recallMode: "fast" | "full" | "balanced";
|
|
23
|
+
fileSync: boolean;
|
|
24
|
+
transcriptSync: boolean;
|
|
25
|
+
reflectIntervalMs: number;
|
|
26
|
+
}, {
|
|
27
|
+
apiKey: string;
|
|
28
|
+
baseUrl?: string | undefined;
|
|
29
|
+
autoRecall?: boolean | undefined;
|
|
30
|
+
autoCapture?: boolean | undefined;
|
|
31
|
+
recallTopK?: number | undefined;
|
|
32
|
+
recallTimeoutMs?: number | undefined;
|
|
33
|
+
recallMode?: "fast" | "full" | "balanced" | undefined;
|
|
34
|
+
fileSync?: boolean | undefined;
|
|
35
|
+
transcriptSync?: boolean | undefined;
|
|
36
|
+
reflectIntervalMs?: number | undefined;
|
|
37
|
+
}>;
|
|
38
|
+
export type CortexConfig = z.infer<typeof CortexConfigSchema>;
|
|
39
|
+
/**
|
|
40
|
+
* Config schema compatible with OpenClaw's pluginConfigSchema interface.
|
|
41
|
+
* OpenClaw calls safeParse() during plugin registration.
|
|
42
|
+
*/
|
|
43
|
+
export declare const configSchema: {
|
|
44
|
+
safeParse(value: unknown): z.SafeParseReturnType<{
|
|
45
|
+
apiKey: string;
|
|
46
|
+
baseUrl?: string | undefined;
|
|
47
|
+
autoRecall?: boolean | undefined;
|
|
48
|
+
autoCapture?: boolean | undefined;
|
|
49
|
+
recallTopK?: number | undefined;
|
|
50
|
+
recallTimeoutMs?: number | undefined;
|
|
51
|
+
recallMode?: "fast" | "full" | "balanced" | undefined;
|
|
52
|
+
fileSync?: boolean | undefined;
|
|
53
|
+
transcriptSync?: boolean | undefined;
|
|
54
|
+
reflectIntervalMs?: number | undefined;
|
|
55
|
+
}, {
|
|
56
|
+
apiKey: string;
|
|
57
|
+
baseUrl: string;
|
|
58
|
+
autoRecall: boolean;
|
|
59
|
+
autoCapture: boolean;
|
|
60
|
+
recallTopK: number;
|
|
61
|
+
recallTimeoutMs: number;
|
|
62
|
+
recallMode: "fast" | "full" | "balanced";
|
|
63
|
+
fileSync: boolean;
|
|
64
|
+
transcriptSync: boolean;
|
|
65
|
+
reflectIntervalMs: number;
|
|
66
|
+
}>;
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,UAAU,yCAAuC,CAAC;AAC/D,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAc7B,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;;GAGG;AACH,eAAO,MAAM,YAAY;qBACN,OAAO;;;;;;;;;;;;;;;;;;;;;;;CAGzB,CAAC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const RecallMode = z.enum(["fast", "balanced", "full"]);
|
|
3
|
+
export const CortexConfigSchema = z.object({
|
|
4
|
+
apiKey: z.string().min(1, "apiKey is required"),
|
|
5
|
+
baseUrl: z
|
|
6
|
+
.string()
|
|
7
|
+
.url("baseUrl must be a valid URL")
|
|
8
|
+
.default("https://q5p64iw9c9.execute-api.us-east-1.amazonaws.com/prod"),
|
|
9
|
+
autoRecall: z.boolean().default(true),
|
|
10
|
+
autoCapture: z.boolean().default(true),
|
|
11
|
+
recallTopK: z.number().int().min(1).max(20).default(5),
|
|
12
|
+
recallTimeoutMs: z.number().int().min(100).max(10000).default(2000),
|
|
13
|
+
recallMode: RecallMode.default("fast"),
|
|
14
|
+
fileSync: z.boolean().default(true),
|
|
15
|
+
transcriptSync: z.boolean().default(true),
|
|
16
|
+
reflectIntervalMs: z.number().int().min(0).default(3_600_000),
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* Config schema compatible with OpenClaw's pluginConfigSchema interface.
|
|
20
|
+
* OpenClaw calls safeParse() during plugin registration.
|
|
21
|
+
*/
|
|
22
|
+
export const configSchema = {
|
|
23
|
+
safeParse(value) {
|
|
24
|
+
return CortexConfigSchema.safeParse(value);
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;AAG/D,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,oBAAoB,CAAC;IAC/C,OAAO,EAAE,CAAC;SACP,MAAM,EAAE;SACR,GAAG,CAAC,6BAA6B,CAAC;SAClC,OAAO,CAAC,6DAA6D,CAAC;IACzE,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IACrC,WAAW,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IACtC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACtD,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;IACnE,UAAU,EAAE,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC;IACtC,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IACnC,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IACzC,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;CAC9D,CAAC,CAAC;AAIH;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,SAAS,CAAC,KAAc;QACtB,OAAO,kBAAkB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC7C,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { CortexClient } from "../client.js";
|
|
2
|
+
import type { CortexConfig } from "../config.js";
|
|
3
|
+
import type { RetryQueue } from "../utils/retry-queue.js";
|
|
4
|
+
interface AgentEndEvent {
|
|
5
|
+
messages: unknown[];
|
|
6
|
+
success: boolean;
|
|
7
|
+
error?: string;
|
|
8
|
+
durationMs?: number;
|
|
9
|
+
}
|
|
10
|
+
interface AgentContext {
|
|
11
|
+
agentId?: string;
|
|
12
|
+
sessionKey?: string;
|
|
13
|
+
sessionId?: string;
|
|
14
|
+
workspaceDir?: string;
|
|
15
|
+
}
|
|
16
|
+
type Logger = {
|
|
17
|
+
debug?(...args: unknown[]): void;
|
|
18
|
+
info(...args: unknown[]): void;
|
|
19
|
+
warn(...args: unknown[]): void;
|
|
20
|
+
error(...args: unknown[]): void;
|
|
21
|
+
};
|
|
22
|
+
export declare function createCaptureHandler(client: CortexClient, config: CortexConfig, logger: Logger, retryQueue?: RetryQueue): (event: AgentEndEvent, ctx: AgentContext) => Promise<void>;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=capture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../src/hooks/capture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAuB,MAAM,cAAc,CAAC;AACtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE1D,UAAU,aAAa;IACrB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,YAAY;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,KAAK,MAAM,GAAG;IACZ,KAAK,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IACjC,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC/B,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC/B,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CACjC,CAAC;AA+BF,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,UAAU,IAIT,OAAO,aAAa,EAAE,KAAK,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CA+CtE"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const MIN_CONTENT_LENGTH = 20;
|
|
2
|
+
const RECENT_MESSAGES_COUNT = 20;
|
|
3
|
+
function extractContent(content) {
|
|
4
|
+
if (typeof content === "string")
|
|
5
|
+
return content;
|
|
6
|
+
if (Array.isArray(content)) {
|
|
7
|
+
return content
|
|
8
|
+
.filter((block) => typeof block === "object" &&
|
|
9
|
+
block !== null &&
|
|
10
|
+
"type" in block &&
|
|
11
|
+
block.type === "text" &&
|
|
12
|
+
"text" in block)
|
|
13
|
+
.map((block) => block.text)
|
|
14
|
+
.join("\n");
|
|
15
|
+
}
|
|
16
|
+
return "";
|
|
17
|
+
}
|
|
18
|
+
function isWorthCapturing(messages) {
|
|
19
|
+
const hasUser = messages.some((m) => m.role === "user" && m.content.length > MIN_CONTENT_LENGTH);
|
|
20
|
+
const hasAssistant = messages.some((m) => m.role === "assistant" && m.content.length > MIN_CONTENT_LENGTH);
|
|
21
|
+
return hasUser && hasAssistant;
|
|
22
|
+
}
|
|
23
|
+
export function createCaptureHandler(client, config, logger, retryQueue) {
|
|
24
|
+
let captureCounter = 0;
|
|
25
|
+
return async (event, ctx) => {
|
|
26
|
+
if (!config.autoCapture)
|
|
27
|
+
return;
|
|
28
|
+
if (!event.success)
|
|
29
|
+
return;
|
|
30
|
+
if (!event.messages?.length)
|
|
31
|
+
return;
|
|
32
|
+
try {
|
|
33
|
+
const recent = event.messages.slice(-RECENT_MESSAGES_COUNT);
|
|
34
|
+
const normalized = recent
|
|
35
|
+
.filter((msg) => typeof msg === "object" &&
|
|
36
|
+
msg !== null &&
|
|
37
|
+
"role" in msg &&
|
|
38
|
+
"content" in msg)
|
|
39
|
+
.map((msg) => ({
|
|
40
|
+
role: String(msg.role),
|
|
41
|
+
content: extractContent(msg.content),
|
|
42
|
+
}))
|
|
43
|
+
.filter((msg) => msg.content.length > 0);
|
|
44
|
+
if (!isWorthCapturing(normalized)) {
|
|
45
|
+
logger.debug?.("Cortex capture: skipping — not enough substantive content");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const sessionId = ctx.sessionKey ?? ctx.sessionId;
|
|
49
|
+
const doIngest = async () => {
|
|
50
|
+
const res = await client.ingestConversation(normalized, sessionId);
|
|
51
|
+
logger.debug?.(`Cortex capture: ingested ${res.facts.length} facts, ${res.entities.length} entities (${res.nodes_created} nodes)`);
|
|
52
|
+
};
|
|
53
|
+
// Fire-and-forget with retry on failure
|
|
54
|
+
doIngest().catch((err) => {
|
|
55
|
+
logger.warn("Cortex capture failed, queuing for retry:", err);
|
|
56
|
+
if (retryQueue) {
|
|
57
|
+
retryQueue.enqueue(doIngest, `capture-${++captureCounter}`);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
logger.warn("Cortex capture error:", err);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=capture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture.js","sourceRoot":"","sources":["../../src/hooks/capture.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { CortexClient } from "../client.js";
|
|
2
|
+
import type { CortexConfig } from "../config.js";
|
|
3
|
+
import { LatencyMetrics } from "../utils/metrics.js";
|
|
4
|
+
interface BeforeAgentStartEvent {
|
|
5
|
+
prompt: string;
|
|
6
|
+
messages?: unknown[];
|
|
7
|
+
}
|
|
8
|
+
interface AgentContext {
|
|
9
|
+
agentId?: string;
|
|
10
|
+
sessionKey?: string;
|
|
11
|
+
sessionId?: string;
|
|
12
|
+
workspaceDir?: string;
|
|
13
|
+
}
|
|
14
|
+
interface BeforeAgentStartResult {
|
|
15
|
+
prependContext?: string;
|
|
16
|
+
}
|
|
17
|
+
type Logger = {
|
|
18
|
+
debug?(...args: unknown[]): void;
|
|
19
|
+
info(...args: unknown[]): void;
|
|
20
|
+
warn(...args: unknown[]): void;
|
|
21
|
+
error(...args: unknown[]): void;
|
|
22
|
+
};
|
|
23
|
+
export declare function createRecallHandler(client: CortexClient, config: CortexConfig, logger: Logger, metrics?: LatencyMetrics): {
|
|
24
|
+
(event: BeforeAgentStartEvent, _ctx: AgentContext): Promise<BeforeAgentStartResult | void>;
|
|
25
|
+
metrics: LatencyMetrics;
|
|
26
|
+
};
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=recall.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recall.d.ts","sourceRoot":"","sources":["../../src/hooks/recall.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,UAAU,qBAAqB;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;CACtB;AAED,UAAU,YAAY;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,UAAU,sBAAsB;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,KAAK,MAAM,GAAG;IACZ,KAAK,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IACjC,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC/B,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC/B,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CACjC,CAAC;AAUF,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,cAAc;YAOf,qBAAqB,QACtB,YAAY,GACjB,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC;;EAmE1C"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { formatMemories } from "../utils/format.js";
|
|
2
|
+
import { LatencyMetrics } from "../utils/metrics.js";
|
|
3
|
+
/**
|
|
4
|
+
* Cold-start detection: if the first N requests all timeout or fail,
|
|
5
|
+
* assume the ECS task is cold and disable recall temporarily.
|
|
6
|
+
* Re-enable after a cooldown period.
|
|
7
|
+
*/
|
|
8
|
+
const COLD_START_WINDOW = 3; // consecutive failures to trigger cold-start
|
|
9
|
+
const COLD_START_COOLDOWN_MS = 30_000; // wait 30s before retrying
|
|
10
|
+
export function createRecallHandler(client, config, logger, metrics) {
|
|
11
|
+
const recallMetrics = metrics ?? new LatencyMetrics();
|
|
12
|
+
let consecutiveFailures = 0;
|
|
13
|
+
let coldStartUntil = 0;
|
|
14
|
+
const handler = async (event, _ctx) => {
|
|
15
|
+
if (!config.autoRecall)
|
|
16
|
+
return;
|
|
17
|
+
const prompt = event.prompt?.trim();
|
|
18
|
+
if (!prompt || prompt.length < 5)
|
|
19
|
+
return;
|
|
20
|
+
// Cold-start gate: skip recall while service is warming
|
|
21
|
+
if (coldStartUntil > Date.now()) {
|
|
22
|
+
logger.debug?.("Cortex recall: skipped (cold-start cooldown)");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const start = Date.now();
|
|
26
|
+
try {
|
|
27
|
+
// recallMode maps to Cortex API mode parameter:
|
|
28
|
+
// "fast" = BM25 + semantic only (~80-150ms server-side)
|
|
29
|
+
// "balanced" = adds light reranking (~150-300ms)
|
|
30
|
+
// "full" = adds graph traversal + full reranker (~300-600ms)
|
|
31
|
+
const apiMode = config.recallMode === "balanced" ? "fast" : config.recallMode;
|
|
32
|
+
const response = await client.retrieve(prompt, config.recallTopK, apiMode, config.recallTimeoutMs);
|
|
33
|
+
const elapsed = Date.now() - start;
|
|
34
|
+
recallMetrics.record(elapsed);
|
|
35
|
+
consecutiveFailures = 0; // reset on success
|
|
36
|
+
if (!response.results?.length)
|
|
37
|
+
return;
|
|
38
|
+
const formatted = formatMemories(response.results);
|
|
39
|
+
if (!formatted)
|
|
40
|
+
return;
|
|
41
|
+
logger.debug?.(`Cortex recall: ${response.results.length} memories in ${elapsed}ms`);
|
|
42
|
+
return { prependContext: formatted };
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
const elapsed = Date.now() - start;
|
|
46
|
+
recallMetrics.record(elapsed);
|
|
47
|
+
consecutiveFailures++;
|
|
48
|
+
// Enter cold-start cooldown after consecutive failures
|
|
49
|
+
if (consecutiveFailures >= COLD_START_WINDOW) {
|
|
50
|
+
coldStartUntil = Date.now() + COLD_START_COOLDOWN_MS;
|
|
51
|
+
logger.warn(`Cortex recall: ${consecutiveFailures} consecutive failures, disabling for ${COLD_START_COOLDOWN_MS / 1000}s`);
|
|
52
|
+
consecutiveFailures = 0;
|
|
53
|
+
}
|
|
54
|
+
// Silent degradation — proceed without memories
|
|
55
|
+
if (err.name === "AbortError") {
|
|
56
|
+
logger.debug?.("Cortex recall timed out, proceeding without memories");
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
logger.warn("Cortex recall failed:", err);
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
// Expose metrics for observability
|
|
65
|
+
handler.metrics = recallMetrics;
|
|
66
|
+
return handler;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=recall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recall.js","sourceRoot":"","sources":["../../src/hooks/recall.ts"],"names":[],"mappings":"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"}
|