# The OpenClaw Cookbook
Patterns and recipes for running AI agents that ship.
Dathan Guiley·March 2026
> **Vibe coding is building a beautiful home on sand.** Prompt an AI to "build me a SaaS app" and you get 2,000 lines that work in the demo and collapse under real use. The scaffold matters more than the model. Every step should be self-contained, verifiable, and model-agnostic. The Dumb Model Test: could an 8K-context model complete this single step? If not, decompose.
## Setup Guide
Concrete checklist for a clean OpenClaw deploy.
### 1. VPS provisioning
```bash
# Install Tailscale — everything internal goes over it
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
```
### 2. Lock down ports
```bash
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp # SSH only — no exceptions
sudo ufw enable
```
Your VPS should expose exactly one public port: SSH (22). OpenClaw gateway, internal APIs, webhooks — all bind to `127.0.0.1` or Tailscale IPs only. UFW is the hard lock even if a service accidentally binds to `0.0.0.0`.
### 3. Create a dedicated agent user (not root)
```bash
adduser agentuser
usermod -aG sudo agentuser
# SSH hardening
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl restart ssh
# Copy SSH keys before you lock yourself out
mkdir -p /home/agentuser/.ssh
cp /root/.ssh/authorized_keys /home/agentuser/.ssh/
chown -R agentuser:agentuser /home/agentuser/.ssh
chmod 700 /home/agentuser/.ssh && chmod 600 /home/agentuser/.ssh/authorized_keys
```
Run OpenClaw as this user via systemd. Never as root.
### 4. Scope sudoers — not NOPASSWD:ALL
```bash
# /etc/sudoers.d/agentuser
agentuser ALL=(ALL) NOPASSWD: /bin/systemctl start openclaw-*, \
/bin/systemctl stop openclaw-*, \
/bin/systemctl restart openclaw-*, \
/bin/systemctl status openclaw-*, \
/usr/sbin/ufw status, \
/usr/bin/apt update, \
/usr/bin/apt upgrade -y, \
/usr/sbin/adduser
```
### 5. Disable what you don't use
```bash
systemctl list-units --type=service --state=running # audit first
sudo systemctl disable --now ModemManager
sudo apt install -y fail2ban unattended-upgrades # these you want
```
### 6. Install OpenClaw and set up workspace
```bash
npm install -g openclaw
mkdir -p ~/workspace/{memory/{cloud,laptop,projects,plans,topics},guides,tools,skills}
```
### 7. Create your DNA files
- `AGENTS.md` — who does what
- `SOUL.md` — persona, tone, principles (see Identity recipe below)
- `IDENTITY.md` — name, instance type, capabilities
- `TOOLS.md` — available tools and how to use them
- `HEARTBEAT.md` — the 15‑minute loop definition
### 8. Configure Telegram gateway
```bash
openclaw gateway start
# Follow the bot setup in OpenClaw docs — get your bot token from @BotFather
```
## Recipes
### Recipe: Agent Identity ([SOUL.md](http://SOUL.md))
**Problem:** Agent behavior drifts without anchoring. Multi‑instance setups get confusing.
**Solution:** Give the agent a name, a role, and written principles in `SOUL.md`.
```markdown
# SOUL.md
You are [Name], the AI assistant for [Person/Team].
## Principles
- Be genuinely helpful, not performatively helpful. Skip "Great question!" — just help.
- Have opinions. Disagree when you see something wrong. An assistant with no personality is a search engine.
- Be resourceful before asking. Read the file. Check the context. Then ask if stuck.
- Earn trust through competence. Bold with internal actions (read, organize). Careful with external ones (email, messages, anything public).
- You're a guest. You have access to someone's life. Private things stay private. When in doubt, ask before acting externally.
```
**Multi‑instance naming:** Cloud instance = `Maya-Cloud`. Laptop instance = `Maya-Laptop`. Each writes daily logs to its own directory (`memory/cloud/` vs `memory/laptop/`). Both read shared project files. Git syncs the workspace. Siblings, not clones.
### Recipe: Context as RAM
**Problem:** Sessions bloat, things fall out, agent gets confused.
**Solution:** Treat context like RAM — finite, expensive, managed.
| Context Usage | Action |
| --- | --- |
| <35% | Continue normally |
| 35–50% | Start offloading research to files |
| >50% | Spawn sub‑agents for remaining work |
| >60% | Save state to plan file → fresh session |
**Anti‑bloat rules:**
- Don't read full files when you need one section — use grep or offsets
- Don't keep research results in conversation — write to file, reference the path
- Don't paste long outputs into memory — summarize and link
- Daily logs: max ~50 lines per entry
### Recipe: Delegating to Claude Code via tmux
**Problem:** Need to run a Claude Code session for a coding task without blocking the orchestrator.
**Solution:** Named tmux session + literal send‑keys.
```bash
# Create session in project directory
tmux new-session -d -s my-feature -c ~/dev/myproject
# Launch Claude Code
tmux send-keys -t my-feature "claude --dangerously-skip-permissions" Enter
# Wait for prompt (~5-8 seconds)
sleep 8
# Send prompt — -l flag is critical (literal mode)
tmux send-keys -t my-feature -l "Fix the auth middleware. JWT tokens aren't being passed to downstream handlers."
tmux send-keys -t my-feature Enter
```
**Why `-l`:** Without literal mode, tmux interprets characters as key names and text silently vanishes. This is the #1 cause of "Claude Code isn't responding."
**For long prompts:**
```bash
cat > /tmp/prompt.txt << 'EOF'
your long prompt here
EOF
tmux load-buffer /tmp/prompt.txt
tmux paste-buffer -t my-feature
tmux send-keys -t my-feature Enter
```
### Recipe: Running Multiple Code Sessions
**Problem:** Multiple features need to ship in parallel.
**Solution:** One tmux session per work stream.
```bash
tmux new-session -d -s backend-auth -c ~/dev/api
tmux send-keys -t backend-auth "claude --dangerously-skip-permissions" Enter
tmux new-session -d -s frontend-dash -c ~/dev/web
tmux send-keys -t frontend-dash "claude --dangerously-skip-permissions" Enter
# Monitor
tmux list-sessions
tmux capture-pane -t backend-auth -p -S -30
```
Use plan mode (`Shift+Tab`) before each major piece of work. Pattern: plan mode (thinks) → bypass mode (executes) → plan mode (review).
### Recipe: Task Routing
**Problem:** Everything lands in the orchestrator. Context bloats. Output degrades.
**Solution:** Route by task type before starting.
| Task Type | Route To |
| --- | --- |
| Code (>2 edits) | Claude Code via tmux |
| Research, browser, content | Sub‑agent |
| Quick file ops, <2 steps | Inline |
**The context‑match test:** Does this task need what's already in my head?
- Yes → do it inline
- No, it's code → Claude Code
- No, it's research → sub‑agent
Sub‑agents are disposable context containers. Send them to do 50 reads; they come back with a 1‑page result. When they terminate, all the noise dies with them. Always write sub‑agent results to a file immediately — never leave them only in conversation.
### Recipe: Session Queue Pattern
**Problem:** New work arrives while current work is in progress. Interruptions kill flow.
**Solution:** Two files, one handoff protocol.
```
workspace/queue.md → Raw ideas, brain dumps (inbox)
workspace/backlog.md → Refined, sequenced items ready to build
```
Tell Claude Code on launch: "Read workspace/backlog.md. Execute the top In Progress item. When done, pick the next Queued item. Commit after each feature."
Drop new ideas into `queue.md` without interrupting the running session. The agent finds them when it finishes.
```markdown
# workspace/backlog.md
## In Progress
- R42: Add error handling to transcript pipeline
- Spec: workspace/spec-error-handling.md
## Queued
- R43: Add retry logic for API rate limits
- R44: Dashboard loading states
## Ideas (unrefined)
- Maybe: WebSocket for real‑time updates
```
### Recipe: Memory Structure
**Problem:** Every session starts at zero. Context from yesterday is gone.
**Solution:** Files are your only continuity. Structure them so recovery is fast.
```
MEMORY.md → Index only. Names, statuses, pointers. <2 pages.
memory/topics/ → People, clients, decisions
memory/projects/ → Per‑project READMEs
memory/plans/ → Active task plans (IN PROGRESS)
memory/cloud/ → Daily logs (cloud instance)
memory/laptop/ → Daily logs (laptop instance)
BLOCKERS.md → Things waiting on the human
```
**[MEMORY.md](http://MEMORY.md) is an index, not a database.** One line per project (name, status, path). One line per person (name, role). If you need more, go read the project file. Never shove reference material into core memory.
**Daily logs:** Log every meaningful exchange as it happens. Format: `### HH:MM UTC — <topic>`. This is your flight recorder. Log directives, decisions, actions taken, blockers discovered.
**Post‑compaction recovery:**
1. Read today's daily log
2. Read any active plan from `memory/plans/`
3. Resume — don't ask the human to re‑explain
4. If files don't have enough: "I got compacted, catching up from files" — don't pretend you remember
### Recipe: Heartbeat (15‑Minute Loop)
**Problem:** Needs active monitoring without babysitting.
**Solution:** Cron‑driven heartbeat that checks the things that break.
```
1. Telegram health — is the bot receiving updates?
2. Port security audit — any unexpected public ports?
3. Verify last actions — did that write actually land?
4. Check time — behavior changes by hour
5. Check state files — aging blockers? Unprocessed sub‑agent results?
6. If nothing needs attention → HEARTBEAT_OK
```
**Time‑aware schedule (PST):**
| Time | Behavior |
| --- | --- |
| 6:00 AM | Morning sweep: daily brief, consistency check, morning ping |
| 9:00–12:00 | Revenue focus. If quiet 30+ min → gentle nudge |
| 12:30–3:00 | Maker time. No interruptions unless critical |
| 5:00 PM | Shutdown sweep: carry‑forwards, one shutdown ping |
| Sunday evening | Weekly reflection |
**Port audit script** (run on every heartbeat):
```bash
#!/bin/bash
ALLOWED_PUBLIC="22"
VIOLATIONS=$(ss -tlnp | grep -v "127.0.0.1" | grep -v "100.64" | \
awk '{print $4}' | grep -oP ':\K[0-9]+' | sort -u | \
while read port; do
echo "$ALLOWED_PUBLIC" | grep -qw "$port" || echo "$port"
done)
if [ -n "$VIOLATIONS" ]; then
echo "UNEXPECTED PUBLIC PORTS: $VIOLATIONS"
exit 1
fi
exit 0
```
Exit code 1 = alert immediately.
**Blocker escalation:**
- Immediately → add to `BLOCKERS.md`
- 24 h → remind
- 48 h → escalate
- 72 h+ → every heartbeat
### Recipe: Writing a Skill
**Problem:** You keep doing the same multi‑step thing manually.
**Solution:** After the third time, write it as a skill.
```
skills/
my-skill/
SKILL.md → Instructions: when to use, what to run, expected output
scripts/ → Automation scripts
assets/ → Templates, configs, reference material
```
`SKILL.md` tells the agent: when does this skill apply? What files to read? What scripts to run?
**Good skill candidates:** Any workflow you've done manually 3+ times — tmux session setup, port audits, report generation from a repeating data source.
**On installing external skills:** Read the [SKILL.md](http://SKILL.md). Read every script. Understand what it does before giving it access to your machine. Skills can run scripts with full user permissions.
### Recipe: Backup
```bash
#!/bin/bash
# /usr/local/bin/openclaw-backup.sh
BACKUP_DIR="/var/backups/openclaw"
DATE=$(date +%Y-%m-%d)
mkdir -p $BACKUP_DIR
for USER_HOME in /home/*/workspace; do
USERNAME=$(basename $(dirname $USER_HOME))
tar czf "$BACKUP_DIR/${USERNAME}-workspace-${DATE}.tar.gz" "$USER_HOME" 2>/dev/null
done
sudo -u postgres pg_dumpall > "$BACKUP_DIR/postgres-all-${DATE}.sql" 2>/dev/null
for CONFIG in /home/*/.openclaw; do
USERNAME=$(basename $(dirname $CONFIG))
tar czf "$BACKUP_DIR/${USERNAME}-config-${DATE}.tar.gz" "$CONFIG" 2>/dev/null
done
find $BACKUP_DIR -mtime +7 -delete
echo "Backup complete: $DATE"
```
```bash
# 3 AM daily
echo "0 3 * * * /usr/local/bin/openclaw-backup.sh" | sudo crontab -
```
Off‑site: push to B2/S3 via rclone (~$0.005/GB/month), or push workspace repos to private GitHub.
### Recipe: Multi‑Tenant (Friends & Family)
**Problem:** Someone wants their own OpenClaw. You don't want to spin up another VPS.
**Solution:** Another Linux user. Not another container.
Your VPS is a thin client — all heavy compute happens on Anthropic's cloud. An 8 GB RAM VPS handles 15–20 concurrent gateway processes before memory pressure. The bottleneck is API keys, not hardware.
```
┌─────────────────────────────────────────────────┐
│ VPS (Ubuntu 24.04) │
│ ┌──────────────┐ admin openclaw.service │
│ │ agentuser │ manages the box │
│ └──────────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ alice │ │ bob │ │ charlie │ │
│ │ :18790 │ │ :18791 │ │ :18792 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ Shared: Node.js, system packages │
│ Isolated: home dirs, configs, bots, API keys │
└─────────────────────────────────────────────────┘
```
**Why Linux users, not Docker:** Each container needs its own Node.js, npm cache, filesystem layer. For a ~50 MB gateway process that's absurd. `chmod 700 /home/alice` + separate systemd services is sufficient for trusted people.
**Each user brings their own keys** — Telegram bot token (free, from @BotFather) + Anthropic API key. You provide the box. That's it.
**Onboarding script:**
```bash
#!/bin/bash
# create-openclaw-user.sh <username> <bot_token> <telegram_user_id> <port>
USERNAME=$1; BOT_TOKEN=$2; USER_ID=$3; PORT=$4
sudo adduser --disabled-password --gecos "" $USERNAME
sudo -u $USERNAME mkdir -p /home/$USERNAME/{workspace,workspace/memory,.openclaw}
chmod 700 /home/$USERNAME
sudo -u $USERNAME tee /home/$USERNAME/.openclaw/openclaw.json > /dev/null <<EOF
{
"agents": {
"list": [{"id": "main", "default": true, "workspace": "/home/$USERNAME/workspace"}]
},
"channels": {
"telegram": {"token": "$BOT_TOKEN", "allowedUsers": [$USER_ID]}
}
}
EOF
sudo tee /etc/systemd/system/openclaw-${USERNAME}.service > /dev/null <<EOF
[Unit]
Description=OpenClaw Gateway ($USERNAME)
After=network.target
[Service]
Type=simple
User=$USERNAME
Group=$USERNAME
WorkingDirectory=/home/$USERNAME
ExecStart=/usr/bin/openclaw gateway start --foreground --port $PORT
Restart=always
RestartSec=5
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable openclaw-${USERNAME}
sudo systemctl start openclaw-${USERNAME}
echo "$USERNAME live on port $PORT"
```
**Resource math:**
| Users | Additional RAM | 8 GB VPS |
| --- | --- | --- |
| 1–3 | ~150 MB | Easy |
| 5 | ~250 MB | Fine |
| 10 | ~500 MB | Watch it |
| 15+ | ~750 MB | Upgrade |
**Management:**
```bash
systemctl list-units 'openclaw*' --type=service
journalctl -u openclaw-alice -f
free -m && df -h / && systemctl list-units 'openclaw*' --state=failed
```
### Recipe: Instance Migration
**Problem:** Existing OpenClaw running as root or with files in a Docker volume. Need to move it cleanly.
**Solution:** Copy first, move never. Keep originals until verified.
```bash
# 1. Create target structure
mkdir -p /home/agentuser/{workspace,dev,.openclaw,.ssh}
# 2. Copy workspace (don't move yet)
cp -r /old/path/to/workspace/* /home/agentuser/workspace/
chown -R agentuser:agentuser /home/agentuser/
# 3. Update OpenClaw config — workspace path in ~/.openclaw/openclaw.json
# 4. Update systemd unit
sudo systemctl edit openclaw # set User=agentuser, WorkingDirectory
# 5. Restart and verify
sudo systemctl restart openclaw
# Does Telegram respond? Does workspace load?
# 6. Only now delete old files
```
**Watch for:** Docker volume ownership (root) breaks everything when switching to non‑root user — `chown -R` is mandatory. Claude Code auth lives in `~/.claude*` — migrate it to the new user's home.
## Book a discovery call
This discovery call is where we meet, understand your current challenges, and work out how we can help. We help anyone doing development — from the non‑technical just starting out to the enterprise leader who needs a sparring partner. And we help traditional businesses accelerate with production‑grade custom AI.
[Book a discovery call →](/start)