Hachimi Reinstall
背景
我在服务器上跑着一个叫 Hachimi 的 AI 助手——基于 OpenClaw 平台, 接入 Telegram,平时帮我处理各种日常任务、控制 Windows Chrome 浏览器。 某天下午,她突然开始出 bug:每次调用工具,系统就把原始的 tool prompt 原封不动地打印出来。
症状分两种。第一种是工具调用的原始 JSON 直接打印出来:
{"type":"tool_use","id":"toolu_01...","name":"exec","input":{"command":"..."}}
{"type":"tool_result","tool_use_id":"toolu_01...","content":"..."}
...
第二种更诡异——回复里开始出现大量 </s> token:
你说得对,我刚才出了问题——回复里出现了大量 </s> token,这是模型异常的表现。
让我现在正常调用一个工具试试:
</s>
</s>
</s>
</s> 是序列结束标记(end-of-sequence token),正常情况下不应该出现在输出文本里。
它的出现说明上下文里混入了不属于当前对话的内容,模型在试图"结束"某个它以为还没结束的序列。
聊天里开始出现大段 JSON 和乱码,完全没法正常对话。
上下文被污染了,而且越滚越大,后面的消息几乎没有可用的 token window。
我试了几次 /new 重置会话,无效;试了重启 gateway,还是一样。
最后决定:重装。
系统架构(重装前)
Telegram
↕
Hachimi(Claude Sonnet)
↕ OpenClaw Gateway(AWS EC2)
↕ WSL2 Node Host(Windows 本地)
↕ Windows Chrome CDP
↕ reverse SSH tunnel
服务器本地端口
整套系统分散在三个地方:服务器上的 gateway、WSL2 里的 node host、 以及 Windows Chrome 的 CDP 接口。 重装意味着不只是重跑一个命令——所有这些连接都要重新拉起来。
02 症状与根因
具体表现是:每次 AI 调用工具(exec、web_search 等), Telegram 里就会收到原始的 tool call/result JSON 字符串, 而不是 AI 处理过的回复。
这类问题通常是上下文组装出了问题——tool result 被错误地插入消息序列,
导致后续轮次的 prompt 里带着上一轮的 raw 结构。
本质上是 session history 里出现了 tool_use 和 tool_result 的错位,
provider 端拒绝或报错,gateway 把错误原样返回。
重置会话无法解决,因为底层 session history 的存储格式已经损坏。 最干净的办法是从头来过。
有意思的是,OpenClaw 的 gateway 代码里本来就有一层防御:
在把消息发到 Telegram 之前,会主动过滤掉 <function_calls>
和 <function_response> 标签:
async sendMessage(chatId, text, options = {}) {
text = text
.replace(/<function_calls>[\s\S]*?<\/function_calls>/g, "")
.replace(/<function_response>[\s\S]*?<\/function_response>/g, "")
.trim();
// ...
}
说明这类污染不是第一次有人遇到——gateway 已经预设了清洗逻辑。
但当上下文损坏到一定程度,光靠过滤已经不够了,
</s> 这类裸 token 不在过滤范围内,照样透传出来。
03 重装过程
Step 1 — 拉取 workspace
Hachimi 的所有配置文件、memory、自定义技能都存在一个私有 GitHub 仓库里。 这是最关键的一步——先把「记忆」拿回来。
cd /home/ubuntu/.openclaw/workspace
git init
git remote add origin https://github.com/<user>/<repo>.git
git fetch origin
git reset --hard origin/master
git branch --set-upstream-to=origin/master master
拉完之后,workspace 里就有了 MEMORY.md、SOUL.md、
memory/ 历史日记、以及几个自定义 skill(self-commit、self-followup、session-resume)。
Step 2 — 配置 openclaw.json
OpenClaw 的核心配置文件是 ~/.openclaw/openclaw.json。
新安装后这里是空白的,需要手动填回来。
主要包括:
-
主模型配置
-
Gateway 端口和 auth token
-
Telegram bot token 和频道白名单
-
各类 API key(LLM、搜索、语音识别、Notion 等)
-
memory-lancedb 插件(向量记忆)
大部分 key 之前存在 pass 里,直接读取。
顺便把所有 key 都备份进了 AWS SSM Parameter Store:
aws ssm put-parameter --name "/mybot/<key-name>" \
--value "..." --type SecureString --overwrite
Step 3 — 重启 gateway,验证插件
systemctl --user restart openclaw-gateway
openclaw status
确认输出里有:
Memory │ enabled (plugin memory-lancedb)
Telegram │ ON │ OK
这次配置里有个小坑:memory-lancedb 的配置字段之前用的是
autoCapture、autoRecall 直接放在 entries 下,
但正确格式是放在 entries.memory-lancedb.config 里。
gateway 的 config validator 报错后才发现。
Step 4 — 导入历史记忆
LanceDB 是 lazy init——gateway 启动后数据库是空的, 需要把之前的 memory 文件重新向量化入库。
NODE_PATH=/home/ubuntu/.npm-global/lib/node_modules \
OAI_KEY='sk-proj-...' \
node /tmp/import_memory.js
脚本遍历所有 memory/*.md 和 MEMORY.md,
按 800 字切块,调 OpenAI text-embedding-3-small,写入 LanceDB。
最终入库 150 条记录。
Step 5 — 重建 cron 任务
cron 任务没有存在 workspace 里(这是个教训),重装后全部丢失,需要重新添加:
openclaw cron add --name "my-task" \
--cron "0 9 * * *" --tz "America/New_York" \
--session isolated --announce --channel telegram \
--timeout-seconds 300 \
--message "执行任务..."
一个实用建议:把 cron 任务的定义也存到 workspace 的某个文件里, 重装后直接脚本化重建,而不是凭记忆一条条敲。
04 重连 WSL2 节点
连接方式
Hachimi 控制 Windows Chrome 的方式,是通过 WSL2 里跑的 OpenClaw node host, 加上一条 SSH reverse tunnel 把 Chrome CDP 端口映射到服务器:
# WSL2 本地:SSH tunnel + node host
ssh -fN -L <local-port>:127.0.0.1:<gateway-port> -i ~/.ssh/key.pem user@server
OPENCLAW_GATEWAY_TOKEN=xxx openclaw node run \
--host 127.0.0.1 --port <local-port> --display-name "Windows WSL2"
# 服务器验证 Chrome CDP 可用
curl -s http://127.0.0.1:<cdp-port>/json/version
token 因为重装变了,之前 WSL2 的 systemd 服务里硬编码的旧 token 连接失败。 更新 token 之后恢复正常。
exec approval 的坑
Node exec 需要 approval——但 Telegram 频道不支持 approval UI。 默认情况下,从 Telegram 触发的 node exec 会直接报错:
Exec approval is required, but chat exec approvals are not enabled on Telegram.
解决方式是两边同时配:
WSL2 侧(~/.openclaw/exec-approvals.json):
{
"version": 1,
"defaults": { "security": "full", "ask": "off", "askFallback": "full" },
"agents": { "main": { "security": "full", "ask": "off", "askFallback": "full" } }
}
服务器侧(openclaw.json):
"tools": {
"exec": { "security": "full", "ask": "off" }
}
另一个坑:如果在 openclaw.json 里全局设置 tools.exec.host = "node",
所有 exec 都会路由到 node,包括需要在服务器本地跑的命令——
这会导致 gateway restart 等操作也跑到 WSL2,形成循环死锁。
正确做法是按需在每次 exec 调用里指定 host=node,不设全局默认。
05 教训与改进
写一份重装手册
这次重装最大的问题是靠「记忆」——AI 的记忆,还有我自己的记忆。 重装过程里有好几个步骤是靠翻历史 memory 文件和 Telegram 记录拼凑的。
事后在 workspace 根目录新增了 SETUP.md,
把所有步骤、字段、命令、踩过的坑全部写进去。
下次重装按文档走就行。
用 AWS SSM 集中管理密钥
之前 key 分散在 pass、openclaw.json、甚至 Telegram 聊天记录里,
重装时找起来非常麻烦。
这次把所有 key 统一存到 AWS SSM Parameter Store, 按服务分命名空间统一管理。
取用时:
aws ssm get-parameter \
--name "/mybot/<key-name>" \
--with-decryption \
--query Parameter.Value --output text
EC2 上的 IAM Role 直接授权,不需要额外的 access key。 这是最后没做完的一步——当前还是靠 root key 访问,后续需要换成 Role-based。
精简 MEMORY.md
重装完之后发现 MEMORY.md 已经膨胀到 300 行,
里面混杂着「运行时要用的信息」和「只有重装才需要看的 setup 步骤」。
做了一次大清理:setup 相关内容全部迁移到 SETUP.md,
MEMORY.md 只保留运行时快速查阅的内容(CDP 用法、常用命令、个人偏好、cron 任务等)。
顺便把 20 几个小碎片日记文件归档到 memory/archive/,
提炼出来的信息合并进 MEMORY.md。
06 小结
整个重装过程大概花了两个小时,期间 Hachimi 全程参与—— 她在查自己的 memory 文件、找历史配置、调试连接、写恢复手册。 某种程度上,是她在帮自己重装自己。
系统复杂度越高,「可重装性」就越重要。 这次的教训是:任何靠记忆维持的状态,都是脆弱的。 文档、代码化的配置、版本控制——这些不只是工程实践, 对 AI 助手来说,也是持续存在的方式。