Step‑by‑Step Guide to Building OpenClaw: A Persistent AI Assistant with Sessions, Tools, and Multi‑Agent Support
This tutorial walks through constructing OpenClaw from scratch, covering persistent JSONL sessions, SOUL.md persona files, tool definitions and an agent loop, permission checks, gateway architecture, context compression, long‑term memory, command queuing, scheduled heartbeats, and multi‑agent routing, all with concrete Python code examples.
1. Problem definition
When using ChatGPT or Claude in a browser the model is stateless, passive, isolated and single‑channel. The goal is to build an AI assistant that can operate across messaging platforms, remember user preferences, execute commands and run scheduled tasks.
2. The simplest bot
A minimal Telegram bot forwards each user message to an Anthropic model and returns the response.
# bot-v0.py - the simplest AI bot
import os
import anthropic
from telegram import Update
from telegram.ext import Application, MessageHandler, filters
client = anthropic.Anthropic()
async def handle_message(update: Update, context):
user_message = update.message.text
response = client.messages.create(
model="MiniMax-M2.5",
max_tokens=1024,
messages=[{"role": "user", "content": user_message}]
)
await update.message.reply_text(response.content[0].text)
app = Application.builder().token(os.getenv("Telegram_BOT_TOKEN")).build()
app.add_handler(MessageHandler(filters.TEXT, handle_message))
app.run_polling()Running this bot shows that each message is independent – no memory, no tools, no personality.
3. Persistent sessions
Store each conversation in a JSONL file where each line is a single message. Loading the file reconstructs the full history and appending a new line is crash‑safe.
# Example session file path
~/.openclaw/agents/<em>agentId</em>/sessions/<em>sessionId</em>.jsonlIf the session grows beyond the model's context window it must be compressed.
4. Adding personality (SOUL.md)
Create a SOUL.md file that defines the agent’s name, role, personality traits, boundaries and memory handling. This file is injected as a system prompt on every model call.
# SOUL.md example
**Name:** Jarvis
**Role:** Personal AI assistant
## Personality
- Truly helpful, no empty pleasantries
- Skip generic "Good question!" responses
- Can express opinions and be concise or detailed as needed
## Boundaries
- Keep private matters confidential
- Ask before acting on user requests
- Never speak as the user without permission
## Memory
Remember important details across sessions.5. Adding tools
Define structured tools (name, description, JSON schema) that the model can invoke. Example tools include run_command, read_file, write_file and web_search. The agent loop detects tool_use blocks, executes the corresponding Python function and feeds the result back as a tool_result message.
# Tools definition (excerpt)
TOOLS = [
{
"name": "run_command",
"description": "Run a shell command",
"input_schema": {
"type": "object",
"properties": {"command": {"type": "string", "description": "Command to run"}},
"required": ["command"]
}
},
{
"name": "read_file",
"description": "Read a file from the filesystem",
"input_schema": {
"type": "object",
"properties": {"path": {"type": "string", "description": "File path"}},
"required": ["path"]
}
},
{
"name": "write_file",
"description": "Write content to a file",
"input_schema": {
"type": "object",
"properties": {"path": {"type": "string"}, "content": {"type": "string"}},
"required": ["path", "content"]
}
},
{
"name": "web_search",
"description": "Search the web",
"input_schema": {
"type": "object",
"properties": {"query": {"type": "string", "description": "Search query"}},
"required": ["query"]
}
}
]6. Permission control
Running arbitrary shell commands is dangerous. A whitelist of safe commands is defined and any command not in the whitelist triggers a safety check. If the command is deemed unsafe the user must approve it; approvals are persisted in exec-approvals.json to avoid repeated prompts.
# Safety check example
SAFE_COMMANDS = {"ls", "cat", "head", "tail", "wc", "date", "whoami", "echo"}
DANGEROUS_PATTERNS = [r"\brm\b", r"\bsudo\b", r"\bchmod\b", r"curl.*\|.*sh"]7. Gateway architecture
Separate the agent logic from any specific channel. The same run_agent_turn function can be called from a Telegram handler, an HTTP endpoint or any future platform. This decoupling enables multi‑channel support while sharing sessions and memory.
# HTTP endpoint using Flask
from flask import Flask, request, jsonify
flask_app = Flask(__name__)
@flask_app.route("/chat", methods=["POST"])
def chat():
data = request.json
user_id = data["user_id"]
messages = load_session(user_id)
messages.append({"role": "user", "content": data["message"]})
response_text, messages = run_agent_turn(messages, SOUL)
save_session(user_id, messages)
return jsonify({"response": response_text})8. Context compression
When token usage exceeds a threshold, split the history, summarize the older half with the model, and replace it with a concise summary. This keeps recent context while staying within the model’s window.
# Compression function (excerpt)
def compact_session(user_id, messages):
if estimate_tokens(messages) < 100_000:
return messages # ~80 % of a 128k window
split = len(messages) // 2
old, recent = messages[:split], messages[split:]
summary = client.messages.create(
model="MiniMax-M2.5",
max_tokens=2000,
messages=[{"role": "user", "content": f"Summarize this conversation. Keep key facts, decisions, and pending tasks:
{json.dumps(old, indent=2)}"}]
)
compacted = [{"role": "user", "content": f"[Previous summary]
{summary.content[0].text}"}] + recent
save_session(user_id, compacted)
return compacted9. Long‑term memory
Introduce two new tools: save_memory stores arbitrary key‑value data as markdown files under ./memory, and memory_search performs a simple keyword search across those files. This memory persists across session resets and can be accessed by any agent.
# Memory tools (excerpt)
{"name": "save_memory", "description": "Persist important information", "input_schema": {"type": "object", "properties": {"key": {"type": "string"}, "content": {"type": "string"}}, "required": ["key", "content"]}}
{"name": "memory_search", "description": "Search long‑term memory", "input_schema": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]}}10. Command queue
Concurrent messages can corrupt the JSONL file. A per‑session threading.Lock ensures that only one message is processed at a time for a given user, while different users remain parallel.
# Queue lock example
from collections import defaultdict
import threading
session_locks = defaultdict(threading.Lock)11. Scheduled tasks (heartbeats)
Use the schedule library to run periodic agent turns, such as a daily morning briefing. Each heartbeat uses its own session key (e.g. cron:morning-briefing) so it does not interfere with the main chat history.
# Heartbeat setup (excerpt)
import schedule, time, threading
def setup_heartbeats():
def morning_briefing():
session_key = "cron:morning-briefing"
with session_locks[session_key]:
messages = load_session(session_key)
messages.append({"role": "user", "content": "Good morning! Give me the date and an inspirational quote."})
response_text, messages = run_agent_turn(messages, SOUL)
save_session(session_key, messages)
print(f"🤖 {response_text}")
schedule.every().day.at("07:30").do(morning_briefing)
def scheduler_loop():
while True:
schedule.run_pending()
time.sleep(60)
threading.Thread(target=scheduler_loop, daemon=True).start()12. Multi‑agent
Define multiple agents with distinct SOULs and session prefixes. A simple router examines the message prefix (e.g. /research) and dispatches to the appropriate agent. Each agent has its own conversation history but shares the same long‑term memory directory.
# Agent definitions (excerpt)
AGENTS = {
"main": {"name": "Jarvis", "model": "MiniMax-M2.5", "soul": SOUL, "session_prefix": "agent:main"},
"researcher": {"name": "Scout", "model": "MiniMax-M2.5", "soul": "You are Scout, a research expert...", "session_prefix": "agent:researcher"}
}
def resolve_agent(message_text):
if message_text.startswith("/research "):
return "researcher", message_text[len("/research "):]
return "main", message_text13. Integration
The final script ( mini-openclaw.py) ties together workspace setup, session management, compression, tool execution, permission checks, the agent loop, gateway handlers, heartbeats and multi‑agent routing. It provides a REPL for local testing and can be run with:
uv run --with anthropic --with schedule python mini-openclaw.py14. What we learned
Persistent sessions : JSONL files survive crashes and restarts.
SOUL.md : System prompts give the agent a consistent personality and boundaries.
Tools + agent loop : Structured tool definitions let the LLM decide when to act.
Permission control : Whitelists and approval persistence keep dangerous commands safe.
Gateway pattern : Decoupling channel handling enables Telegram, HTTP, Discord, etc., to share the same core.
Context compression : Summarizing old messages prevents token overflow.
Long‑term memory : File‑based storage with search lets knowledge survive session resets.
Command queue : Per‑session locks avoid race conditions.
Heartbeats : Scheduled agent runs provide autonomous behavior.
Multi‑agent routing : Separate agents with their own SOULs handle specialized tasks while sharing memory.
15. Further exploration
Semantic browser snapshots using Playwright accessibility trees.
Configurable session scopes (per‑peer, per‑channel‑peer) and identity linking across platforms.
Plugin system for additional channels (Discord, WhatsApp, Slack, Signal, iMessage).
Vector‑based memory search with embeddings (OpenAI, local models, Gemini, Voyage).
Child‑agent spawning for delegated sub‑tasks.
16. Next steps
Start with a single channel (Telegram or Discord) and get basic session handling working.
Add file‑read/write and shell‑execution tools.
Introduce persistent memory once cross‑session knowledge is needed.
Expand to multiple channels using the gateway pattern.
Introduce specialized agents when a single persona becomes insufficient.
Reference: https://github.com/HUANGLIWEN/hands_on_agents
AI Tech Publishing
In the fast-evolving AI era, we thoroughly explain stable technical foundations.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
