@remnic/core 9.25.2 → 9.25.4
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/dist/access-cli.js +3 -3
- package/dist/{chunk-GBAJCDTW.js → chunk-BUK2FXOL.js} +207 -227
- package/dist/chunk-BUK2FXOL.js.map +1 -0
- package/dist/{chunk-LFHMUWA2.js → chunk-FDAPXLRC.js} +3 -3
- package/dist/{chunk-5GSUBCZM.js → chunk-TGTTESJS.js} +2 -2
- package/dist/chunk-TGTTESJS.js.map +1 -0
- package/dist/extraction.js +6 -2
- package/dist/index.js +3 -3
- package/dist/orchestrator.js +3 -3
- package/dist/schemas.js +1 -1
- package/package.json +2 -2
- package/src/extraction-prompt-safety.test.ts +335 -0
- package/src/extraction.ts +258 -326
- package/src/schemas.ts +1 -1
- package/dist/chunk-5GSUBCZM.js.map +0 -1
- package/dist/chunk-GBAJCDTW.js.map +0 -1
- /package/dist/{chunk-LFHMUWA2.js.map → chunk-FDAPXLRC.js.map} +0 -0
package/dist/access-cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Orchestrator
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-FDAPXLRC.js";
|
|
4
4
|
import "./chunk-4B77XSG5.js";
|
|
5
5
|
import "./chunk-B7GROTXD.js";
|
|
6
6
|
import "./chunk-DPYYSG6F.js";
|
|
@@ -61,9 +61,9 @@ import "./chunk-PHRLJFWZ.js";
|
|
|
61
61
|
import "./chunk-64NJRYU2.js";
|
|
62
62
|
import "./chunk-K2TDK7GG.js";
|
|
63
63
|
import "./chunk-LTHVM4XN.js";
|
|
64
|
-
import "./chunk-
|
|
64
|
+
import "./chunk-BUK2FXOL.js";
|
|
65
65
|
import "./chunk-4RA3C3EV.js";
|
|
66
|
-
import "./chunk-
|
|
66
|
+
import "./chunk-TGTTESJS.js";
|
|
67
67
|
import "./chunk-54V4BZWP.js";
|
|
68
68
|
import "./chunk-2CGLBUW3.js";
|
|
69
69
|
import "./chunk-WLZYGLJ4.js";
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
ProactiveExtractionResultSchema,
|
|
10
10
|
ProactiveQuestionsResultSchema,
|
|
11
11
|
buildProfileConsolidationResultSchema
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-TGTTESJS.js";
|
|
13
13
|
import {
|
|
14
14
|
normalizeReasoningTrace
|
|
15
15
|
} from "./chunk-54V4BZWP.js";
|
|
@@ -43,6 +43,9 @@ import {
|
|
|
43
43
|
import {
|
|
44
44
|
normalizeProcedureSteps
|
|
45
45
|
} from "./chunk-QDW3E4RD.js";
|
|
46
|
+
import {
|
|
47
|
+
isMemoryCategory
|
|
48
|
+
} from "./chunk-CGASUJVP.js";
|
|
46
49
|
import {
|
|
47
50
|
FallbackLlmClient,
|
|
48
51
|
fallbackLlmRuntimeContextFromConfig,
|
|
@@ -74,6 +77,41 @@ import {
|
|
|
74
77
|
// src/extraction.ts
|
|
75
78
|
import OpenAI from "openai";
|
|
76
79
|
var PROACTIVE_MIN_CONFIDENCE = 0.8;
|
|
80
|
+
var EXTRACTION_RESPONSE_SHAPE = `{
|
|
81
|
+
"facts": [{
|
|
82
|
+
"category": "<category>",
|
|
83
|
+
"content": "<source-grounded statement>",
|
|
84
|
+
"confidence": 0.0,
|
|
85
|
+
"tags": ["<tag>"],
|
|
86
|
+
"entityRef": "<optional normalized-name>",
|
|
87
|
+
"promptedByQuestion": "<optional source-grounded question>",
|
|
88
|
+
"quote": "<optional exact contiguous source span>",
|
|
89
|
+
"scope": "<optional project-or-global>",
|
|
90
|
+
"structuredAttributes": {"<key>": "<value>"},
|
|
91
|
+
"procedureSteps": [{"order": 1, "intent": "<step>"}, {"order": 2, "intent": "<step>"}],
|
|
92
|
+
"reasoningTrace": {
|
|
93
|
+
"steps": [{"order": 1, "description": "<step>"}, {"order": 2, "description": "<step>"}],
|
|
94
|
+
"finalAnswer": "<answer>",
|
|
95
|
+
"observedOutcome": "<optional outcome>"
|
|
96
|
+
},
|
|
97
|
+
"eventTime": "<optional source temporal expression>"
|
|
98
|
+
}],
|
|
99
|
+
"entities": [{
|
|
100
|
+
"name": "<normalized-name>",
|
|
101
|
+
"type": "<entity-type>",
|
|
102
|
+
"facts": ["<source-grounded statement>"],
|
|
103
|
+
"promptedByQuestion": "<optional source-grounded question>",
|
|
104
|
+
"structuredSections": [{"key": "<section-key>", "title": "<section-title>", "facts": ["<source-grounded statement>"]}]
|
|
105
|
+
}],
|
|
106
|
+
"profileUpdates": ["<source-grounded profile update>"],
|
|
107
|
+
"questions": [{"question": "<source-grounded unresolved question>", "context": "<source-grounded context>", "priority": 0.0}],
|
|
108
|
+
"identityReflection": "<conversation-grounded agent reflection>",
|
|
109
|
+
"relationships": [{"source": "<normalized-name>", "target": "<normalized-name>", "label": "<source-grounded relationship>"}]
|
|
110
|
+
}`;
|
|
111
|
+
var EXTRACTION_RESPONSE_PLACEHOLDERS = {};
|
|
112
|
+
for (const placeholder of EXTRACTION_RESPONSE_SHAPE.match(/<[^<>\r\n]+>/g) ?? []) {
|
|
113
|
+
EXTRACTION_RESPONSE_PLACEHOLDERS[placeholder] = true;
|
|
114
|
+
}
|
|
77
115
|
var CONSOLIDATION_RESPONSE_SCHEMA = `{
|
|
78
116
|
"items": [
|
|
79
117
|
{
|
|
@@ -90,6 +128,35 @@ var CONSOLIDATION_RESPONSE_SCHEMA = `{
|
|
|
90
128
|
function isPlainRecord(value) {
|
|
91
129
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
92
130
|
}
|
|
131
|
+
function containsExtractionPlaceholder(value) {
|
|
132
|
+
if (typeof value === "string") return EXTRACTION_RESPONSE_PLACEHOLDERS[value.trim()] === true;
|
|
133
|
+
if (Array.isArray(value)) return value.some(containsExtractionPlaceholder);
|
|
134
|
+
return isPlainRecord(value) && Object.values(value).some(containsExtractionPlaceholder);
|
|
135
|
+
}
|
|
136
|
+
function extractionText(value) {
|
|
137
|
+
if (typeof value !== "string") return void 0;
|
|
138
|
+
const text = value.trim();
|
|
139
|
+
return text.length > 0 && !containsExtractionPlaceholder(text) ? text : void 0;
|
|
140
|
+
}
|
|
141
|
+
function extractionAttributes(value) {
|
|
142
|
+
if (!isPlainRecord(value)) return void 0;
|
|
143
|
+
const attributes = {};
|
|
144
|
+
for (const [key, candidate] of Object.entries(value)) {
|
|
145
|
+
const normalizedKey = extractionText(key);
|
|
146
|
+
const normalizedValue = extractionText(candidate);
|
|
147
|
+
if (normalizedKey !== void 0 && normalizedValue !== void 0) {
|
|
148
|
+
attributes[normalizedKey] = normalizedValue;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return Object.keys(attributes).length > 0 ? attributes : void 0;
|
|
152
|
+
}
|
|
153
|
+
function extractionEntityType(value) {
|
|
154
|
+
const type = extractionText(value);
|
|
155
|
+
if (type === "person" || type === "project" || type === "tool" || type === "company" || type === "place" || type === "other") {
|
|
156
|
+
return type;
|
|
157
|
+
}
|
|
158
|
+
return void 0;
|
|
159
|
+
}
|
|
93
160
|
function normalizeQuestion(question) {
|
|
94
161
|
const priority = Number.isFinite(question.priority) ? Math.max(0, Math.min(1, question.priority)) : 0.5;
|
|
95
162
|
return {
|
|
@@ -209,82 +276,111 @@ var ExtractionEngine = class {
|
|
|
209
276
|
return !!parsed && typeof parsed === "object" && !Array.isArray(parsed) && ("facts" in parsed || "entities" in parsed || "profileUpdates" in parsed || "questions" in parsed || "relationships" in parsed || "identityReflection" in parsed);
|
|
210
277
|
}
|
|
211
278
|
normalizeExtractionResultPayload(parsed) {
|
|
212
|
-
const entities = Array.isArray(parsed?.entities) ? parsed.entities.map((
|
|
213
|
-
const facts = Array.isArray(parsed?.facts) ? parsed.facts.map((
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
) : void 0,
|
|
224
|
-
procedureSteps: Array.isArray(f?.procedureSteps) ? normalizeProcedureSteps(f.procedureSteps) : void 0,
|
|
225
|
-
reasoningTrace: (() => {
|
|
226
|
-
const candidate = f?.reasoningTrace && typeof f.reasoningTrace === "object" && !Array.isArray(f.reasoningTrace) ? f.reasoningTrace : f?.reasoning_trace && typeof f.reasoning_trace === "object" && !Array.isArray(f.reasoning_trace) ? f.reasoning_trace : null;
|
|
227
|
-
return candidate ? normalizeReasoningTrace(candidate) ?? void 0 : void 0;
|
|
228
|
-
})(),
|
|
229
|
-
// Issue #1575 PR 2: the LLM provides a verbatim supporting quote
|
|
230
|
-
// per fact. Optional/nullable per repo gotcha 6 (OpenAI Responses
|
|
231
|
-
// API emits null for absent optional fields). The post-parse
|
|
232
|
-
// validator (buildFactProvenance) locates this quote in the
|
|
233
|
-
// buffered turns and builds verified ProvenanceSource[] entries.
|
|
234
|
-
quote: typeof f?.quote === "string" && f.quote.trim().length > 0 ? f.quote : void 0,
|
|
235
|
-
eventTime: typeof f?.eventTime === "string" && f.eventTime.trim().length > 0 ? f.eventTime.trim() : typeof f?.event_time === "string" && f.event_time.trim().length > 0 ? f.event_time.trim() : void 0
|
|
236
|
-
})).filter((f) => f.content.length > 0) : [];
|
|
237
|
-
const questions = Array.isArray(parsed?.questions) ? parsed.questions.map((q) => {
|
|
238
|
-
if (typeof q === "string") return { question: q, context: "", priority: 0.5 };
|
|
279
|
+
const entities = Array.isArray(parsed?.entities) ? parsed.entities.map((candidate) => this.normalizeEntityUpdate(candidate)).filter((entity) => entity !== void 0 && entity.name.length > 0) : [];
|
|
280
|
+
const facts = Array.isArray(parsed?.facts) ? parsed.facts.map((candidate) => {
|
|
281
|
+
const f = isPlainRecord(candidate) ? candidate : {};
|
|
282
|
+
const category = typeof f.category === "string" ? f.category.trim() : "fact";
|
|
283
|
+
const reasoningTraceInput = isPlainRecord(f.reasoningTrace) ? f.reasoningTrace : isPlainRecord(f?.reasoning_trace) ? f.reasoning_trace : void 0;
|
|
284
|
+
if (!isMemoryCategory(category)) return void 0;
|
|
285
|
+
const procedureSteps = Array.isArray(f.procedureSteps) ? normalizeProcedureSteps(f.procedureSteps) : void 0;
|
|
286
|
+
const reasoningTrace = reasoningTraceInput ? normalizeReasoningTrace(reasoningTraceInput) ?? void 0 : void 0;
|
|
287
|
+
if (containsExtractionPlaceholder(procedureSteps) || containsExtractionPlaceholder(reasoningTrace)) {
|
|
288
|
+
return void 0;
|
|
289
|
+
}
|
|
239
290
|
return {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
291
|
+
category,
|
|
292
|
+
content: extractionText(f.content) ?? extractionText(f.text) ?? "",
|
|
293
|
+
confidence: typeof f.confidence === "number" ? f.confidence : 0.7,
|
|
294
|
+
tags: Array.isArray(f.tags) ? f.tags.flatMap((tag) => {
|
|
295
|
+
const text = extractionText(tag);
|
|
296
|
+
return text === void 0 ? [] : [text];
|
|
297
|
+
}) : [],
|
|
298
|
+
entityRef: extractionText(f.entityRef),
|
|
299
|
+
promptedByQuestion: extractionText(f.promptedByQuestion),
|
|
300
|
+
scope: f.scope === "global" || f.scope === "project" ? f.scope : void 0,
|
|
301
|
+
structuredAttributes: extractionAttributes(f.structuredAttributes),
|
|
302
|
+
procedureSteps,
|
|
303
|
+
reasoningTrace,
|
|
304
|
+
quote: extractionText(f.quote),
|
|
305
|
+
eventTime: extractionText(f.eventTime) ?? extractionText(f.event_time)
|
|
243
306
|
};
|
|
244
|
-
}).filter((
|
|
307
|
+
}).filter((fact) => fact !== void 0 && fact.content.length > 0) : [];
|
|
308
|
+
const questions = Array.isArray(parsed?.questions) ? parsed.questions.flatMap((candidate) => {
|
|
309
|
+
const record = isPlainRecord(candidate) ? candidate : void 0;
|
|
310
|
+
const question = extractionText(record?.question) ?? extractionText(record?.text) ?? extractionText(candidate);
|
|
311
|
+
if (question === void 0) return [];
|
|
312
|
+
return [{
|
|
313
|
+
question,
|
|
314
|
+
context: extractionText(record?.context) ?? "",
|
|
315
|
+
priority: typeof record?.priority === "number" ? record.priority : 0.5
|
|
316
|
+
}];
|
|
317
|
+
}) : [];
|
|
318
|
+
const profileUpdates = Array.isArray(parsed?.profileUpdates) ? parsed.profileUpdates.flatMap((candidate) => {
|
|
319
|
+
const update = extractionText(candidate);
|
|
320
|
+
return update === void 0 ? [] : [update];
|
|
321
|
+
}) : [];
|
|
322
|
+
const relationships = Array.isArray(parsed?.relationships) ? parsed.relationships.flatMap((candidate) => {
|
|
323
|
+
const relationship = isPlainRecord(candidate) ? candidate : void 0;
|
|
324
|
+
const source = extractionText(relationship?.source);
|
|
325
|
+
const target = extractionText(relationship?.target);
|
|
326
|
+
const label = extractionText(relationship?.label);
|
|
327
|
+
if (source === void 0 || target === void 0 || label === void 0) return [];
|
|
328
|
+
return [{
|
|
329
|
+
source,
|
|
330
|
+
target,
|
|
331
|
+
label,
|
|
332
|
+
promptedByQuestion: extractionText(relationship?.promptedByQuestion)
|
|
333
|
+
}];
|
|
334
|
+
}) : void 0;
|
|
245
335
|
return {
|
|
246
336
|
facts,
|
|
247
337
|
entities,
|
|
248
|
-
profileUpdates
|
|
338
|
+
profileUpdates,
|
|
249
339
|
questions,
|
|
250
|
-
identityReflection: parsed?.identityReflection
|
|
251
|
-
relationships
|
|
252
|
-
(r) => typeof r?.source === "string" && typeof r?.target === "string" && typeof r?.label === "string"
|
|
253
|
-
).map((r) => ({
|
|
254
|
-
source: r.source,
|
|
255
|
-
target: r.target,
|
|
256
|
-
label: r.label,
|
|
257
|
-
promptedByQuestion: typeof r?.promptedByQuestion === "string" ? r.promptedByQuestion : void 0
|
|
258
|
-
})) : void 0
|
|
340
|
+
identityReflection: extractionText(parsed?.identityReflection),
|
|
341
|
+
relationships
|
|
259
342
|
};
|
|
260
343
|
}
|
|
261
344
|
normalizeEntityUpdate(entity) {
|
|
262
|
-
const
|
|
263
|
-
const
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
345
|
+
const record = isPlainRecord(entity) ? entity : {};
|
|
346
|
+
const rawUpdates = isPlainRecord(record.updates) ? record.updates : void 0;
|
|
347
|
+
const normalizedTexts = (value) => Array.isArray(value) ? value.flatMap((candidate) => {
|
|
348
|
+
const text = extractionText(candidate);
|
|
349
|
+
return text === void 0 ? [] : [text];
|
|
350
|
+
}) : [];
|
|
351
|
+
const directFacts = normalizedTexts(record.facts);
|
|
352
|
+
const updateFacts = normalizedTexts(rawUpdates?.facts);
|
|
353
|
+
const scalarUpdateFacts = rawUpdates ? Object.entries(rawUpdates).sort(([left], [right]) => left.localeCompare(right)).flatMap(([key, value]) => {
|
|
354
|
+
if (["facts", "name", "promptedByQuestion", "structuredSections", "type"].includes(key)) {
|
|
355
|
+
return [];
|
|
269
356
|
}
|
|
357
|
+
const normalizedKey = extractionText(key);
|
|
358
|
+
if (normalizedKey === void 0) return [];
|
|
359
|
+
const normalizedValue = extractionText(value);
|
|
360
|
+
if (normalizedValue !== void 0) return [`${normalizedKey}: ${normalizedValue}`];
|
|
270
361
|
if (typeof value === "number" || typeof value === "boolean") {
|
|
271
|
-
return [`${
|
|
362
|
+
return [`${normalizedKey}: ${String(value)}`];
|
|
272
363
|
}
|
|
273
364
|
return [];
|
|
274
365
|
}) : [];
|
|
275
|
-
const structuredSectionsSource = Array.isArray(
|
|
276
|
-
const
|
|
277
|
-
|
|
366
|
+
const structuredSectionsSource = Array.isArray(record.structuredSections) ? record.structuredSections : Array.isArray(rawUpdates?.structuredSections) ? rawUpdates.structuredSections : [];
|
|
367
|
+
const structuredSections = structuredSectionsSource.flatMap((candidate) => {
|
|
368
|
+
const section = isPlainRecord(candidate) ? candidate : {};
|
|
369
|
+
const key = extractionText(section.key);
|
|
370
|
+
const title = extractionText(section.title);
|
|
371
|
+
const facts = normalizedTexts(section.facts);
|
|
372
|
+
if (key === void 0 || title === void 0 || facts.length === 0) return [];
|
|
373
|
+
return [{ key, title, facts }];
|
|
374
|
+
});
|
|
375
|
+
const rawType = record.type ?? rawUpdates?.type;
|
|
376
|
+
const type = rawType === void 0 ? "other" : extractionEntityType(rawType);
|
|
377
|
+
if (type === void 0) return void 0;
|
|
278
378
|
return {
|
|
279
|
-
name,
|
|
379
|
+
name: extractionText(record.name) ?? extractionText(record.entityId) ?? extractionText(rawUpdates?.name) ?? "",
|
|
280
380
|
type,
|
|
281
381
|
facts: [...directFacts, ...updateFacts, ...scalarUpdateFacts],
|
|
282
|
-
structuredSections:
|
|
283
|
-
|
|
284
|
-
title: typeof section?.title === "string" ? section.title.trim() : "",
|
|
285
|
-
facts: Array.isArray(section?.facts) ? section.facts.filter((fact) => typeof fact === "string").map((fact) => fact.trim()).filter((fact) => fact.length > 0) : []
|
|
286
|
-
})).filter((section) => section.key.length > 0 && section.title.length > 0 && section.facts.length > 0) : void 0,
|
|
287
|
-
promptedByQuestion: typeof entity?.promptedByQuestion === "string" ? entity.promptedByQuestion : typeof rawUpdates?.promptedByQuestion === "string" ? rawUpdates.promptedByQuestion : void 0
|
|
382
|
+
structuredSections: structuredSections.length > 0 ? structuredSections : void 0,
|
|
383
|
+
promptedByQuestion: extractionText(record.promptedByQuestion) ?? extractionText(rawUpdates?.promptedByQuestion)
|
|
288
384
|
};
|
|
289
385
|
}
|
|
290
386
|
parseJsonObject(content) {
|
|
@@ -372,7 +468,7 @@ var ExtractionEngine = class {
|
|
|
372
468
|
const profileUpdates = (Array.isArray(result.profileUpdates) ? result.profileUpdates : []).map(
|
|
373
469
|
(update) => typeof update === "string" ? update.trim() : typeof update?.content === "string" ? update.content.trim() : ""
|
|
374
470
|
).filter((update) => update.length > 0);
|
|
375
|
-
const entityUpdates = (Array.isArray(result.entityUpdates) ? result.entityUpdates : []).map((entity) => this.normalizeEntityUpdate(entity)).filter((entity) => entity.name.length > 0);
|
|
471
|
+
const entityUpdates = (Array.isArray(result.entityUpdates) ? result.entityUpdates : []).map((entity) => this.normalizeEntityUpdate(entity)).filter((entity) => entity !== void 0 && entity.name.length > 0);
|
|
376
472
|
return { items, profileUpdates, entityUpdates };
|
|
377
473
|
}
|
|
378
474
|
async applyProactiveQuestionPass(conversation, base) {
|
|
@@ -951,22 +1047,8 @@ var ExtractionEngine = class {
|
|
|
951
1047
|
log.debug(
|
|
952
1048
|
`extracted ${result.facts.length} facts, ${result.entities.length} entities, ${(result.questions ?? []).length} questions via fallback (${detailed.modelUsed})`
|
|
953
1049
|
);
|
|
954
|
-
const
|
|
955
|
-
|
|
956
|
-
const eventTime = typeof f.eventTime === "string" && f.eventTime.trim().length > 0 ? f.eventTime.trim() : typeof f.event_time === "string" && f.event_time.trim().length > 0 ? f.event_time.trim() : void 0;
|
|
957
|
-
if (!f.reasoningTrace && eventTime === void 0) return f;
|
|
958
|
-
return {
|
|
959
|
-
...f,
|
|
960
|
-
...f.reasoningTrace ? { reasoningTrace: normalizeReasoningTrace(f.reasoningTrace) ?? void 0 } : {},
|
|
961
|
-
...eventTime !== void 0 ? { eventTime } : {}
|
|
962
|
-
};
|
|
963
|
-
});
|
|
964
|
-
const sanitized = this.sanitizeExtractionResult({
|
|
965
|
-
...result,
|
|
966
|
-
facts: normalizedFacts,
|
|
967
|
-
questions: result.questions ?? [],
|
|
968
|
-
identityReflection: result.identityReflection ?? void 0
|
|
969
|
-
}, messageTimestamp);
|
|
1050
|
+
const normalized = this.normalizeExtractionResultPayload(result);
|
|
1051
|
+
const sanitized = this.sanitizeExtractionResult(normalized, messageTimestamp);
|
|
970
1052
|
const finalResult = await this.applyProactiveQuestionPass(conversation, sanitized);
|
|
971
1053
|
return this.attachProvenanceToResult(finalResult, boundedTurns);
|
|
972
1054
|
}
|
|
@@ -1039,90 +1121,36 @@ var ExtractionEngine = class {
|
|
|
1039
1121
|
const truncatedConversation = conversation.length > maxConversationChars ? conversation.slice(0, maxConversationChars) + "\n\n[truncated]" : conversation;
|
|
1040
1122
|
const localPrompt = `You are a memory extraction system. Extract durable, reusable memories from this conversation.
|
|
1041
1123
|
|
|
1042
|
-
|
|
1043
|
-
- fact:
|
|
1044
|
-
- preference:
|
|
1045
|
-
- correction:
|
|
1046
|
-
- entity:
|
|
1047
|
-
- decision:
|
|
1048
|
-
- relationship:
|
|
1049
|
-
- principle:
|
|
1050
|
-
- commitment:
|
|
1051
|
-
- moment:
|
|
1052
|
-
- skill:
|
|
1053
|
-
-
|
|
1054
|
-
-
|
|
1055
|
-
- reasoning_trace: Stored solution chains \u2014 use when the user narrates HOW they solved a specific problem step-by-step ("here's how I figured out\u2026", "the debugging went like this\u2026"). Put a short title in "content" (e.g. "How I debugged the staging latency spike") and the chain in "reasoningTrace": {"steps":[{"order":1,"description":"\u2026"}, \u2026], "finalAnswer":"\u2026", "observedOutcome":"\u2026" (optional)}. Require \u22652 ordered steps and a finalAnswer. Do NOT use for ordinary decisions (prefer "decision") or reusable workflows (prefer "procedure").
|
|
1056
|
-
|
|
1057
|
-
IMPORTANT: Do NOT label everything as "fact". Use "decision" for architectural choices, "commitment" for deadlines/promises, "principle" for reusable rules, "correction" for when the user rejects a suggestion, etc.
|
|
1058
|
-
|
|
1059
|
-
=== DO NOT EXTRACT (negative examples) ===
|
|
1060
|
-
These are operational noise - skip them:
|
|
1061
|
-
- "The user has a cron job that runs every 30 minutes" (scheduled task descriptions)
|
|
1062
|
-
- "The user encountered error XYZ at 3:45 PM" (temporary error states)
|
|
1063
|
-
- "The file is located at /path/to/project/file" (transient file paths)
|
|
1064
|
-
- "The system is using 4GB of memory" (current resource usage)
|
|
1065
|
-
- "The user ran the 'git status' command" (individual command executions)
|
|
1066
|
-
- "The conversation took place on Tuesday" (session metadata)
|
|
1067
|
-
- "The agent read the file at /path/to/file.txt" (agent's own actions)
|
|
1068
|
-
- "The user's OpenClaw automation posts to #channel on failures" (automation behavior descriptions)
|
|
1069
|
-
- "The user stores state in /path/to/state.json" (implementation details)
|
|
1070
|
-
- "The X-watch automation has been stalled for 58 hours" (system status updates)
|
|
1071
|
-
- "The user processed 5 batch files and extracted insights" (processing summaries)
|
|
1072
|
-
- "The user has a cron job that runs a Checkpoint Loop every 2 hours" (automation schedules)
|
|
1073
|
-
- "The user runs a Morning Surprise cron job daily at 7:30 AM" (automation schedules)
|
|
1074
|
-
- "The user runs an X Bookmarks \u2192 Insights pipeline hourly at :13" (automation schedules)
|
|
1075
|
-
- "The user's system mines X/Twitter mentions for ideas every 10a/2p/6p" (automation schedules)
|
|
1076
|
-
- "The user runs a Health Insights cron job weekday mornings" (automation schedules)
|
|
1077
|
-
- "The system monitors the showcase page every 12 hours" (system monitoring configurations)
|
|
1078
|
-
|
|
1079
|
-
=== DO EXTRACT (positive examples) ===
|
|
1080
|
-
These are durable insights - capture them:
|
|
1081
|
-
- "The user prefers dark mode interfaces and finds light mode uncomfortable" (preference)
|
|
1082
|
-
- "The user works primarily with TypeScript and avoids Python for frontend code" (long-term fact)
|
|
1083
|
-
- "The user's side project 'alpha-trader' uses a custom algorithm for arbitrage" (entity + detail)
|
|
1084
|
-
- "The user corrected that PostgreSQL 15 is required, not version 14" (correction)
|
|
1085
|
-
- "The user never commits code without running tests first" (principle)
|
|
1086
|
-
- "The user has a meeting with the design team every Friday at 2pm" (commitment)
|
|
1087
|
-
|
|
1088
|
-
=== Rules ===
|
|
1089
|
-
- Extract only NEW information worth remembering across sessions
|
|
1090
|
-
- Skip transient details (file paths, current errors, temporary states, agent actions)
|
|
1091
|
-
- Confidence: Explicit (0.95-1.0), Implied (0.70-0.94), Inferred (0.40-0.69), Speculative (0.00-0.39)${this.config.provenance?.enabled ? `
|
|
1092
|
-
- Source quotes: For each fact, include a "quote" field with the EXACT verbatim words from the conversation that support the fact (copy a contiguous span from a single turn, not a paraphrase). Cap at ~300 chars.` : ""}
|
|
1093
|
-
- Corrections get highest confidence (0.95+)
|
|
1094
|
-
- Each fact should be standalone and self-contained
|
|
1095
|
-
- Lines labelled [context user] or [context assistant] are reference context only. Use them to resolve pronouns and adjacent question/answer pairs, but do not extract a memory stated only in context lines unless a normal [user] or [assistant] line confirms or completes it.
|
|
1096
|
-
- CRITICAL: Use canonical hyphenated entity names (e.g., "jane-doe" not "janedoe")
|
|
1097
|
-
- CRITICAL: NEVER extract the same fact twice - check for duplicates before adding to facts array
|
|
1098
|
-
- CRITICAL: NEVER extract cron job schedules, automation configurations, or system monitoring details (these are operational noise)
|
|
1099
|
-
- If uncertain about relevance, prefer NOT extracting${lifecycleCaps.extractionScopeClassification ? `
|
|
1100
|
-
- For each fact, set "scope" to "global" (cross-project knowledge: framework bugs, library behavior, user preferences, tool configs, general patterns) or "project" (codebase-specific: file paths, env configs, deployment details, project workarounds). When in doubt, prefer "project".` : ""}
|
|
1101
|
-
|
|
1102
|
-
=== Structured Attributes ===
|
|
1103
|
-
When a fact contains measurable, categorical, or precisely valued data, add a "structuredAttributes" object with key-value string pairs. This captures exact values for precise retrieval later.
|
|
1104
|
-
Examples of when to add structuredAttributes:
|
|
1105
|
-
- Product details: {"price": "29.99", "brand": "Sony", "color": "black", "rating": "4.5"}
|
|
1106
|
-
- Person details: {"age": "32", "occupation": "engineer", "city": "Austin"}
|
|
1107
|
-
- Events with dates: {"date": "2024-03-15", "location": "San Francisco"}
|
|
1108
|
-
- Decisions: {"chosen": "PostgreSQL", "rejected": "MongoDB", "reason": "ACID compliance"}
|
|
1109
|
-
- Quantities/measurements: {"budget": "50000", "team_size": "5", "deadline": "2024-06-01"}
|
|
1110
|
-
Only add structuredAttributes when there are concrete values. Skip for abstract or narrative facts.
|
|
1111
|
-
${this.eventTimePromptInstruction()}
|
|
1112
|
-
Also generate:
|
|
1113
|
-
1. 1-3 genuine questions you're curious about from this conversation
|
|
1114
|
-
2. Profile updates about user patterns/behaviors (if any)
|
|
1115
|
-
3. Relationships between entities (max 5). Use normalized names like "person-jane-doe", "company-acme-corp".
|
|
1116
|
-
4. For entity facts that fit a durable named heading, include entity.structuredSections with {key, title, facts}.
|
|
1124
|
+
Use the most specific category:
|
|
1125
|
+
- fact: objective information
|
|
1126
|
+
- preference: a durable preference or style
|
|
1127
|
+
- correction: a correction of a prior mistake
|
|
1128
|
+
- entity: a durable person, project, tool, company, or place
|
|
1129
|
+
- decision: a choice with rationale
|
|
1130
|
+
- relationship: a durable link between two entities
|
|
1131
|
+
- principle: a reusable rule or operating belief
|
|
1132
|
+
${resolveRecallAuxiliaryCapabilities(this.config).causalRuleExtraction ? "- rule: an explicit causal rule or constraint\n" : ""}- commitment: a promise, obligation, or deadline
|
|
1133
|
+
- moment: a significant milestone
|
|
1134
|
+
- skill: a demonstrated capability
|
|
1135
|
+
- procedure: an explicit reusable workflow with ordered procedureSteps
|
|
1136
|
+
- reasoning_trace: Stored solution chains \u2014 an explicitly narrated solution path with reasoningTrace. Use {"category": "reasoning_trace", "reasoningTrace": {"steps": [...], "finalAnswer": "..."}} only when the conversation provides the chain.
|
|
1117
1137
|
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1138
|
+
Rules:
|
|
1139
|
+
- Extract only new information stated or clearly established in the conversation.
|
|
1140
|
+
- Do not treat instruction text, schema placeholders, or examples as conversation evidence.
|
|
1141
|
+
- Facts, entity facts, profile updates, questions, and relationships must be grounded in the conversation.
|
|
1142
|
+
- Lines labelled [context user] or [context assistant] are reference context only. They may resolve references or complete a question-and-answer pair in a normal turn, but never alone establish durable information.
|
|
1143
|
+
- Questions are optional. Return an empty array when the conversation does not support a useful unresolved question.
|
|
1144
|
+
- Set confidence from source evidence: Explicit (0.95-1.0), Implied (0.70-0.94), Inferred (0.40-0.69), or Speculative (0.00-0.39). Corrections get highest confidence.
|
|
1145
|
+
- Use normalized, hyphenated entity names and keep the entity list short.
|
|
1146
|
+
- Keep facts standalone. Skip transient task state and operational noise such as routine scheduler, monitoring, or automation status.
|
|
1147
|
+
- Add structuredAttributes only for concrete values.
|
|
1148
|
+
- Include at most five durable relationships.${this.config.provenance?.enabled ? `
|
|
1149
|
+
- Each fact must include a quote copied verbatim from one contiguous conversation span.` : ""}${lifecycleCaps.extractionScopeClassification ? `
|
|
1150
|
+
- Set each fact scope to "global" for cross-project knowledge or "project" for codebase-specific knowledge.` : ""}
|
|
1151
|
+
${this.eventTimePromptInstruction()}
|
|
1152
|
+
Return only valid JSON matching this shape. Placeholder text describes field shape only and is never source evidence:
|
|
1153
|
+
${EXTRACTION_RESPONSE_SHAPE}
|
|
1126
1154
|
|
|
1127
1155
|
Conversation:
|
|
1128
1156
|
${truncatedConversation}`;
|
|
@@ -1190,14 +1218,8 @@ ${truncatedConversation}`;
|
|
|
1190
1218
|
role: "system",
|
|
1191
1219
|
content: this.buildExtractionInstructions(existingEntities) + `
|
|
1192
1220
|
|
|
1193
|
-
|
|
1194
|
-
{
|
|
1195
|
-
"facts": [{"category": "decision", "content": "Chose React over Vue for the dashboard rewrite", "importance": 8, "confidence": 0.9, "tags": ["frontend"], "scope": "project", "structuredAttributes": {"chosen": "React", "rejected": "Vue"}}, {"category": "fact", "content": "The API gateway uses rate limiting at 1000 req/min", "importance": 6, "confidence": 0.95, "tags": ["infra"], "scope": "project", "entityRef": "project-dashboard", "structuredAttributes": {"rate_limit": "1000 req/min"}}, {"category": "reasoning_trace", "content": "How I chose the dashboard rewrite framework", "confidence": 0.9, "tags": ["frontend"], "scope": "project", "reasoningTrace": {"steps": [{"order": 1, "description": "Listed constraints: SSR needed, team mostly JS"}, {"order": 2, "description": "Ran a spike in Vue 3 \u2014 worked, but ecosystem felt thin for our needs"}, {"order": 3, "description": "Ran the same spike in React \u2014 integrated faster with Next.js"}], "finalAnswer": "Picked React with Next.js for SSR + ecosystem fit"}}],
|
|
1196
|
-
"entities": [{"name": "person-sarah-chen", "type": "person", "facts": ["Leads the backend team", "Joined from Google in 2024"], "structuredSections": [{"key": "beliefs", "title": "Beliefs", "facts": ["Small teams should own whole systems."]}]}, {"name": "project-dashboard", "type": "project", "facts": ["React-based admin panel", "Deployed on AWS ECS"]}],
|
|
1197
|
-
"profileUpdates": ["User prefers TypeScript over plain JavaScript"],
|
|
1198
|
-
"questions": [{"question": "What database does the analytics service use?", "context": "Came up during discussion of migration plan", "priority": 0.5}],
|
|
1199
|
-
"relationships": [{"source": "person-sarah-chen", "target": "project-dashboard", "label": "leads development of"}]
|
|
1200
|
-
}`
|
|
1221
|
+
Return only valid JSON matching this shape. Placeholder text describes field shape only and is never source evidence:
|
|
1222
|
+
${EXTRACTION_RESPONSE_SHAPE}`
|
|
1201
1223
|
},
|
|
1202
1224
|
{ role: "user", content: conversation }
|
|
1203
1225
|
],
|
|
@@ -1226,37 +1248,14 @@ Respond with valid JSON matching this schema:
|
|
|
1226
1248
|
* Local LLMs sometimes hit token limits mid-JSON. This tries to salvage valid facts.
|
|
1227
1249
|
*/
|
|
1228
1250
|
extractPartialFacts(jsonStr) {
|
|
1229
|
-
const allowedCategories = /* @__PURE__ */ new Set([
|
|
1230
|
-
"fact",
|
|
1231
|
-
"preference",
|
|
1232
|
-
"correction",
|
|
1233
|
-
"entity",
|
|
1234
|
-
"decision",
|
|
1235
|
-
"relationship",
|
|
1236
|
-
"principle",
|
|
1237
|
-
"commitment",
|
|
1238
|
-
"moment",
|
|
1239
|
-
"skill",
|
|
1240
|
-
"rule",
|
|
1241
|
-
"procedure",
|
|
1242
|
-
"reasoning_trace"
|
|
1243
|
-
]);
|
|
1244
|
-
const allowedEntityTypes = /* @__PURE__ */ new Set([
|
|
1245
|
-
"person",
|
|
1246
|
-
"project",
|
|
1247
|
-
"tool",
|
|
1248
|
-
"company",
|
|
1249
|
-
"place",
|
|
1250
|
-
"other"
|
|
1251
|
-
]);
|
|
1252
1251
|
const facts = [];
|
|
1253
1252
|
const entities = [];
|
|
1254
1253
|
try {
|
|
1255
1254
|
const factRegex = /\{\s*"category"\s*:\s*"([^"]+)"\s*,\s*"content"\s*:\s*"([^"]+)"\s*,\s*"confidence"\s*:\s*([0-9.]+)/g;
|
|
1256
1255
|
let match;
|
|
1257
1256
|
while ((match = factRegex.exec(jsonStr)) !== null) {
|
|
1258
|
-
const
|
|
1259
|
-
|
|
1257
|
+
const category = match[1]?.trim() ?? "";
|
|
1258
|
+
if (!isMemoryCategory(category)) continue;
|
|
1260
1259
|
facts.push({
|
|
1261
1260
|
category,
|
|
1262
1261
|
content: match[2].replace(/\\n/g, "\n").replace(/\\"/g, '"'),
|
|
@@ -1266,8 +1265,8 @@ Respond with valid JSON matching this schema:
|
|
|
1266
1265
|
}
|
|
1267
1266
|
const entityRegex = /\{\s*"name"\s*:\s*"([^"]+)"\s*,\s*"type"\s*:\s*"([^"]+)"/g;
|
|
1268
1267
|
while ((match = entityRegex.exec(jsonStr)) !== null) {
|
|
1269
|
-
const
|
|
1270
|
-
|
|
1268
|
+
const type = extractionEntityType(match[2]);
|
|
1269
|
+
if (type === void 0) continue;
|
|
1271
1270
|
entities.push({
|
|
1272
1271
|
name: match[1],
|
|
1273
1272
|
type,
|
|
@@ -1276,7 +1275,7 @@ Respond with valid JSON matching this schema:
|
|
|
1276
1275
|
}
|
|
1277
1276
|
} catch {
|
|
1278
1277
|
}
|
|
1279
|
-
return { facts, entities, profileUpdates: [], questions: [] };
|
|
1278
|
+
return this.normalizeExtractionResultPayload({ facts, entities, profileUpdates: [], questions: [] });
|
|
1280
1279
|
}
|
|
1281
1280
|
/**
|
|
1282
1281
|
* Bi-temporal event-time extraction instruction (#1578 PR2). Emitted on
|
|
@@ -1289,15 +1288,7 @@ Respond with valid JSON matching this schema:
|
|
|
1289
1288
|
eventTimePromptInstruction() {
|
|
1290
1289
|
if (!this.config.temporalBiTemporal) return "";
|
|
1291
1290
|
return `
|
|
1292
|
-
|
|
1293
|
-
When a fact has an explicit temporal anchor \u2014 a date, month, season, or relative time expression stating WHEN the fact became (or stopped being) true \u2014 capture it verbatim in an "eventTime" field on that fact. Examples:
|
|
1294
|
-
- "We moved offices in March" \u2192 "eventTime": "last March"
|
|
1295
|
-
- "The API has been rate-limited since 2024" \u2192 "eventTime": "since 2024"
|
|
1296
|
-
- "I switched to PostgreSQL on 2025-01-15" \u2192 "eventTime": "2025-01-15"
|
|
1297
|
-
- "We used MongoDB until June 2025" \u2192 "eventTime": "until 2025-06"
|
|
1298
|
-
Accepted forms: ISO dates ("2025-03-01"), year-month ("2025-03"), month/season + year ("March 2025", "summer 2024"), relative ("yesterday", "last week", "this month", "next year", "last December"), and open-ended ("since 2024", "until 2025-06").
|
|
1299
|
-
Omit "eventTime" when the fact has no explicit temporal anchor \u2014 do NOT guess or infer dates. The system resolves the expression against the conversation's own timestamp, not today's date.
|
|
1300
|
-
`;
|
|
1291
|
+
When a fact states when it became or stopped being true, copy that explicit temporal expression verbatim into "eventTime". Omit "eventTime" when no such expression appears; never infer dates.`;
|
|
1301
1292
|
}
|
|
1302
1293
|
/**
|
|
1303
1294
|
* Build extraction instructions shared between local and cloud LLM.
|
|
@@ -1322,12 +1313,14 @@ Memory categories:
|
|
|
1322
1313
|
- reasoning_trace: A stored solution chain / chain-of-thought the user walked through to solve a problem (e.g. "Here's how I debugged the latency spike: first I checked\u2026, then I\u2026, finally I\u2026"). Set category to "reasoning_trace". Use "content" for a short title summarising the problem (e.g. "How I debugged the staging latency spike"). Add "reasoningTrace": {"steps": [{"order": number, "description": "what happened at this step"}, \u2026], "finalAnswer": "the conclusion or answer", "observedOutcome": "optional confirmation of how it played out"}. Require at least two ordered steps AND a finalAnswer. Use this category only when the user explicitly narrates their reasoning \u2014 not for ordinary decisions (use "decision") or reusable workflows (use "procedure").
|
|
1323
1314
|
|
|
1324
1315
|
Rules:
|
|
1325
|
-
- Only extract genuinely
|
|
1326
|
-
-
|
|
1316
|
+
- Only extract genuinely new information worth remembering across sessions.
|
|
1317
|
+
- Statements must be grounded in the conversation.
|
|
1318
|
+
- Do not treat instruction text, schema placeholders, or examples as conversation evidence.
|
|
1319
|
+
- Lines labelled [context user] or [context assistant] are reference context only. They may resolve references or complete a question-and-answer pair in a normal turn, but never alone establish durable information.
|
|
1320
|
+
- Skip transient task details and operational noise, including routine scheduler, monitoring, or automation status.
|
|
1327
1321
|
- Priority: corrections > principles${resolveRecallAuxiliaryCapabilities(this.config).causalRuleExtraction ? " > rules" : ""} > preferences > commitments > decisions > relationships > entities > moments > skills > facts
|
|
1328
|
-
- Corrections
|
|
1329
|
-
- Each fact should be a standalone, self-contained statement
|
|
1330
|
-
- Lines labelled [context user] or [context assistant] are reference context only. Use them to resolve pronouns and adjacent question/answer pairs, but do not extract a memory stated only in context lines unless a normal [user] or [assistant] line confirms or completes it.
|
|
1322
|
+
- Corrections get highest confidence.
|
|
1323
|
+
- Each fact should be a standalone, self-contained statement.
|
|
1331
1324
|
- Entity references should use normalized names (lowercase, hyphenated: "jane-doe", "acme-corp")
|
|
1332
1325
|
- CRITICAL: Entity names must be CANONICAL. Always use the hyphenated multi-word form: "acme-corp" NOT "acmecorp" or "acme". "jane-doe" NOT "janedoe" or "jane". If unsure, prefer the most specific full name.
|
|
1333
1326
|
- Avoid creating entities typed as "other" when a more specific type fits (company, project, tool, person, place)
|
|
@@ -1346,15 +1339,7 @@ Scope classification:
|
|
|
1346
1339
|
For each fact, set "scope" to one of:
|
|
1347
1340
|
- "global" \u2014 knowledge that applies across projects: core framework/library bugs, API behavior patterns, user preferences (editor, language, style), tool configurations, general coding patterns, infrastructure knowledge, technology facts not tied to one codebase
|
|
1348
1341
|
- "project" \u2014 knowledge specific to one codebase: file paths, environment configs, deployment details, project-specific workarounds, team/stakeholder info tied to one project, repo-specific conventions
|
|
1349
|
-
When in doubt, prefer "project" \u2014 it is safer to keep knowledge scoped narrowly
|
|
1350
|
-
Examples:
|
|
1351
|
-
"Magento 2.4.8 has a race condition in checkout" \u2192 "global"
|
|
1352
|
-
"User prefers dark mode in all editors" \u2192 "global"
|
|
1353
|
-
"The staging server is at staging.acme.com" \u2192 "project"
|
|
1354
|
-
"The deploy script lives at scripts/deploy.sh" \u2192 "project"
|
|
1355
|
-
"PostgreSQL 15 requires the uuid-ossp extension for gen_random_uuid()" \u2192 "global"
|
|
1356
|
-
"The acme-store repo uses a custom Webpack config for SSR" \u2192 "project"` : ""}
|
|
1357
|
-
|
|
1342
|
+
When in doubt, prefer "project" \u2014 it is safer to keep knowledge scoped narrowly.` : ""}
|
|
1358
1343
|
Entity creation rules (STRICT):
|
|
1359
1344
|
- Only create entities for DURABLE things: real people, companies, products, tools, ongoing projects
|
|
1360
1345
|
- NEVER create entities for transient items: individual PRs, branches, Jira tickets, meetings, agent task IDs, log files, database tables, cron job runs, sessions
|
|
@@ -1375,14 +1360,9 @@ Also extract relationships between entities mentioned in the conversation.
|
|
|
1375
1360
|
- Only include clear, durable relationships (e.g., "works at", "created", "manages", "uses")
|
|
1376
1361
|
- Use normalized entity names (e.g., "person-jane-doe", "company-acme-corp")
|
|
1377
1362
|
|
|
1378
|
-
|
|
1363
|
+
Questions are optional. Include only source-grounded unresolved questions that would be useful in future sessions; otherwise return an empty array.
|
|
1379
1364
|
|
|
1380
|
-
Finally, write a brief identity reflection about the
|
|
1381
|
-
- What communication patterns did the agent show? (e.g., proactive vs reactive, verbose vs concise)
|
|
1382
|
-
- Did the agent handle the user's needs well or miss something?
|
|
1383
|
-
- What behavioral tendencies are visible? (e.g., cautious, creative, thorough, impatient)
|
|
1384
|
-
- What could the agent improve next time?
|
|
1385
|
-
Do NOT write about the extraction process itself. Do NOT say things like "I extracted durable facts" \u2014 that's about YOUR job, not the agent's behavior.`;
|
|
1365
|
+
Finally, write a brief identity reflection about the agent who had this conversation, based only on the conversation. Do not write about the extraction process.`;
|
|
1386
1366
|
}
|
|
1387
1367
|
async consolidate(newMemories, existingMemories, currentProfile) {
|
|
1388
1368
|
const newList = newMemories.map(
|
|
@@ -2453,4 +2433,4 @@ export {
|
|
|
2453
2433
|
shouldEnableLocalExtractionThinking,
|
|
2454
2434
|
ExtractionEngine
|
|
2455
2435
|
};
|
|
2456
|
-
//# sourceMappingURL=chunk-
|
|
2436
|
+
//# sourceMappingURL=chunk-BUK2FXOL.js.map
|