# 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)