Hachimi Reinstall
Background
I run an AI assistant called Hachimi on my server — built on the OpenClaw platform, connected to Telegram, handling various daily tasks and controlling a Windows Chrome browser remotely. One afternoon she started behaving strangely: every time she called a tool, the raw tool prompt was printed verbatim into the chat.
Two kinds of symptoms. First, raw tool call JSON printed directly into the chat:
{"type":"tool_use","id":"toolu_01...","name":"exec","input":{"command":"..."}}
{"type":"tool_result","tool_use_id":"toolu_01...","content":"..."}
...
Second, and stranger — </s> tokens started appearing in responses:
Something went wrong — the response contained a large number of </s> tokens, which indicates a model anomaly.
Let me try calling a tool normally now:
</s>
</s>
</s>
</s> is an end-of-sequence token that should never appear in output text.
Its presence means the context contains something that doesn't belong to the current conversation —
the model is trying to "end" a sequence it thinks is still open.
The chat filled up with JSON and garbage tokens. Context was polluted and kept growing,
leaving almost no usable token window for actual responses.
I tried /new to reset the session — no effect. Restarted the gateway — still broken.
Eventually I made the call: reinstall.
System Architecture
Telegram
↕
Hachimi (Claude Sonnet)
↕ OpenClaw Gateway (AWS EC2)
↕ WSL2 Node Host (local Windows machine)
↕ Windows Chrome CDP
↕ reverse SSH tunnel
local port on server
The system spans three places: the gateway on the server, the node host inside WSL2, and the Chrome CDP interface on Windows. A reinstall doesn't mean running one command — every one of these connections has to be re-established.
02 Symptom and Root Cause
The symptom was clear: every time the AI invoked a tool (exec, web_search, etc.),
Telegram received the raw tool_use / tool_result JSON
instead of a processed response.
This kind of issue usually means the context assembly went wrong —
tool results got inserted into the message sequence in the wrong format,
so subsequent prompt rounds carried raw structured data from the previous turn.
Essentially, the session history had a tool_use / tool_result mismatch,
the provider rejected it, and the gateway bubbled the error back as-is.
Session reset can't fix this because the underlying history format is already corrupted. The cleanest solution is to start fresh.
Interestingly, OpenClaw's gateway already has a defensive layer for exactly this kind of leakage —
before sending anything to Telegram, it strips <function_calls>
and <function_response> tags from the output:
async sendMessage(chatId, text, options = {}) {
text = text
.replace(/<function_calls>[\s\S]*?<\/function_calls>/g, "")
.replace(/<function_response>[\s\S]*?<\/function_response>/g, "")
.trim();
// ...
}
Which tells you this isn't the first time someone's hit this problem — the gateway already anticipated it.
But when the context is corrupted enough, filtering isn't sufficient.
Bare tokens like </s> fall outside the filter and pass through unchanged.
03 Reinstall Process
Step 1 — Pull the workspace
All of Hachimi's config files, memory, and custom skills live in a private GitHub repository. This is the most important step — getting the "memory" back first.
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
Once pulled, the workspace has MEMORY.md, SOUL.md,
the memory/ journal directory, and several custom skills
(self-commit, self-followup, session-resume).
Step 2 — Configure openclaw.json
OpenClaw's core config file is ~/.openclaw/openclaw.json.
After a fresh install, it's blank. The main things to fill in:
-
Model configuration
-
Gateway port and auth token
-
Telegram bot token and channel allowlist
-
API keys (LLM, search, speech-to-text, integrations)
-
memory-lancedb plugin (vector memory)
Most keys were already stored in pass.
This time I also backed everything up to AWS SSM Parameter Store for easier recovery next time:
aws ssm put-parameter --name "/mybot/<key-name>" \
--value "..." --type SecureString --overwrite
Step 3 — Restart gateway and verify plugins
systemctl --user restart openclaw-gateway
openclaw status
Checking for:
Memory │ enabled (plugin memory-lancedb)
Telegram │ ON │ OK
One gotcha here: the memory-lancedb config fields autoCapture
and autoRecall need to go inside entries.memory-lancedb.config,
not directly under entries. The gateway's config validator caught it,
but it took a couple of attempts to get the nesting right.
Step 4 — Re-import memory into LanceDB
LanceDB uses lazy init — the database is empty after gateway startup. All the previous memory files need to be re-embedded and re-ingested.
NODE_PATH=/home/ubuntu/.npm-global/lib/node_modules \
OAI_KEY='sk-proj-...' \
node /tmp/import_memory.js
The script walks all memory/*.md and MEMORY.md,
splits them into ~800-character chunks, calls OpenAI's text-embedding-3-small,
and writes to LanceDB. Ended up with 150 records ingested.
Step 5 — Rebuild cron jobs
Cron jobs weren't stored in the workspace (lesson learned), so they were all gone after reinstall:
openclaw cron add --name "my-task" \
--cron "0 9 * * *" --tz "America/New_York" \
--session isolated --announce --channel telegram \
--timeout-seconds 300 \
--message "run task..."
Practical tip: keep cron job definitions in a file in your workspace, so reinstall means running a script, not typing from memory.
04 Reconnecting the WSL2 Node
How the connection works
Hachimi controls Windows Chrome through an OpenClaw node host running inside WSL2, combined with a reverse SSH tunnel that maps the Chrome CDP port back to the server:
# 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"
# Verify Chrome CDP is reachable from server
curl -s http://127.0.0.1:<cdp-port>/json/version
The token changed during reinstall, so the old hardcoded token in the WSL2 systemd service caused the connection to fail silently. Updated the token, reconnected.
The exec approval problem
Node exec requires approval — but Telegram doesn't have an approval UI. By default, triggering node exec from Telegram just fails:
Exec approval is required, but chat exec approvals are not enabled on Telegram.
The fix requires config on both sides:
WSL2 side (~/.openclaw/exec-approvals.json):
{
"version": 1,
"defaults": { "security": "full", "ask": "off", "askFallback": "full" },
"agents": { "main": { "security": "full", "ask": "off", "askFallback": "full" } }
}
Server side (openclaw.json):
"tools": {
"exec": { "security": "full", "ask": "off" }
}
Another trap: if you set tools.exec.host = "node" globally in openclaw.json,
every exec call gets routed to the node — including server-local commands like gateway restart.
This creates a circular deadlock where restarting the gateway also runs on the node.
The right approach is to pass host=node per-call, not as a global default.
05 Takeaways
Write a reinstall playbook
The biggest problem during this reinstall was relying on memory — Hachimi's memory, and my own. Several steps had to be pieced together from old memory files and Telegram history.
Afterward I added a SETUP.md to the workspace root with every step,
every field, every command, and every mistake documented.
Next reinstall: follow the doc.
Centralize secrets in AWS SSM
Keys were previously scattered across pass, openclaw.json,
and even Telegram message history. Painful to track down during reinstall.
Everything is now in AWS SSM Parameter Store under a consistent namespace:
aws ssm get-parameter \
--name "/mybot/<key-name>" \
--with-decryption \
--query Parameter.Value --output text
One remaining item: the EC2 instance is still using root credentials to access SSM. That should be swapped out for a proper IAM Role.
Keep MEMORY.md lean
After the reinstall I found MEMORY.md had ballooned to 300 lines,
mixing runtime reference material with setup instructions that only matter during reinstall.
Did a cleanup pass: setup content moved to SETUP.md,
MEMORY.md trimmed to runtime essentials only.
Twenty-odd small journal fragments archived to memory/archive/,
with the relevant parts folded into MEMORY.md.
06 Summary
The whole reinstall took about two hours, with Hachimi actively involved throughout — reading her own memory files, hunting for old config values, debugging connections, writing the recovery playbook. In some sense, she was reinstalling herself.
The more complex the system, the more "reinstallability" matters. The lesson here: any state that only exists in memory — human or AI — is fragile. Documentation, version-controlled config, and proper secret management aren't just good engineering hygiene. For an AI assistant, they're how she continues to exist.