memorylake-openclaw 0.0.5-beta.1 → 0.0.5-beta.2

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.

Potentially problematic release.


This version of memorylake-openclaw might be problematic. Click here for more details.

package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorylake-openclaw",
3
- "version": "0.0.5-beta.1",
3
+ "version": "0.0.5-beta.2",
4
4
  "type": "module",
5
5
  "description": "MemoryLake memory backend for OpenClaw",
6
6
  "license": "MIT",
@@ -1,22 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "WebFetch(domain:10.71.10.71)",
5
- "Bash(find /Users/henry/work/github/memorylake-openclaw -maxdepth 2 \\\\\\( -name \".env*\" -o -name \"*.config.*\" -o -name \"openclaw.json\" \\\\\\) 2>/dev/null | grep -v node_modules)",
6
- "Bash(jq '.env' /Users/henry/.openclaw/openclaw.json)",
7
- "Bash(wc -l /Users/henry/.openclaw/agents/main/sessions/*.jsonl)",
8
- "Bash(curl -s http://10.71.10.71:8001/v3/api-docs/open-api 2>&1 | head -5000)",
9
- "Bash(cat /Users/henry/.claude/projects/-Users-henry-work-github-memorylake-openclaw/667ef3f7-2edf-4030-b7cf-239a4d916489/tool-results/bf9jm64sc.txt | python3 -c \"\nimport json, sys\ndata = json.load\\(sys.stdin\\)\n# Extract the memories endpoint\npaths = data.get\\('paths', {}\\)\nfor path, methods in paths.items\\(\\):\n if 'memories' in path:\n print\\(f'\\\\\\\\n=== {path} ==='\\)\n for method, spec in methods.items\\(\\):\n print\\(f'\\\\\\\\n--- {method.upper\\(\\)} ---'\\)\n print\\(json.dumps\\(spec, indent=2\\)\\)\n\n# Also print relevant schemas\nschemas = data.get\\('components', {}\\).get\\('schemas', {}\\)\nfor name, schema in schemas.items\\(\\):\n if 'memory' in name.lower\\(\\) or 'Memory' in name or 'Message' in name:\n print\\(f'\\\\\\\\n=== Schema: {name} ==='\\)\n print\\(json.dumps\\(schema, indent=2\\)\\)\n\")",
10
- "Bash(cat /Users/henry/.openclaw/openclaw.json | python3 -c \"\nimport json, sys\ndata = json.load\\(sys.stdin\\)\n# Look for plugin-related config\nfor key in data:\n if 'plugin' in key.lower\\(\\) or 'memory' in key.lower\\(\\) or 'memorylake' in key.lower\\(\\) or 'extension' in key.lower\\(\\):\n print\\(f'=== {key} ==='\\)\n print\\(json.dumps\\(data[key], indent=2\\)[:3000]\\)\n# Also print top-level keys\nprint\\('\\\\\\\\n=== Top-level keys ==='\\)\nprint\\(list\\(data.keys\\(\\)\\)\\)\n\")",
11
- "Bash(cat /Users/henry/.openclaw/agents/main/sessions/sessions.json | python3 -c \"\nimport json, sys\ndata = json.load\\(sys.stdin\\)\nfor key, val in data.items\\(\\):\n print\\(f'Key: {key}'\\)\n print\\(f' sessionId: {val.get\\(\\\\\"sessionId\\\\\"\\)}'\\)\n print\\(f' chatType: {val.get\\(\\\\\"chatType\\\\\"\\)}'\\)\n print\\(f' channel: {val.get\\(\\\\\"channel\\\\\"\\)}'\\)\n print\\(f' displayName: {val.get\\(\\\\\"displayName\\\\\"\\)}'\\)\n print\\(f' subject: {val.get\\(\\\\\"subject\\\\\"\\)}'\\)\n print\\(f' sessionFile: {val.get\\(\\\\\"sessionFile\\\\\"\\)}'\\)\n print\\(\\)\n\")",
12
- "Bash(head -20 /Users/henry/.openclaw/agents/main/sessions/8cc2bff3-a3af-4725-b071-43caffb36771.jsonl | python3 -c \"\nimport json, sys\nfor line in sys.stdin:\n line = line.strip\\(\\)\n if not line:\n continue\n obj = json.loads\\(line\\)\n t = obj.get\\('type'\\)\n if t == 'message':\n msg = obj.get\\('message', {}\\)\n role = msg.get\\('role'\\)\n content = msg.get\\('content', []\\)\n # Truncate content for display\n content_str = str\\(content\\)[:200]\n print\\(f'Type: {t}, Role: {role}, Content: {content_str}...'\\)\n else:\n print\\(f'Type: {t}, Keys: {list\\(obj.keys\\(\\)\\)}'\\)\n\")",
13
- "Bash(ls ~/.openclaw/agents/main/sessions/*.jsonl 2>/dev/null | head -3)",
14
- "Bash(cat ~/.openclaw/agents/main/sessions/sessions.json 2>/dev/null | python3 -c \"import json,sys; d=json.load\\(sys.stdin\\); print\\(json.dumps\\(dict\\(list\\(d.items\\(\\)\\)[:2]\\), indent=2\\)\\)\" 2>/dev/null || echo \"No sessions.json or parse error\")",
15
- "Bash(head -5 ~/.openclaw/agents/main/sessions/372112f5-ea26-4440-995f-aa756c707858.jsonl 2>/dev/null | python3 -c \"\nimport json, sys\nfor line in sys.stdin:\n line = line.strip\\(\\)\n if not line: continue\n d = json.loads\\(line\\)\n print\\(json.dumps\\({k: d[k] for k in list\\(d.keys\\(\\)\\)[:3]}, indent=2, ensure_ascii=False\\)\\)\n if d.get\\('type'\\) == 'message':\n msg = d.get\\('message', {}\\)\n role = msg.get\\('role'\\)\n content = msg.get\\('content'\\)\n if isinstance\\(content, list\\):\n types = [c.get\\('type'\\) for c in content]\n print\\(f' role={role}, content_types={types}'\\)\n else:\n print\\(f' role={role}, content type={type\\(content\\).__name__}'\\)\n\")",
16
- "Bash(python3 -c \"\nimport json\nwith open\\('/Users/henry/.openclaw/agents/main/sessions/372112f5-ea26-4440-995f-aa756c707858.jsonl'\\) as f:\n for line in f:\n line = line.strip\\(\\)\n if not line: continue\n d = json.loads\\(line\\)\n if d.get\\('type'\\) != 'message': continue\n msg = d.get\\('message', {}\\)\n role = msg.get\\('role'\\)\n content = msg.get\\('content'\\)\n if isinstance\\(content, list\\):\n types = [c.get\\('type'\\) for c in content]\n print\\(f'role={role}, content_types={types}'\\)\n elif isinstance\\(content, str\\):\n print\\(f'role={role}, content=str[{len\\(content\\)}]'\\)\n else:\n print\\(f'role={role}, content={type\\(content\\).__name__}'\\)\n\" | head -20)",
17
- "Bash(python3 << 'PYEOF'\nimport json\nwith open\\(\"/Users/henry/.openclaw/agents/main/sessions/372112f5-ea26-4440-995f-aa756c707858.jsonl\"\\) as f:\n for line in f:\n line = line.strip\\(\\)\n if not line:\n continue\n d = json.loads\\(line\\)\n if d.get\\(\"type\"\\) != \"message\":\n continue\n msg = d.get\\(\"message\", {}\\)\n role = msg.get\\(\"role\"\\)\n content = msg.get\\(\"content\"\\)\n if isinstance\\(content, list\\):\n types = [c.get\\(\"type\"\\) for c in content]\n print\\(f\"role={role}, content_types={types}\"\\)\n elif isinstance\\(content, str\\):\n print\\(f\"role={role}, content=str[{len\\(content\\)}]\"\\)\n else:\n print\\(f\"role={role}, content={type\\(content\\).__name__}\"\\)\nPYEOF)",
18
- "Bash(python3 << 'PYEOF'\nimport json\nwith open\\(\"/Users/henry/.openclaw/openclaw.json\"\\) as f:\n config = json.load\\(f\\)\n# Show agents structure\nagents = config.get\\(\"agents\", {}\\)\nprint\\(json.dumps\\({\n \"defaults\": agents.get\\(\"defaults\", {}\\),\n \"list_keys\": [list\\(a.keys\\(\\)\\) for a in agents.get\\(\"list\", []\\)][:3],\n \"list_sample\": agents.get\\(\"list\", []\\)[:2]\n}, indent=2, default=str\\)\\)\nPYEOF)",
19
- "Bash(chmod +x /Users/henry/work/github/memorylake-openclaw/skills/migrate-memories-to-memorylake/migrate.mjs)"
20
- ]
21
- }
22
- }
@@ -1,79 +0,0 @@
1
- ## PR #2 Review:Add advanced web search tool with plugin-level constraints
2
-
3
- > 之前那条简短英文评论格式不太好,这条为主,请以本评论为准。
4
-
5
- ### 总体评价
6
- - **整体设计合理**:`advanced_web_search` 作为可选工具(`optional: true`),需要在 OpenClaw 侧显式允许,和现有工具体系一致。
7
- - **配置与文档同步到位**:新增的 `webSearch*` 配置在 README、OpenClaw 文档以及 `openclaw.plugin.json` 里都有说明,label/placeholder/help 也比较清晰。
8
- - **错误处理风格统一**:工具实现中的 try/catch、返回 `content` + `details` 的方式,与现有 `document_search` 等工具保持一致。
9
-
10
- ### 需要在合并前确认/修复的点(Blocking)
11
- 1. **Web Search API 路径**
12
-
13
- ```ts
14
- this.webSearchPath = "api/v1/search"; //TODO: update to the new web search API `openapi/memorylake/api/v1/search`
15
- ```
16
-
17
- - 目前这里用的是 `api/v1/search`,但其它路径(memories/doc search)都是走 `openapi/memorylake/...`。
18
- - TODO 里也写了目标路径可能是 `openapi/memorylake/api/v1/search`。
19
- - **建议**:跟后端或 API 文档确认最终路径,二选一:
20
- - 若最终是 `openapi/memorylake/api/v1/search`,这里改成该路径并删除 TODO;
21
- - 若确实是 `api/v1/search`,则保留该值、删掉 TODO,并补一行注释说明这是 web search 的独立 endpoint。
22
-
23
- 2. **统一搜索 API 的响应结构**
24
-
25
- - 目前 `searchWeb` 是:
26
-
27
- ```ts
28
- const resp = await this.http
29
- .post(this.webSearchPath, { json: body })
30
- .json<WebSearchResponse>();
31
- return normalizeWebSearchResponse(resp);
32
- ```
33
-
34
- - 但其它接口(memories/doc search)是走统一 `ApiResponse` 包装:先检查 `success`,再从 `data` 里取结果。
35
- - 如果 unified web search API 实际返回的是 `{ success, data: { results, total_results } }` 这一套,那么这里会少了解包,`normalizeWebSearchResponse` 的入参就不对齐。
36
- - **建议**:确认该接口真实响应:
37
- - 若也是 `ApiResponse` 风格,就改成先解析 `ApiResponse`(含 `success` 判断),再对 `data` 做 `normalizeWebSearchResponse`,与其它接口保持一致;
38
- - 若这个接口是裸的 `WebSearchResponse`(没有 `success/data` 包装),当前写法就 OK,可以在注释里说明这是一个特例。
39
-
40
- ### 非阻塞的改进建议(Nice to have)
41
- 1. **空数组语义**
42
-
43
- - 现在发送请求前的判断是:
44
-
45
- ```ts
46
- if (options.include_domains?.length) body.include_domains = options.include_domains;
47
- if (options.exclude_domains?.length) body.exclude_domains = options.exclude_domains;
48
- ```
49
-
50
- - 这意味着:当配置为 `[]` 时字段不会发送,仅当长度 > 0 才会发。
51
- - 如果产品上不需要区分:“未配置” vs “显式设为空数组”,当前实现是合理的;
52
- - 如果未来希望支持“显式禁用所有域名”之类的语义,可能需要改成 `options.include_domains !== undefined` 决定是否传字段。
53
-
54
- 2. **normalizeWebSearchResponse 的稳健性**
55
-
56
- - 当前只对顶层 `results`/`total_results` 做了类型兜底:
57
-
58
- ```ts
59
- return {
60
- results: Array.isArray(raw?.results) ? raw.results : [],
61
- total_results: typeof raw?.total_results === "number" ? raw.total_results : 0,
62
- };
63
- ```
64
-
65
- - 如果后端在单条 result 的字段上(`url`/`title` 等)有可能返回 `null` 或非字符串,将来可以考虑在这里顺便做一层浅 normalize,减少下游使用时的防御性代码。不是必须,看 API 稳定性。
66
-
67
- 3. **配置解析的测试**
68
-
69
- - 新增的 `parseOptionalStringArray` / `parseOptionalString` 实现本身比较简单,但关系到配置体验。
70
- - 如果项目中已有 config 相关的测试套件,可以考虑加一小组用例(合法/非法类型,`null`、`[]` 等),防止以后改动时出现回归。
71
-
72
- ### 结论
73
-
74
- - **总体 LGTM**:设计、类型、文档和插件配置都比较完整。
75
- - 真正阻塞合并的主要是:
76
- - 确认并固定 `webSearchPath` 路径;
77
- - 确认统一搜索 API 的响应是否包在 `ApiResponse` 里,并根据结果决定是否需要像其它接口那样做一层解包。
78
- - 这两个点确认/修完之后,就可以放心合并了。
79
-
@@ -1,83 +0,0 @@
1
- ---
2
- name: gh-pr-description-gen
3
- description: Use when creating a GitHub PR with gh CLI and needing to generate title and description from git diff and optional issue context
4
- ---
5
-
6
- # gh PR Create with Generated Description
7
-
8
- ## Overview
9
-
10
- Create a GitHub pull request using `gh pr create`, with title and body generated by the agent from the git diff. No external model calls—the agent generates the content directly.
11
-
12
- ## When to Use
13
-
14
- - User wants to create a PR with `gh` and needs title/description written
15
- - User has uncommitted or committed changes and wants a PR created
16
- - User may reference related issues (e.g. "fixes #123") to include in context
17
-
18
- ## Core Workflow
19
-
20
- 1. **Gather context**
21
- - Current branch: `git branch --show-current`
22
- - Base branch: `git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's#.*/##'` or fallback to `main`/`master`
23
- - Git diff: `git diff origin/{base}...HEAD` (or `git diff origin/{base}` if no merge base)
24
- - Truncate diff to ~15000 chars if large
25
- - Optional: if user provides issue numbers, run `gh issue view {number}` for each to get title and description
26
-
27
- 2. **Generate title and body**
28
- - Use the diff (and optionally issue context) to write a clear title and description
29
- - Title: concise, descriptive, no "to #X:" prefix
30
- - Body format:
31
-
32
- ```
33
- ### Motivation
34
-
35
- (Describe the motivation of this PR)
36
-
37
- ### Modifications
38
-
39
- * (Modification 1)
40
- * (Modification 2)
41
- * ...
42
- ```
43
-
44
- - Use fluent, simple-yet-elegant English
45
- - Keep meanings clear, sentences short, lines under ~150 characters
46
- - If related to issues, add "Fixes #123" or "Closes #123" in body to auto-link
47
-
48
- 3. **Create PR**
49
- - `gh pr create --title "..." --body "..."` (or `--body-file -` with stdin)
50
- - Add `--base` if base branch differs from default
51
- - Add `--draft` if user wants a draft PR
52
-
53
- ## Quick Reference
54
-
55
- | Step | Command |
56
- |------|---------|
57
- | Current branch | `git branch --show-current` |
58
- | Default base | `git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null \| sed 's#.*/##'` or `main` |
59
- | Diff | `git diff origin/{base}...HEAD` |
60
- | Issue view | `gh issue view {number} --json title,body` |
61
- | Create PR | `gh pr create -t "Title" -b "Body"` |
62
-
63
- ## Body Template
64
-
65
- Use this structure when generating the description:
66
-
67
- ```markdown
68
- ### Motivation
69
-
70
- (Describe what problem this PR solves or why it matters)
71
-
72
- ### Modifications
73
-
74
- * (First change)
75
- * (Second change)
76
- * ...
77
- ```
78
-
79
- ## Common Mistakes
80
-
81
- - **Forgetting to push**: Ensure branch is pushed before `gh pr create`; `gh` will prompt if not
82
- - **Long body**: Use `--body-file -` with heredoc when body is large or contains special chars
83
- - **Wrong base**: Use `--base branch` when base differs from default
@@ -1,97 +0,0 @@
1
- ---
2
- name: git-create-branch
3
- description: 标准化流程:先从远端获取最新 main,再创建 feature/bugfix 分支用于功能开发或 bug 修复。当你准备开始一项新开发任务并需要新分支时使用。
4
- ---
5
-
6
- # Git 新建分支流程(基于 main)
7
-
8
- ## 前置约定
9
-
10
- - **默认远端名**:`origin`(如果项目不是用 `origin`,请将下面命令中的 `origin` 换成实际远端名)
11
- - **主干分支名**:`main`(如果项目主干是 `master` 或其他名字,同理替换)
12
- - **前提条件**:在运行本流程前,工作区应当是干净的(没有未提交更改,或明确知道自己在做什么)
13
-
14
- ## 一、检查当前工作区状态
15
-
16
- 1. 查看当前分支与工作区:
17
-
18
- ```bash
19
- git status
20
- ```
21
-
22
- 2. 如果有未提交的更改:
23
- - 需要的话先提交:`git commit -am "your message"`
24
- - 或者暂存:`git stash push -m "temp before new branch"`
25
- - 或者放弃本地修改:`git restore .`(谨慎使用,会丢弃修改)
26
-
27
- 目标:在继续之前,`git status` 应尽量是干净状态。
28
-
29
- ## 二、从远端获取最新 main
30
-
31
- 1. 抓取远端最新记录(包括 main):
32
-
33
- ```bash
34
- git fetch origin main
35
- ```
36
-
37
- 2. 切换到本地 `main` 分支(如果当前还不在):
38
-
39
- ```bash
40
- git checkout main
41
- ```
42
-
43
- 3. 将本地 `main` 更新到与远端一致,避免产生多余 merge 提交:
44
-
45
- ```bash
46
- git pull --ff-only origin main
47
- ```
48
-
49
- 如果 `--ff-only` 报错,说明本地 `main` 有额外提交,需要先确认是否应该保留;通常建议保持本地 `main` 与远端完全一致,没有额外提交。
50
-
51
- ## 三、从最新 main 创建开发分支
52
-
53
- 1. 根据需求选择分支前缀:
54
- - 功能开发:`feature/xxx-简短描述`
55
- - Bug 修复:`bugfix/xxx-简短描述`
56
-
57
- 示例:
58
- - `feature/1234-add-knowledge-search`
59
- - `bugfix/5678-fix-memory-sync`
60
-
61
- 2. 从最新 `main` 创建并切换到新分支(将 `<new-branch-name>` 替换为你的实际分支名):
62
-
63
- ```bash
64
- git checkout -b <new-branch-name> main
65
- ```
66
-
67
- 3. 确认当前分支确实是新建分支:
68
-
69
- ```bash
70
- git branch --show-current
71
- git status
72
- ```
73
-
74
- ## 四、(可选)立即将新分支推送到远端
75
-
76
- 如果你希望尽早在远端创建对应分支(方便备份或协作):
77
-
78
- ```bash
79
- git push -u origin <new-branch-name>
80
- ```
81
-
82
- - `-u` 会设置默认上游分支,之后只需要 `git push` / `git pull` 即可。
83
-
84
- ## 五、后续使用建议
85
-
86
- - **开始开发**:在新分支上进行所有与本任务相关的修改与提交。
87
- - **保持同步 main**:在开发周期较长时,周期性地从 `main` 合并或 rebase,避免与主干差异过大,例如:
88
-
89
- ```bash
90
- git checkout main
91
- git pull --ff-only origin main
92
- git checkout <new-branch-name>
93
- git rebase main # 或 git merge main,根据团队习惯
94
- ```
95
-
96
- - **完成开发后**:通过 PR / Merge Request 将该分支合入主干,然后根据团队流程删除本地与远端分支。
97
-
@@ -1,96 +0,0 @@
1
- ---
2
- title: OpenClaw(中文)
3
- ---
4
-
5
- 通过 `memorylake-openclaw` 插件为 [OpenClaw](https://github.com/openclaw/openclaw) 代理添加长期记忆。你的代理在不同会话之间会忘记一切——这个插件会自动“观察”对话、提取重要信息,并在相关时把它们带回上下文,从而解决遗忘问题。
6
-
7
- ## 概览
8
-
9
- {/*<Frame>
10
- <img src="/images/openclaw-architecture.png" alt="OpenClaw MemoryLake Architecture" />
11
- </Frame>*/}
12
-
13
- 该插件提供:
14
- 1. **自动召回(Auto-Recall)** — 在代理回复前,将与当前消息匹配的记忆注入到上下文中
15
- 2. **自动捕获(Auto-Capture)** — 在代理回复后,将本轮对话发送到 MemoryLake,由其判断哪些内容值得长期保存
16
- 3. **代理工具(Agent Tools)** — 提供 5 个工具,便于在对话中显式进行记忆操作
17
-
18
- 自动召回与自动捕获默认静默运行,无需手动配置即可生效。
19
-
20
- ## 安装
21
-
22
- ```bash
23
- openclaw plugins install memorylake-openclaw
24
- ```
25
-
26
- ## 设置与配置
27
-
28
- <Note>请从 [app.memorylake.ai](https://app.memorylake.ai) 获取 API key 和 project ID。</Note>
29
-
30
- 在你的 `openclaw.json` 中添加:
31
-
32
- ```json5
33
- // plugins.entries
34
- "memorylake-openclaw": {
35
- "enabled": true,
36
- "config": {
37
- "apiKey": "${MEMORYLAKE_API_KEY}",
38
- "projectId": "proj-..."
39
- }
40
- }
41
- ```
42
-
43
- ## 代理工具
44
-
45
- 代理在对话中可调用以下 5 个工具:
46
-
47
- | 工具 | 说明 |
48
- |------|------|
49
- | `memory_search` | 用自然语言搜索记忆 |
50
- | `memory_list` | 列出某个用户已存储的全部记忆 |
51
- | `memory_store` | 显式保存一条事实 |
52
- | `memory_get` | 通过 ID 读取一条记忆 |
53
- | `memory_forget` | 通过 ID 删除一条记忆 |
54
-
55
- ## CLI 命令
56
-
57
- ```bash
58
- # 搜索记忆
59
- openclaw memorylake search "what languages does the user know"
60
-
61
- # 查看统计信息
62
- openclaw memorylake stats
63
- ```
64
-
65
- ## 配置项
66
-
67
- | Key | 类型 | 默认值 | 说明 |
68
- |-----|------|--------|------|
69
- | `apiKey` | `string` | — | **必填。** MemoryLake API key(支持 `${MEMORYLAKE_API_KEY}`) |
70
- | `projectId` | `string` | — | **必填。** MemoryLake project ID |
71
- | `host` | `string` | `https://app.memorylake.ai` | MemoryLake 服务端点 URL |
72
- | `autoRecall` | `boolean` | `true` | 每轮对话前注入记忆 |
73
- | `autoCapture` | `boolean` | `true` | 每轮对话后存储事实 |
74
- | `topK` | `number` | `5` | 每次召回最多注入的记忆条数 |
75
- | `searchThreshold` | `number` | `0.3` | 最小相似度阈值(0–1) |
76
- | `rerank` | `boolean` | `true` | 对搜索结果重排以提升相关性 |
77
-
78
- ## 关键特性
79
-
80
- 1. **零配置** — 自动召回与自动捕获开箱即用,无需额外提示词或手动开关
81
- 2. **异步处理** — 记忆提取通过 MemoryLake API 异步执行
82
- 3. **会话追踪** — 对话会附带 `chat_session_id`,便于追溯与排查
83
- 4. **工具完备** — 需要时可使用 5 个代理工具显式管理记忆
84
-
85
- ## 总结
86
-
87
- `memorylake-openclaw` 插件让 OpenClaw 代理以极低成本获得持久记忆能力。你的代理可以跨会话自动记住用户偏好、事实与上下文,从而更稳定地连续协作。
88
-
89
- {/*<CardGroup cols={2}>
90
- <Card title="MemoryLake" icon="brain" href="https://app.memorylake.ai">
91
- MemoryLake platform
92
- </Card>
93
- <Card title="OpenClaw" icon="robot" href="https://github.com/openclaw/openclaw">
94
- OpenClaw agent framework
95
- </Card>
96
- </CardGroup>*/}
@@ -1,371 +0,0 @@
1
- # Knowledge Search 接口调用与 LLM Context 拼接指南
2
-
3
- ## 1. 整体思路
4
-
5
- ```
6
- 用户问题 (query)
7
-
8
-
9
- Knowledge Search API ── POST /api/v1/knowledge/dataset/{dataset_id}/search
10
-
11
-
12
- 返回三类结果: table / paragraph / figure
13
-
14
-
15
- 格式化为纯文本
16
-
17
-
18
- 拼接到 LLM 的 prompt 中作为 context
19
- ```
20
-
21
- 核心逻辑就三步:**调接口 → 按类型解析结果 → 格式化为文本塞进 prompt**。
22
-
23
- ---
24
-
25
- ## 2. Knowledge Search API
26
-
27
- ### 请求
28
-
29
- ```
30
- POST {endpoint}/api/v1/knowledge/dataset/{dataset_id}/search
31
- Content-Type: application/json
32
- ```
33
-
34
- ```json
35
- {
36
- "user_query": "你的搜索查询",
37
- "top_n": 5,
38
- "datasource_list": null,
39
- "__knobs": {"use_mini_aa": false}
40
- }
41
- ```
42
-
43
- | 参数 | 类型 | 说明 |
44
- |------|------|------|
45
- | `user_query` | string | 搜索查询文本 |
46
- | `top_n` | int | 返回的最大结果数 |
47
- | `datasource_list` | list[string] \| null | 限定搜索范围的 datasource ID 列表,null 表示搜索全部 |
48
-
49
- ### 响应
50
-
51
- ```json
52
- {
53
- "n": 3,
54
- "results": [
55
- { "type": "table", ... },
56
- { "type": "paragraph", ... },
57
- { "type": "figure", ... }
58
- ]
59
- }
60
- ```
61
-
62
- `results` 数组中每个元素通过 `type` 字段区分类型,共三种:
63
-
64
- ---
65
-
66
- ### 2.1 type = "table"(表格结果)
67
-
68
- ```json
69
- {
70
- "type": "table",
71
- "datasource_id": "ds_xxx",
72
- "datasource_name": "销售报表.xlsx",
73
- "sheet_name": "Sheet1",
74
- "table_id": "tbl_xxx",
75
- "table_region_info": "A1:F20",
76
- "title": "2024年Q3销售汇总",
77
- "footnote": "数据来源:财务部",
78
- "highlight": {
79
- "chunks": [],
80
- "inner_tables": [
81
- {
82
- "id": "it_xxx",
83
- "persist_path": "s3://bucket/path/to/table.parquet",
84
- "columns": [
85
- {
86
- "id": "col_1",
87
- "name": "产品名称",
88
- "data_type": "string",
89
- "null_count": 0,
90
- "count": 100,
91
- "examples": ["产品A", "产品B"],
92
- "min_value": null,
93
- "max_value": null
94
- },
95
- {
96
- "id": "col_2",
97
- "name": "销售额",
98
- "data_type": "decimal",
99
- "null_count": 0,
100
- "count": 100,
101
- "examples": [12345.67],
102
- "min_value": "100.00",
103
- "max_value": "99999.99"
104
- }
105
- ],
106
- "data_range": "A1:F20",
107
- "num_rows": 100
108
- }
109
- ],
110
- "figure": null
111
- }
112
- }
113
- ```
114
-
115
- **关键字段提取**:
116
- - `title` / `footnote`:表格的标题和备注
117
- - `highlight.inner_tables[].columns`:列名、数据类型、示例值、min/max
118
- - `highlight.inner_tables[].persist_path`:表格数据文件的存储路径(如果需要加载实际数据)
119
- - `highlight.inner_tables[].num_rows`:行数
120
-
121
- ---
122
-
123
- ### 2.2 type = "paragraph"(文本段落结果)
124
-
125
- ```json
126
- {
127
- "type": "paragraph",
128
- "datasource_id": "ds_yyy",
129
- "datasource_name": "公司简介.pdf",
130
- "sheet_name": null,
131
- "highlight": {
132
- "chunks": [
133
- {
134
- "text": "公司成立于2010年,主要从事...",
135
- "range": null
136
- }
137
- ],
138
- "inner_tables": [],
139
- "figure": null
140
- }
141
- }
142
- ```
143
-
144
- **关键字段提取**:
145
- - `highlight.chunks[].text`:文本内容(这是最有价值的信息)
146
- - `datasource_name`:来源文件名
147
-
148
- ---
149
-
150
- ### 2.3 type = "figure"(图表结果)
151
-
152
- ```json
153
- {
154
- "type": "figure",
155
- "figure_id": 1,
156
- "datasource_id": "ds_zzz",
157
- "datasource_name": "年报.pdf",
158
- "sheet_name": null,
159
- "highlight": {
160
- "chunks": [],
161
- "inner_tables": [],
162
- "figure": {
163
- "id": "fig_xxx",
164
- "text": "图表中提取的文本内容...",
165
- "caption": "图1: 2024年营收趋势",
166
- "persist_path": "s3://bucket/path/to/figure.png",
167
- "summary_text": "该图展示了2024年各季度营收数据..."
168
- }
169
- }
170
- }
171
- ```
172
-
173
- **关键字段提取**:
174
- - `highlight.figure.caption`:图表标题
175
- - `highlight.figure.text`:从图表中提取的文本
176
- - `highlight.figure.summary_text`:图表内容摘要
177
- - `highlight.figure.persist_path`:图片文件路径
178
-
179
- ---
180
-
181
- ## 3. 解析与格式化逻辑(伪代码)
182
-
183
- 下面是与框架无关的伪代码,描述如何把 API 响应转化为 LLM context:
184
-
185
- ```
186
- function build_context(api_response):
187
- context_parts = []
188
-
189
- for result in api_response.results:
190
-
191
- if result.type == "table":
192
- # 拼表格元信息
193
- append "### Table: {result.title} (from {result.datasource_name})"
194
- if result.footnote:
195
- append "Note: {result.footnote}"
196
- for inner_table in result.highlight.inner_tables:
197
- # 拼列信息:列名(类型) 的列表
198
- col_desc = join(
199
- "{col.name}({col.data_type})" for col in inner_table.columns
200
- )
201
- append "Columns: {col_desc}"
202
- append "Rows: {inner_table.num_rows}"
203
-
204
- # 【可选】如果需要样本数据,从 persist_path 加载前 N 行
205
- # df = load_dataframe(inner_table.persist_path)
206
- # append df.head(10).to_markdown()
207
-
208
- elif result.type == "paragraph":
209
- append "### Paragraph (from {result.datasource_name}):"
210
- for chunk in result.highlight.chunks:
211
- if chunk.text:
212
- # 限制长度,避免单个 chunk 占太多 token
213
- text = chunk.text[:10000]
214
- append text
215
-
216
- elif result.type == "figure":
217
- figure = result.highlight.figure
218
- if figure:
219
- append "### Figure (from {result.datasource_name}):"
220
- if figure.caption:
221
- append "Caption: {figure.caption}"
222
- if figure.text:
223
- append figure.text
224
- elif figure.summary_text:
225
- append figure.summary_text
226
-
227
- return join(context_parts, "\n\n")
228
- ```
229
-
230
- ---
231
-
232
- ## 4. 拼接到 LLM Prompt 的模式
233
-
234
- ```
235
- system_prompt = """
236
- You are a helpful assistant. Answer the user's question based on the following retrieved context.
237
-
238
- <context>
239
- {上面 build_context 的输出}
240
- </context>
241
-
242
- If the context doesn't contain enough information to answer, say so.
243
- """
244
-
245
- messages = [
246
- {"role": "system", "content": system_prompt},
247
- {"role": "user", "content": 用户原始问题},
248
- ]
249
-
250
- # 发送给 LLM
251
- response = llm.chat(messages)
252
- ```
253
-
254
- ---
255
-
256
- ## 5. 实际调用示例 (Python + httpx)
257
-
258
- 以下是一个完全独立、可以在任意 Python 项目中使用的实现:
259
-
260
- ```python
261
- import httpx
262
-
263
-
264
- async def knowledge_search(
265
- endpoint: str,
266
- dataset_id: str,
267
- query: str,
268
- top_n: int = 5,
269
- datasource_list: list[str] | None = None,
270
- timeout: int = 60,
271
- ) -> dict:
272
- """调用 knowledge search API,返回原始 JSON 响应。"""
273
- async with httpx.AsyncClient() as client:
274
- resp = await client.post(
275
- f"{endpoint}/api/v1/knowledge/dataset/{dataset_id}/search",
276
- json={
277
- "user_query": query,
278
- "top_n": top_n,
279
- "datasource_list": datasource_list,
280
- "__knobs": {"use_mini_aa": False},
281
- },
282
- timeout=timeout,
283
- )
284
- resp.raise_for_status()
285
- return resp.json()
286
-
287
-
288
- def build_llm_context(search_response: dict, max_chunk_length: int = 10000) -> str:
289
- """将 knowledge search 响应解析并格式化为 LLM context 文本。"""
290
- parts: list[str] = []
291
-
292
- for result in search_response.get("results", []):
293
- result_type = result.get("type")
294
- source = result.get("datasource_name", "unknown")
295
- highlight = result.get("highlight", {})
296
-
297
- if result_type == "table":
298
- title = result.get("title") or "Untitled Table"
299
- parts.append(f"### Table: {title} (from {source})")
300
-
301
- footnote = result.get("footnote")
302
- if footnote:
303
- parts.append(f"Note: {footnote}")
304
-
305
- for inner_table in highlight.get("inner_tables", []):
306
- columns = inner_table.get("columns", [])
307
- col_desc = ", ".join(
308
- f"{c['name']}({c['data_type']})" for c in columns
309
- )
310
- parts.append(f"Columns: {col_desc}")
311
- parts.append(f"Rows: {inner_table.get('num_rows', '?')}")
312
-
313
- for chunk in highlight.get("chunks", []):
314
- text = chunk.get("text", "")
315
- if text:
316
- parts.append(text[:max_chunk_length])
317
-
318
- elif result_type == "paragraph":
319
- parts.append(f"### Paragraph (from {source}):")
320
- for chunk in highlight.get("chunks", []):
321
- text = chunk.get("text", "")
322
- if text:
323
- parts.append(text[:max_chunk_length])
324
-
325
- elif result_type == "figure":
326
- figure = highlight.get("figure")
327
- if figure:
328
- parts.append(f"### Figure (from {source}):")
329
- caption = figure.get("caption")
330
- if caption:
331
- parts.append(f"Caption: {caption}")
332
- text = figure.get("text") or figure.get("summary_text") or ""
333
- if text:
334
- parts.append(text)
335
-
336
- parts.append("") # 空行分隔
337
-
338
- return "\n".join(parts)
339
-
340
-
341
- async def query_with_knowledge(
342
- endpoint: str,
343
- dataset_id: str,
344
- query: str,
345
- top_n: int = 5,
346
- ) -> str:
347
- """完整流程:搜索 → 构建 context → 返回可用于 LLM 的 prompt。"""
348
- response = await knowledge_search(endpoint, dataset_id, query, top_n)
349
- context = build_llm_context(response)
350
-
351
- system_prompt = (
352
- "You are a helpful assistant. "
353
- "Answer the user's question based on the following retrieved context.\n\n"
354
- f"<context>\n{context}\n</context>\n\n"
355
- "If the context doesn't contain enough information to answer, say so."
356
- )
357
- return system_prompt
358
- ```
359
-
360
- ---
361
-
362
- ## 6. 设计要点总结
363
-
364
- | 要点 | 说明 |
365
- |------|------|
366
- | **一次请求返回三种类型** | search API 的 results 混合了 table / paragraph / figure,需要按 `type` 字段分别处理 |
367
- | **表格数据需二次加载** | search API 只返回列元信息,实际数据存在 `inner_tables[].persist_path` 指向的文件中(通常是 S3 上的 parquet),如果只需要元信息就不用加载 |
368
- | **文本内容在 chunks 里** | paragraph 类型的实际文本在 `highlight.chunks[].text` 中 |
369
- | **图表有三个文本字段** | `figure.caption`(标题)、`figure.text`(提取文本)、`figure.summary_text`(摘要),取其中有值的即可 |
370
- | **context 长度控制** | 需要根据 LLM 的 token 限制控制总 context 长度,建议对单个 chunk 做截断 + 控制 top_n |
371
- | **API 幂等且可缓存** | 相同 dataset_id + query + top_n 的请求返回相同结果,适合做客户端缓存 |
@@ -1,182 +0,0 @@
1
- ---
2
- name: migrate-memories-to-memorylake
3
- description: Migrates memories, conversations to MemoryLake. Use when the user wants to import existing memories, conversations into MemoryLake.
4
- ---
5
-
6
- # Migrate Memories to MemoryLake
7
-
8
- ## Overview
9
-
10
- Extract memory files and conversation history from session files, then submit them to MemoryLake's API so memories are persisted in the platform.
11
-
12
- ## When to Use
13
-
14
- - User wants to migrate memories or conversations into MemoryLake
15
- - User is setting up MemoryLake and needs to import existing memories or conversations
16
-
17
- ## Prerequisites
18
-
19
- The caller must provide:
20
- - **`user_id`**: The user ID to tag memories with (e.g., a Feishu user ID like `ou_xxx`)
21
- - **`agent`**: The agent name (e.g., `main`)
22
-
23
- ## Step 1 — Read MemoryLake Config
24
-
25
- Read `~/.openclaw/openclaw.json` and extract the plugin config:
26
-
27
- ```bash
28
- cat ~/.openclaw/openclaw.json | jq '.plugins.entries["memorylake-openclaw"].config'
29
- ```
30
-
31
- Extract these values:
32
- - **`host`** — API host (default: `https://app.memorylake.ai`)
33
- - **`apiKey`** — API key for authentication
34
- - **`projectId`** — MemoryLake project ID
35
-
36
- ## Step 2 — Identify User and Agent
37
-
38
- Use the `user_id` and `agent` provided by the caller. These are used to:
39
- - Filter sessions in Step 3 (session keys contain the user ID)
40
- - Tag all submitted memories with `user_id`
41
-
42
- ## Step 3 — Filter Sessions by User ID
43
-
44
- Read the session index:
45
-
46
- ```bash
47
- cat ~/.openclaw/agents/{agent}/sessions/sessions.json
48
- ```
49
-
50
- This file maps session keys to session metadata. Session keys follow the format:
51
-
52
- ```
53
- agent:{agent}:{channel}:{type}:{user_id}
54
- ```
55
-
56
- Filter entries whose key contains the `user_id`. Collect the matching session IDs and their corresponding `.jsonl` file paths at:
57
-
58
- ```
59
- ~/.openclaw/agents/{agent}/sessions/{sessionId}.jsonl
60
- ```
61
-
62
- ## Step 4 — Read Memory Files
63
-
64
- Read the workspace path from config:
65
-
66
- ```bash
67
- cat ~/.openclaw/openclaw.json | jq -r '.agents.defaults.workspace'
68
- ```
69
-
70
- Then read:
71
- - `{workspace}/MEMORY.md`
72
- - All files in `{workspace}/memory/` directory
73
-
74
- These contain curated memory that should also be migrated.
75
-
76
- ## Step 5 — Submit Data to MemoryLake
77
-
78
- ### 5a — Submit Session Conversations
79
-
80
- For each matched `.jsonl` session file:
81
-
82
- 1. **Parse the JSONL file** line by line
83
- 2. **Extract message entries**: lines where `type` is `"message"`
84
- 3. **Extract text content** from message content blocks:
85
-
86
- ```json
87
- {"type":"message","message":{"role":"user","content":[{"type":"text","text":"actual message text"}]}}
88
- ```
89
-
90
- - Filter to `role: "user"` and `role: "assistant"` only
91
- - For each content block array, concatenate all `text` blocks into a single string
92
- - Skip entries with empty text content
93
-
94
- 4. **Build the messages array**: `[{role, content}, {role, content}, ...]`
95
-
96
- 5. **POST to the API**:
97
-
98
- ```bash
99
- curl -X POST "{host}/openapi/memorylake/api/v2/projects/{projectId}/memories" \
100
- -H "Authorization: Bearer {apiKey}" \
101
- -H "Content-Type: application/json" \
102
- -d '{
103
- "messages": [
104
- {"role": "user", "content": "..."},
105
- {"role": "assistant", "content": "..."}
106
- ],
107
- "user_id": "{user_id}",
108
- "chat_session_id": "{sessionId}",
109
- "metadata": {"source": "OPENCLAW_MIGRATION"},
110
- "infer": true
111
- }'
112
- ```
113
-
114
- **Important**: If a session has many messages, batch them in chunks of ~20 messages per request to avoid timeouts. Preserve message order within each batch.
115
-
116
- 6. **Log the result**: Each successful response returns:
117
-
118
- ```json
119
- {
120
- "success": true,
121
- "data": {
122
- "results": [
123
- {"event_id": "...", "status": "...", "message": "..."}
124
- ]
125
- }
126
- }
127
- ```
128
- ### 5b — Submit Memory Files
129
-
130
- For each memory file (`MEMORY.md` and files in `memory/`):
131
-
132
- 1. **Read the file content**
133
- 2. **Wrap as a single user message**:
134
-
135
- ```json
136
- [{"role": "user", "content": "<file content here>"}]
137
- ```
138
-
139
- 3. **POST to the API**:
140
-
141
- ```bash
142
- curl -X POST "{host}/openapi/memorylake/api/v2/projects/{projectId}/memories" \
143
- -H "Authorization: Bearer {apiKey}" \
144
- -H "Content-Type: application/json" \
145
- -d '{
146
- "messages": [{"role": "user", "content": "..."}],
147
- "user_id": "{user_id}",
148
- "metadata": {"source": "OPENCLAW_MIGRATION", "file": "{filename}"},
149
- "infer": true
150
- }'
151
- ```
152
-
153
- ## Progress Tracking
154
-
155
- Report progress after each submission:
156
- - `[session X/N] Submitted {count} messages from session {sessionId} — {status}`
157
- - `[file X/N] Submitted {filename} — {status}`
158
-
159
- At the end, print a summary:
160
- - Total sessions processed
161
- - Total memory files processed
162
- - Total API calls made
163
- - Any errors encountered
164
-
165
- ## Error Handling
166
-
167
- - If a session file is missing or unreadable, log a warning and continue with the next one
168
- - If an API call fails, log the error with the session/file context and continue
169
- - If `apiKey` or `projectId` is missing from config, stop immediately and inform the user
170
-
171
- ## Quick Reference
172
-
173
- | Item | Path / Value |
174
- |------|-------------|
175
- | Config file | `~/.openclaw/openclaw.json` |
176
- | Plugin config key | `plugins.entries["memorylake-openclaw"].config` |
177
- | Session index | `~/.openclaw/agents/{agent}/sessions/sessions.json` |
178
- | Session files | `~/.openclaw/agents/{agent}/sessions/{id}.jsonl` |
179
- | Workspace path | `agents.defaults.workspace` in config |
180
- | API endpoint | `{host}/openapi/memorylake/api/v2/projects/{projectId}/memories` |
181
- | Auth header | `Authorization: Bearer {apiKey}` |
182
- | Default host | `https://app.memorylake.ai` |
@@ -1,210 +0,0 @@
1
- ---
2
- name: migrate-memories-to-memorylake
3
- description: Use when the user asks to migrate memories, conversations, or history into MemoryLake. This is THE skill for any migration of OpenClaw data to MemoryLake.
4
- ---
5
-
6
- # Migrate OpenClaw Memories to MemoryLake
7
-
8
- ## Overview
9
-
10
- Extract conversation history from OpenClaw session files and curated workspace memory files, then submit them to MemoryLake's API so memories are persisted in the platform.
11
-
12
- ## When to Use
13
-
14
- - User wants to migrate existing OpenClaw data into MemoryLake
15
- - User wants to backfill MemoryLake with historical conversations
16
- - User is setting up MemoryLake for the first time and has existing OpenClaw sessions
17
-
18
- - User wants to migrate OpenClaw conversation history or workspace memories into MemoryLake
19
- - User wants to backfill MemoryLake with historical OpenClaw sessions
20
- - User is setting up MemoryLake and needs to import existing OpenClaw data
21
-
22
- ## Prerequisites
23
-
24
- The caller must provide:
25
- - **`user_id`**: The user ID to tag memories with (e.g., a Feishu user ID like `ou_xxx`)
26
- - **`agent`**: The OpenClaw agent name (e.g., `main`)
27
-
28
- ## Step 1 — Read MemoryLake Config
29
-
30
- Read `~/.openclaw/openclaw.json` and extract the plugin config:
31
-
32
- ```bash
33
- cat ~/.openclaw/openclaw.json | jq '.plugins.entries["memorylake-openclaw"].config'
34
- ```
35
-
36
- Extract these values:
37
- - **`host`** — API host (default: `https://app.memorylake.ai`)
38
- - **`apiKey`** — API key for authentication
39
- - **`projectId`** — MemoryLake project ID
40
-
41
- The API base path used by the plugin is:
42
-
43
- ```
44
- {host}/openapi/memorylake/api/v2/projects/{projectId}/memories
45
- ```
46
-
47
- Auth header for all requests:
48
-
49
- ```
50
- Authorization: Bearer {apiKey}
51
- ```
52
-
53
- ## Step 2 — Identify User and Agent
54
-
55
- Use the `user_id` and `agent` provided by the caller. These are used to:
56
- - Filter sessions in Step 3 (session keys contain the user ID)
57
- - Tag all submitted memories with `user_id`
58
-
59
- ## Step 3 — Filter Sessions by User ID
60
-
61
- Read the session index:
62
-
63
- ```bash
64
- cat ~/.openclaw/agents/{agent}/sessions/sessions.json
65
- ```
66
-
67
- This file maps session keys to session metadata. Session keys follow the format:
68
-
69
- ```
70
- agent:{agent}:{channel}:{type}:{userId}
71
- ```
72
-
73
- Filter entries whose key contains the `user_id`. Collect the matching session IDs and their corresponding `.jsonl` file paths at:
74
-
75
- ```
76
- ~/.openclaw/agents/{agent}/sessions/{sessionId}.jsonl
77
- ```
78
-
79
- ## Step 4 — Read Workspace Memory Files
80
-
81
- Read the workspace path from config:
82
-
83
- ```bash
84
- cat ~/.openclaw/openclaw.json | jq -r '.agents.defaults.workspace'
85
- ```
86
-
87
- Then read:
88
- - `{workspace}/MEMORY.md`
89
- - All files in `{workspace}/memory/` directory
90
-
91
- These contain curated memory that should also be migrated.
92
-
93
- ## Step 5 — Verify API Format (Optional)
94
-
95
- Fetch the OpenAPI spec to confirm the request schema:
96
-
97
- ```bash
98
- curl -s "{host}/v3/api-docs/open-api" | jq '.paths["/api/v2/projects/{id}/memories"].post'
99
- ```
100
-
101
- This is optional — the format is documented below in Step 6.
102
-
103
- ## Step 6 — Submit Data to MemoryLake
104
-
105
- ### 6a — Submit Session Conversations
106
-
107
- For each matched `.jsonl` session file:
108
-
109
- 1. **Parse the JSONL file** line by line
110
- 2. **Extract message entries**: lines where `type` is `"message"`
111
- 3. **Extract text content** from message content blocks:
112
-
113
- ```json
114
- {"type":"message","message":{"role":"user","content":[{"type":"text","text":"actual message text"}]}}
115
- ```
116
-
117
- - Filter to `role: "user"` and `role: "assistant"` only
118
- - For each content block array, concatenate all `text` blocks into a single string
119
- - Skip entries with empty text content
120
-
121
- 4. **Build the messages array**: `[{role, content}, {role, content}, ...]`
122
-
123
- 5. **POST to the API**:
124
-
125
- ```bash
126
- curl -X POST "{host}/openapi/memorylake/api/v2/projects/{projectId}/memories" \
127
- -H "Authorization: Bearer {apiKey}" \
128
- -H "Content-Type: application/json" \
129
- -d '{
130
- "messages": [
131
- {"role": "user", "content": "..."},
132
- {"role": "assistant", "content": "..."}
133
- ],
134
- "user_id": "{user_id}",
135
- "chat_session_id": "{sessionId}",
136
- "metadata": {"source": "OPENCLAW_MIGRATION"},
137
- "infer": true
138
- }'
139
- ```
140
-
141
- **Important**: If a session has many messages, batch them in chunks of ~20 messages per request to avoid timeouts. Preserve message order within each batch.
142
-
143
- 6. **Log the result**: Each successful response returns:
144
-
145
- ```json
146
- {
147
- "success": true,
148
- "data": {
149
- "results": [
150
- {"event_id": "...", "status": "...", "message": "..."}
151
- ]
152
- }
153
- }
154
- ```
155
-
156
- ### 6b — Submit Workspace Memory Files
157
-
158
- For each workspace memory file (`MEMORY.md` and files in `memory/`):
159
-
160
- 1. **Read the file content**
161
- 2. **Wrap as a single user message**:
162
-
163
- ```json
164
- [{"role": "user", "content": "<file content here>"}]
165
- ```
166
-
167
- 3. **POST to the API**:
168
-
169
- ```bash
170
- curl -X POST "{host}/openapi/memorylake/api/v2/projects/{projectId}/memories" \
171
- -H "Authorization: Bearer {apiKey}" \
172
- -H "Content-Type: application/json" \
173
- -d '{
174
- "messages": [{"role": "user", "content": "..."}],
175
- "user_id": "{user_id}",
176
- "metadata": {"source": "OPENCLAW_MIGRATION", "file": "{filename}"},
177
- "infer": true
178
- }'
179
- ```
180
-
181
- ## Progress Tracking
182
-
183
- Report progress after each submission:
184
- - `[session X/N] Submitted {count} messages from session {sessionId} — {status}`
185
- - `[file X/N] Submitted {filename} — {status}`
186
-
187
- At the end, print a summary:
188
- - Total sessions processed
189
- - Total memory files processed
190
- - Total API calls made
191
- - Any errors encountered
192
-
193
- ## Error Handling
194
-
195
- - If a session file is missing or unreadable, log a warning and continue with the next one
196
- - If an API call fails, log the error with the session/file context and continue
197
- - If `apiKey` or `projectId` is missing from config, stop immediately and inform the user
198
-
199
- ## Quick Reference
200
-
201
- | Item | Path / Value |
202
- |------|-------------|
203
- | OpenClaw config | `~/.openclaw/openclaw.json` |
204
- | Plugin config key | `plugins.entries["memorylake-openclaw"].config` |
205
- | Session index | `~/.openclaw/agents/{agent}/sessions/sessions.json` |
206
- | Session files | `~/.openclaw/agents/{agent}/sessions/{id}.jsonl` |
207
- | Workspace path | `agents.defaults.workspace` in config |
208
- | API endpoint | `{host}/openapi/memorylake/api/v2/projects/{projectId}/memories` |
209
- | Auth header | `Authorization: Bearer {apiKey}` |
210
- | Default host | `https://app.memorylake.ai` |