memorylake-openclaw 1.0.0 → 1.0.2-beta.1
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/docs/openclaw.mdx +3 -3
- package/index.ts +203 -118
- package/package.json +1 -1
package/docs/openclaw.mdx
CHANGED
|
@@ -54,7 +54,7 @@ The agent gets eight tools it can call during conversations:
|
|
|
54
54
|
| `memory_forget` | Delete a memory by ID |
|
|
55
55
|
| `document_search` | Search project documents for relevant paragraphs, tables, and figures |
|
|
56
56
|
| `advanced_web_search` | Optional web search tool backed by the unified search API with plugin-level domain and locale constraints |
|
|
57
|
-
| `open_data_search` |
|
|
57
|
+
| `open_data_search` | Search across open datasets — academic, clinical, drug, financial, economic, and more — routed to the appropriate proprietary data source based on the `dataset` field |
|
|
58
58
|
|
|
59
59
|
<Note>`open_data_search` requires the project to have at least one open data industry configured in MemoryLake. The `dataset` parameter is required and validated against the project's subscribed datasets at call time. The agent is automatically informed of available datasets via context injection at the start of each session. Supported datasets: `research/academic`, `clinical/trials`, `drug/database`, `financial/markets`, `company/fundamentals`, `economic/data`, `patents/ip`.</Note>
|
|
60
60
|
|
|
@@ -86,7 +86,7 @@ openclaw memorylake stats
|
|
|
86
86
|
| `webSearchCountry` | `string` | — | Optional ISO country code for localizing `advanced_web_search` |
|
|
87
87
|
| `webSearchTimezone` | `string` | — | Optional IANA timezone for localizing `advanced_web_search` |
|
|
88
88
|
|
|
89
|
-
<Note>`advanced_web_search`
|
|
89
|
+
<Note>`advanced_web_search` is registered as an optional OpenClaw tool, so it must be explicitly allowed before an agent can call it.</Note>
|
|
90
90
|
|
|
91
91
|
## Key Features
|
|
92
92
|
|
|
@@ -98,7 +98,7 @@ openclaw memorylake stats
|
|
|
98
98
|
|
|
99
99
|
## Conclusion
|
|
100
100
|
|
|
101
|
-
The `memorylake-openclaw` plugin gives OpenClaw agents persistent memory with minimal setup. Your agents can remember user preferences, facts, and context across sessions automatically — and
|
|
101
|
+
The `memorylake-openclaw` plugin gives OpenClaw agents persistent memory with minimal setup. Your agents can remember user preferences, facts, and context across sessions automatically — and search across a wide range of open datasets when deeper external knowledge is needed.
|
|
102
102
|
|
|
103
103
|
{/*<CardGroup cols={2}>
|
|
104
104
|
<Card title="MemoryLake" icon="brain" href="https://app.memorylake.ai">
|
package/index.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Long-term memory via MemoryLake platform.
|
|
5
5
|
*
|
|
6
6
|
* Features:
|
|
7
|
-
* -
|
|
7
|
+
* - 9 tools: memory_search, memory_list, memory_store, memory_get, memory_forget, document_search, document_download, advanced_web_search, open_data_search
|
|
8
8
|
* - Auto-recall: injects relevant memories and document excerpts before each agent turn
|
|
9
9
|
* - Auto-capture: stores key facts scoped to the current session after each agent turn
|
|
10
10
|
* - CLI: openclaw memorylake search, openclaw memorylake stats
|
|
@@ -293,6 +293,7 @@ interface MemoryLakeProvider {
|
|
|
293
293
|
getAll(options: ListOptions): Promise<MemoryItem[]>;
|
|
294
294
|
delete(memoryId: string): Promise<void>;
|
|
295
295
|
searchDocuments(query: string, topN: number): Promise<DocumentSearchResponse>;
|
|
296
|
+
getDocumentDownloadUrl(documentId: string): Promise<string>;
|
|
296
297
|
searchWeb(query: string, options: WebSearchOptions): Promise<WebSearchResponse>;
|
|
297
298
|
searchOpenData(query: string, options: OpenDataSearchOptions): Promise<OpenDataSearchResponse>;
|
|
298
299
|
getProject(): Promise<ProjectInfo>;
|
|
@@ -418,6 +419,24 @@ class PlatformProvider implements MemoryLakeProvider {
|
|
|
418
419
|
};
|
|
419
420
|
}
|
|
420
421
|
|
|
422
|
+
async getDocumentDownloadUrl(documentId: string): Promise<string> {
|
|
423
|
+
const downloadPath = `openapi/memorylake/api/v1/projects/${this.projectId}/documents/${documentId}/download`;
|
|
424
|
+
const resp = await this.http.get(downloadPath, {
|
|
425
|
+
followRedirect: false,
|
|
426
|
+
responseType: "text" as any,
|
|
427
|
+
throwHttpErrors: false,
|
|
428
|
+
});
|
|
429
|
+
if (resp.statusCode === 303 || resp.statusCode === 302) {
|
|
430
|
+
const location = resp.headers.location;
|
|
431
|
+
if (!location) throw new Error("Download redirect missing Location header");
|
|
432
|
+
return location;
|
|
433
|
+
}
|
|
434
|
+
if (resp.statusCode === 404) {
|
|
435
|
+
throw new Error(`Document not found: ${documentId}`);
|
|
436
|
+
}
|
|
437
|
+
throw new Error(`Unexpected download response status: ${resp.statusCode}`);
|
|
438
|
+
}
|
|
439
|
+
|
|
421
440
|
async searchWeb(query: string, options: WebSearchOptions): Promise<WebSearchResponse> {
|
|
422
441
|
const domain = options.domain != null ? normalizeWebSearchDomain(options.domain) : "web";
|
|
423
442
|
const body: Record<string, unknown> = {
|
|
@@ -569,11 +588,12 @@ function buildDocumentContext(
|
|
|
569
588
|
|
|
570
589
|
for (const result of results) {
|
|
571
590
|
const source = result.document_name ?? result.source_document?.file_name ?? "unknown";
|
|
591
|
+
const docId = result.document_id ?? "unknown";
|
|
572
592
|
const highlight = result.highlight;
|
|
573
593
|
|
|
574
594
|
if (result.type === "table") {
|
|
575
595
|
const title = result.title || "Untitled Table";
|
|
576
|
-
parts.push(`### Table: ${title} (from ${source})`);
|
|
596
|
+
parts.push(`### Table: ${title} (from ${source}, doc_id: ${docId})`);
|
|
577
597
|
if (result.footnote) parts.push(`Note: ${result.footnote}`);
|
|
578
598
|
|
|
579
599
|
for (const innerTable of highlight?.inner_tables ?? []) {
|
|
@@ -587,14 +607,14 @@ function buildDocumentContext(
|
|
|
587
607
|
if (chunk.text) parts.push(chunk.text.slice(0, maxChunkLength));
|
|
588
608
|
}
|
|
589
609
|
} else if (result.type === "paragraph") {
|
|
590
|
-
parts.push(`### Paragraph (from ${source}):`);
|
|
610
|
+
parts.push(`### Paragraph (from ${source}, doc_id: ${docId}):`);
|
|
591
611
|
for (const chunk of highlight?.chunks ?? []) {
|
|
592
612
|
if (chunk.text) parts.push(chunk.text.slice(0, maxChunkLength));
|
|
593
613
|
}
|
|
594
614
|
} else if (result.type === "figure") {
|
|
595
615
|
const figure = highlight?.figure;
|
|
596
616
|
if (figure) {
|
|
597
|
-
parts.push(`### Figure (from ${source}):`);
|
|
617
|
+
parts.push(`### Figure (from ${source}, doc_id: ${docId}):`);
|
|
598
618
|
if (figure.caption) parts.push(`Caption: ${figure.caption}`);
|
|
599
619
|
const text = figure.text || figure.summary_text || "";
|
|
600
620
|
if (text) parts.push(text);
|
|
@@ -820,6 +840,62 @@ const memoryPlugin = {
|
|
|
820
840
|
// Cache project industries per session — fetched once, reused on subsequent prompts
|
|
821
841
|
const sessionIndustriesCache = new Map<string, OpenDataIndustry[]>();
|
|
822
842
|
|
|
843
|
+
// ========================================================================
|
|
844
|
+
// System Prompt: Memory Section (injected into every system prompt)
|
|
845
|
+
// ========================================================================
|
|
846
|
+
api.registerMemoryPromptSection((params) => {
|
|
847
|
+
const lines: string[] = [
|
|
848
|
+
"## Memory (MANDATORY — MemoryLake)",
|
|
849
|
+
"",
|
|
850
|
+
"You have access to MemoryLake, a long-term memory system that stores the user's preferences, history, personal context, and uploaded documents across sessions.",
|
|
851
|
+
"",
|
|
852
|
+
];
|
|
853
|
+
|
|
854
|
+
if (params.availableTools.has("memory_search")) {
|
|
855
|
+
lines.push(
|
|
856
|
+
"### memory_search — MUST be called BEFORE every response",
|
|
857
|
+
"",
|
|
858
|
+
"**RULE: Your FIRST action for EVERY user message MUST be calling `memory_search`.** No exceptions.",
|
|
859
|
+
"",
|
|
860
|
+
"This tool searches BOTH memories AND documents in one call. It returns personal context (preferences, history, decisions) and relevant document excerpts (uploaded files, tables, figures).",
|
|
861
|
+
"",
|
|
862
|
+
"This applies to ALL types of questions, not just questions about memory or recall:",
|
|
863
|
+
"- Greetings -> search for who the user is",
|
|
864
|
+
"- Recommendations (books, activities, food) -> search for preferences and interests",
|
|
865
|
+
"- Advice (what to wear, what to eat) -> search for habits, location, preferences",
|
|
866
|
+
"- Tasks (write an email, self-introduction) -> search for user name, role, background",
|
|
867
|
+
"- Document questions -> search for relevant uploaded document content",
|
|
868
|
+
"- General chat -> search for recent context and ongoing topics",
|
|
869
|
+
"",
|
|
870
|
+
"Derive a broad query from the user's message. For example:",
|
|
871
|
+
"- User asks for a book recommendation -> search: reading preferences favorite books",
|
|
872
|
+
"- User asks about weekend plans -> search: hobbies interests weekend activities",
|
|
873
|
+
"- User says hello -> search: user name background recent context",
|
|
874
|
+
"",
|
|
875
|
+
"**If you respond without calling memory_search first, your response is WRONG.**",
|
|
876
|
+
"",
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
if (params.availableTools.has("memory_list")) {
|
|
881
|
+
lines.push(
|
|
882
|
+
"### memory_list",
|
|
883
|
+
"- When the user asks what you remember or wants to see all stored memories, call `memory_list`.",
|
|
884
|
+
"",
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
if (params.availableTools.has("memory_forget")) {
|
|
889
|
+
lines.push(
|
|
890
|
+
"### memory_forget",
|
|
891
|
+
"- When the user explicitly asks to delete or forget a specific memory, call `memory_forget` with the memory ID.",
|
|
892
|
+
"",
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
return lines;
|
|
897
|
+
});
|
|
898
|
+
|
|
823
899
|
api.logger.info(
|
|
824
900
|
`memorylake-openclaw: registered (user: ${cfg.userId}, autoRecall: ${cfg.autoRecall}, autoCapture: ${cfg.autoCapture}, autoUpload: ${cfg.autoUpload})`,
|
|
825
901
|
);
|
|
@@ -858,7 +934,7 @@ const memoryPlugin = {
|
|
|
858
934
|
name: "memory_search",
|
|
859
935
|
label: "Memory Search",
|
|
860
936
|
description:
|
|
861
|
-
"Search through long-term memories stored in MemoryLake.
|
|
937
|
+
"MANDATORY: Search through long-term memories AND uploaded documents stored in MemoryLake. You MUST call this tool at the start of every conversation to recall the user's context, preferences, past decisions, previously discussed topics, and relevant document content. Always search before answering.",
|
|
862
938
|
parameters: Type.Object({
|
|
863
939
|
query: Type.String({ description: "Search query" }),
|
|
864
940
|
limit: Type.Optional(
|
|
@@ -893,54 +969,65 @@ const memoryPlugin = {
|
|
|
893
969
|
scope?: "session" | "long-term" | "all";
|
|
894
970
|
};
|
|
895
971
|
|
|
896
|
-
|
|
897
|
-
|
|
972
|
+
const [memoryResult, docResult] = await Promise.allSettled([
|
|
973
|
+
effectiveProvider.search(
|
|
898
974
|
query,
|
|
899
975
|
buildSearchOptions(effectiveCfg, userId, limit),
|
|
900
|
-
)
|
|
976
|
+
),
|
|
977
|
+
effectiveProvider.searchDocuments(query, effectiveCfg.topK),
|
|
978
|
+
]);
|
|
901
979
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
],
|
|
907
|
-
details: { count: 0 },
|
|
908
|
-
};
|
|
909
|
-
}
|
|
980
|
+
const sections: string[] = [];
|
|
981
|
+
let memoryCount = 0;
|
|
982
|
+
let docCount = 0;
|
|
983
|
+
let sanitizedMemories: { id: string; content: string; created_at: string }[] = [];
|
|
910
984
|
|
|
985
|
+
if (memoryResult.status === "fulfilled" && memoryResult.value.length > 0) {
|
|
986
|
+
const results = memoryResult.value;
|
|
987
|
+
memoryCount = results.length;
|
|
911
988
|
const text = results
|
|
912
|
-
.map(
|
|
913
|
-
(r, i) =>
|
|
914
|
-
`${i + 1}. ${r.content} (id: ${r.id})`,
|
|
915
|
-
)
|
|
989
|
+
.map((r, i) => `${i + 1}. ${r.content} (id: ${r.id})`)
|
|
916
990
|
.join("\n");
|
|
917
|
-
|
|
918
|
-
|
|
991
|
+
sections.push(`## Memories\nFound ${results.length} memories:\n\n${text}`);
|
|
992
|
+
sanitizedMemories = results.map((r) => ({
|
|
919
993
|
id: r.id,
|
|
920
994
|
content: r.content,
|
|
921
995
|
created_at: r.created_at,
|
|
922
996
|
}));
|
|
997
|
+
} else if (memoryResult.status === "rejected") {
|
|
998
|
+
sections.push(`## Memories\nMemory search failed: ${String(memoryResult.reason)}`);
|
|
999
|
+
}
|
|
923
1000
|
|
|
1001
|
+
if (docResult.status === "fulfilled" && docResult.value.results.length > 0) {
|
|
1002
|
+
docCount = docResult.value.results.length;
|
|
1003
|
+
const context = buildDocumentContext(docResult.value.results);
|
|
1004
|
+
sections.push(`## Documents\nFound ${docCount} document results:\n\n${context}`);
|
|
1005
|
+
} else if (docResult.status === "rejected") {
|
|
1006
|
+
sections.push(`## Documents\nDocument search failed: ${String(docResult.reason)}`);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
if (memoryCount === 0 && docCount === 0) {
|
|
924
1010
|
return {
|
|
925
1011
|
content: [
|
|
926
|
-
{
|
|
927
|
-
type: "text",
|
|
928
|
-
text: `Found ${results.length} memories:\n\n${text}`,
|
|
929
|
-
},
|
|
930
|
-
],
|
|
931
|
-
details: { count: results.length, memories: sanitized },
|
|
932
|
-
};
|
|
933
|
-
} catch (err) {
|
|
934
|
-
return {
|
|
935
|
-
content: [
|
|
936
|
-
{
|
|
937
|
-
type: "text",
|
|
938
|
-
text: `Memory search failed: ${String(err)}`,
|
|
939
|
-
},
|
|
1012
|
+
{ type: "text", text: "No relevant memories or documents found." },
|
|
940
1013
|
],
|
|
941
|
-
details: {
|
|
1014
|
+
details: { count: 0 },
|
|
942
1015
|
};
|
|
943
1016
|
}
|
|
1017
|
+
|
|
1018
|
+
return {
|
|
1019
|
+
content: [
|
|
1020
|
+
{
|
|
1021
|
+
type: "text",
|
|
1022
|
+
text: sections.join("\n\n"),
|
|
1023
|
+
},
|
|
1024
|
+
],
|
|
1025
|
+
details: {
|
|
1026
|
+
memoryCount,
|
|
1027
|
+
documentCount: docCount,
|
|
1028
|
+
memories: sanitizedMemories,
|
|
1029
|
+
},
|
|
1030
|
+
};
|
|
944
1031
|
},
|
|
945
1032
|
}),
|
|
946
1033
|
{ name: "memory_search" },
|
|
@@ -1233,6 +1320,55 @@ const memoryPlugin = {
|
|
|
1233
1320
|
{ name: "document_search" },
|
|
1234
1321
|
);
|
|
1235
1322
|
|
|
1323
|
+
api.registerTool(
|
|
1324
|
+
(ctx) => ({
|
|
1325
|
+
name: "document_download",
|
|
1326
|
+
label: "Document Download",
|
|
1327
|
+
description:
|
|
1328
|
+
"Download a document (image, PDF, etc.) from MemoryLake and send it to the user. Returns a temporary download URL. Use document_search first to find the document_id.",
|
|
1329
|
+
parameters: Type.Object({
|
|
1330
|
+
documentId: Type.String({
|
|
1331
|
+
description:
|
|
1332
|
+
"The document ID to download (from document_search results or document listing)",
|
|
1333
|
+
}),
|
|
1334
|
+
}),
|
|
1335
|
+
async execute(_toolCallId, params) {
|
|
1336
|
+
const effectiveCfg = resolveConfig(ctx);
|
|
1337
|
+
const effectiveProvider = getProvider(effectiveCfg);
|
|
1338
|
+
const { documentId } = params as { documentId: string };
|
|
1339
|
+
|
|
1340
|
+
try {
|
|
1341
|
+
const downloadUrl =
|
|
1342
|
+
await effectiveProvider.getDocumentDownloadUrl(documentId);
|
|
1343
|
+
|
|
1344
|
+
return {
|
|
1345
|
+
content: [
|
|
1346
|
+
{
|
|
1347
|
+
type: "text",
|
|
1348
|
+
text: `Document ${documentId} is ready. Tell the user you are sending the file now.\nMEDIA: ${downloadUrl}`,
|
|
1349
|
+
},
|
|
1350
|
+
],
|
|
1351
|
+
details: {
|
|
1352
|
+
media: { mediaUrl: downloadUrl },
|
|
1353
|
+
documentId,
|
|
1354
|
+
},
|
|
1355
|
+
};
|
|
1356
|
+
} catch (err) {
|
|
1357
|
+
return {
|
|
1358
|
+
content: [
|
|
1359
|
+
{
|
|
1360
|
+
type: "text",
|
|
1361
|
+
text: `Document download failed: ${String(err)}`,
|
|
1362
|
+
},
|
|
1363
|
+
],
|
|
1364
|
+
details: { error: String(err) },
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
},
|
|
1368
|
+
}),
|
|
1369
|
+
{ name: "document_download" },
|
|
1370
|
+
);
|
|
1371
|
+
|
|
1236
1372
|
api.registerTool(
|
|
1237
1373
|
(ctx) => ({
|
|
1238
1374
|
name: "advanced_web_search",
|
|
@@ -1506,7 +1642,6 @@ const memoryPlugin = {
|
|
|
1506
1642
|
}
|
|
1507
1643
|
},
|
|
1508
1644
|
}),
|
|
1509
|
-
{ optional: true },
|
|
1510
1645
|
);
|
|
1511
1646
|
|
|
1512
1647
|
// ========================================================================
|
|
@@ -1811,7 +1946,9 @@ const memoryPlugin = {
|
|
|
1811
1946
|
}
|
|
1812
1947
|
|
|
1813
1948
|
// ------------------------------------------------------------------
|
|
1814
|
-
// Auto-recall: inject
|
|
1949
|
+
// Auto-recall: inject system-level memory instructions and open data
|
|
1950
|
+
// categories before prompt build. Memory content is NOT pre-fetched;
|
|
1951
|
+
// the model is instructed to call memory_search itself.
|
|
1815
1952
|
// ------------------------------------------------------------------
|
|
1816
1953
|
if (cfg.autoRecall) {
|
|
1817
1954
|
api.on("before_prompt_build", async (event, ctx) => {
|
|
@@ -1826,18 +1963,6 @@ const memoryPlugin = {
|
|
|
1826
1963
|
|
|
1827
1964
|
const sessionId = (ctx as any)?.sessionId ?? undefined;
|
|
1828
1965
|
|
|
1829
|
-
// LLM-rewrite FIRST — short prompts like "它呢?" can become meaningful
|
|
1830
|
-
// search queries when the LLM has conversation history context.
|
|
1831
|
-
const searchQuery = await rewriteQueryForSearch(event.prompt, event.messages, ctx);
|
|
1832
|
-
|
|
1833
|
-
// Only skip if the rewritten result is still too short
|
|
1834
|
-
if (searchQuery.length < 5) {
|
|
1835
|
-
api.logger.info(
|
|
1836
|
-
`memorylake-openclaw: skipping auto-recall, rewritten query too short (${searchQuery.length} chars)`,
|
|
1837
|
-
);
|
|
1838
|
-
return;
|
|
1839
|
-
}
|
|
1840
|
-
|
|
1841
1966
|
// Fetch industries once per session, then cache
|
|
1842
1967
|
let industries: OpenDataIndustry[] | undefined;
|
|
1843
1968
|
if (sessionId && sessionIndustriesCache.has(sessionId)) {
|
|
@@ -1854,82 +1979,42 @@ const memoryPlugin = {
|
|
|
1854
1979
|
}
|
|
1855
1980
|
}
|
|
1856
1981
|
|
|
1857
|
-
const
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
.filter((m) => m.has_unresolved_conflict)
|
|
1879
|
-
.map((m) => m.id);
|
|
1880
|
-
if (conflictedIds.length > 0) {
|
|
1881
|
-
try {
|
|
1882
|
-
const conflicts = await effectiveProvider.listConflicts(conflictedIds, effectiveCfg.userId);
|
|
1883
|
-
if (conflicts.length > 0) {
|
|
1884
|
-
const conflictContext = buildConflictContext(conflicts);
|
|
1885
|
-
contextParts.push(
|
|
1886
|
-
`<memory-conflicts>\nThe following conflicts exist among the recalled memories. ` +
|
|
1887
|
-
`Consider these contradictions when using the above memories.\n` +
|
|
1888
|
-
`If you have not already informed the user about these conflicts in this conversation, briefly mention that some recalled memories contain contradictions and note which points are uncertain. Do not repeat this notice if you have already done so.\n` +
|
|
1889
|
-
`${conflictContext}\n</memory-conflicts>`,
|
|
1890
|
-
);
|
|
1891
|
-
api.logger.info(
|
|
1892
|
-
`memorylake-openclaw: injecting ${conflicts.length} memory conflicts into context`,
|
|
1893
|
-
);
|
|
1894
|
-
}
|
|
1895
|
-
} catch (err) {
|
|
1896
|
-
api.logger.warn(`memorylake-openclaw: conflict fetch failed: ${String(err)}`);
|
|
1897
|
-
}
|
|
1898
|
-
}
|
|
1899
|
-
} else if (memoryResult.status === "rejected") {
|
|
1900
|
-
api.logger.warn(`memorylake-openclaw: memory recall failed: ${String(memoryResult.reason)}`);
|
|
1901
|
-
}
|
|
1902
|
-
|
|
1903
|
-
if (docResult.status === "fulfilled" && docResult.value.results.length > 0) {
|
|
1904
|
-
const docContext = buildDocumentContext(docResult.value.results);
|
|
1905
|
-
contextParts.push(
|
|
1906
|
-
`<relevant-documents>\nThe following document excerpts may be relevant to this conversation:\n${docContext}\n</relevant-documents>`,
|
|
1907
|
-
);
|
|
1908
|
-
api.logger.info(
|
|
1909
|
-
`memorylake-openclaw: injecting ${docResult.value.results.length} document results into context`,
|
|
1910
|
-
);
|
|
1911
|
-
} else if (docResult.status === "rejected") {
|
|
1912
|
-
api.logger.warn(`memorylake-openclaw: document search failed: ${String(docResult.reason)}`);
|
|
1913
|
-
}
|
|
1914
|
-
|
|
1915
|
-
const result: { prependContext?: string; appendSystemContext?: string } = {};
|
|
1916
|
-
|
|
1917
|
-
if (contextParts.length > 0) {
|
|
1918
|
-
result.prependContext = contextParts.join("\n\n");
|
|
1919
|
-
}
|
|
1920
|
-
|
|
1982
|
+
const result: { prependSystemContext?: string; appendSystemContext?: string } = {};
|
|
1983
|
+
|
|
1984
|
+
const memoryDirectiveParts: string[] = [
|
|
1985
|
+
`<memory-instructions>`,
|
|
1986
|
+
`You have access to the user's long-term memory AND documents via MemoryLake (memory_search tool).`,
|
|
1987
|
+
`The memory_search tool searches both memories and uploaded documents in a single call.`,
|
|
1988
|
+
``,
|
|
1989
|
+
`MANDATORY PROCEDURE for EVERY user message:`,
|
|
1990
|
+
`1. Read the user's message.`,
|
|
1991
|
+
`2. Call memory_search with a query derived from the message — this step is NOT optional.`,
|
|
1992
|
+
`3. Use the search results (memories + documents) to personalize your response.`,
|
|
1993
|
+
`4. Respond to the user.`,
|
|
1994
|
+
``,
|
|
1995
|
+
`This applies to ALL messages — greetings, recommendations, advice, casual chat, tasks, questions.`,
|
|
1996
|
+
`The user's preferences, history, name, hobbies, context, and documents are stored in MemoryLake.`,
|
|
1997
|
+
`A response without memory_search is INCORRECT because it ignores personalized context.`,
|
|
1998
|
+
`</memory-instructions>`,
|
|
1999
|
+
];
|
|
2000
|
+
result.prependSystemContext = memoryDirectiveParts.join("\n");
|
|
2001
|
+
|
|
2002
|
+
const appendParts: string[] = [];
|
|
1921
2003
|
if (industries && industries.length > 0) {
|
|
1922
2004
|
const categoryList = industries
|
|
1923
2005
|
.map((ind) => `- ${ind.id}: ${ind.name}${ind.description ? ` — ${ind.description}` : ""}`)
|
|
1924
2006
|
.join("\n");
|
|
1925
|
-
|
|
1926
|
-
`<open-data-categories>\nThis project has access to the following open data categories via the open_data_search tool:\n${categoryList}\nWhen the user's question relates to any of these categories, use the open_data_search tool to retrieve relevant data.\n</open-data-categories
|
|
2007
|
+
appendParts.push(
|
|
2008
|
+
`<open-data-categories>\nThis project has access to the following open data categories via the open_data_search tool:\n${categoryList}\nWhen the user's question relates to any of these categories, use the open_data_search tool to retrieve relevant data.\n</open-data-categories>`,
|
|
2009
|
+
);
|
|
1927
2010
|
api.logger.info(
|
|
1928
2011
|
`memorylake-openclaw: injecting ${industries.length} open data categories into system context`,
|
|
1929
2012
|
);
|
|
1930
2013
|
}
|
|
1931
2014
|
|
|
1932
|
-
if (
|
|
2015
|
+
if (appendParts.length > 0) {
|
|
2016
|
+
result.appendSystemContext = appendParts.join("\n\n");
|
|
2017
|
+
}
|
|
1933
2018
|
|
|
1934
2019
|
return result;
|
|
1935
2020
|
});
|