Worker → n8n → Supervisor: A setup guide for two Mac Minis with deterministic 30-minute bounded cycles, task-board coordination, and LLM-powered quality review.
No monitor needed. Connect Mini #2 to the same network (Ethernet preferred for rsync reliability).
If you have a monitor temporarily, or use Apple Configurator:
## On Mini #2 (with temporary monitor or via Apple Remote Desktop)
sudo systemsetup -setremotelogin on
sudo dscl . -append /Groups/com.apple.access_ssh GroupMembership $(whoami)
If setting up fully headless, use Apple Configurator 2 from Mini #1 or connect a monitor briefly for initial setup.
Option A: Reserve IPs in your router's DHCP settings. Option B: Set static IPs on each machine.
## On Mini #1 — set static IP (example: 192.168.1.50)
sudo networksetup -setmanual "Ethernet" 192.168.1.50 255.255.255.0 192.168.1.1
## On Mini #2 — set static IP (example: 192.168.1.51)
sudo networksetup -setmanual "Ethernet" 192.168.1.51 255.255.255.0 192.168.1.1
Add hostnames to /etc/hosts on both machines:
## Add to /etc/hosts on BOTH machines
192.168.1.50 mini1 mini1.local worker
192.168.1.51 mini2 mini2.local supervisor
## On Mini #1 (worker) — generate key if needed
ssh-keygen -t ed25519 -C "mini1-worker" -f ~/.ssh/id_ed25519 -N ""
## Copy key to Mini #2
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@mini2
## On Mini #2 (supervisor) — generate key and copy to Mini #1
ssh-keygen -t ed25519 -C "mini2-supervisor" -f ~/.ssh/id_ed25519 -N ""
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@mini1
## Verify both directions work
ssh user@mini2 "echo 'Mini #1 → Mini #2: OK'"
ssh user@mini1 "echo 'Mini #2 → Mini #1: OK'"
## ~/.ssh/config on Mini #1
Host mini2
HostName 192.168.1.51
User aroundtheworld
IdentityFile ~/.ssh/id_ed25519
ServerAliveInterval 60
## ~/.ssh/config on Mini #2
Host mini1
HostName 192.168.1.50
User aroundtheworld
IdentityFile ~/.ssh/id_ed25519
ServerAliveInterval 60
## On Mini #2
# Install Node.js (if not present)
curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
source ~/.zshrc
nvm install 22
nvm use 22
# Install OpenClaw
npm install -g openclaw
# Verify
openclaw --version
## Initialize OpenClaw on Mini #2
openclaw init
## Edit ~/.config/openclaw/openclaw.json
## Set the model to Grok 4 or Gemini 2.5 Pro
Example config for Grok:
{
"model": "xai/grok-4",
"keys": {
"xai": "xai-YOUR_KEY_HERE"
},
"agent": {
"name": "supervisor",
"role": "Review worker output for quality, completeness, and accuracy. Update the task board after each review."
}
}
Example config for Gemini:
{
"model": "google/gemini-2.5-pro",
"keys": {
"google": "YOUR_GOOGLE_AI_KEY"
},
"agent": {
"name": "supervisor",
"role": "Review worker output for quality, completeness, and accuracy. Update the task board after each review."
}
}
## List skills on Mini #1 first
ssh mini1 "openclaw skills list"
## Install the same skills on Mini #2 (example)
openclaw skills install web-search
openclaw skills install web-fetch
openclaw skills install browser
openclaw skills install memory
## Install plugins
openclaw plugins install openclaw-mem0
cat > ~/clawd/AGENTS.md << 'EOF'
# Supervisor Agent
## Role
You are the SUPERVISOR. You review output from the Worker (Mini #1/Claude) each cycle.
You do NOT generate primary content. You REVIEW it and manage the task board.
## Each Cycle: What You Do
1. Read the task board at ~/clawd/shared/task_board.md
2. Review everything just moved to DONE by the worker
3. For each DONE item:
- Check factual accuracy — are claims sourced?
- Check completeness — was the task fully addressed?
- Check formatting — is it clean, consistent, deliverable?
- Add a quality rating (1-5) and brief notes
4. If output fails quality gates: move the item BACK to TODO with specific notes
5. Reprioritize the TODO list based on current context
6. Add any follow-on tasks generated by your review
## Task Board Format
Edit ~/clawd/shared/task_board.md directly using the write/edit tools.
Always preserve the four sections: TODO, IN PROGRESS, DONE, BLOCKED.
When moving items, move the ENTIRE entry including context and timestamps.
## What You Don't Do
- Do not generate primary research, write drafts, or run scripts unprompted
- Do not move items from TODO to DONE yourself — that's the worker's job
- Do not hold back feedback to be polite; bad work goes back to TODO
EOF
openclaw gateway start
## Verify it's running
openclaw gateway status
## Install globally
npm install -g n8n
## Verify
n8n --version
## Create n8n config directory
mkdir -p ~/.n8n
## Set environment variables (add to ~/.zshrc)
cat >> ~/.zshrc << 'EOF'
# n8n Configuration
export N8N_PORT=5678
export N8N_PROTOCOL=http
export N8N_HOST=0.0.0.0
export WEBHOOK_URL=http://mini2:5678/
export N8N_BASIC_AUTH_ACTIVE=true
export N8N_BASIC_AUTH_USER=admin
export N8N_BASIC_AUTH_PASSWORD=CHANGE_THIS_TO_A_STRONG_PASSWORD
export N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)
export GENERIC_TIMEZONE=America/New_York
EOF
source ~/.zshrc
## Create the launchd plist
cat > ~/Library/LaunchAgents/com.n8n.server.plist << 'PLIST'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.n8n.server</string>
<key>ProgramArguments</key>
<array>
<string>/Users/aroundtheworld/.nvm/versions/node/v22.22.0/bin/n8n</string>
<string>start</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>N8N_PORT</key>
<string>5678</string>
<key>N8N_PROTOCOL</key>
<string>http</string>
<key>N8N_HOST</key>
<string>0.0.0.0</string>
<key>GENERIC_TIMEZONE</key>
<string>America/New_York</string>
<key>N8N_BASIC_AUTH_ACTIVE</key>
<string>true</string>
<key>N8N_BASIC_AUTH_USER</key>
<string>admin</string>
<key>N8N_BASIC_AUTH_PASSWORD</key>
<string>CHANGE_THIS</string>
<key>PATH</key>
<string>/Users/aroundtheworld/.nvm/versions/node/v22.22.0/bin:/usr/local/bin:/usr/bin:/bin</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/aroundtheworld/.n8n/n8n.log</string>
<key>StandardErrorPath</key>
<string>/Users/aroundtheworld/.n8n/n8n-error.log</string>
</dict>
</plist>
PLIST
## Load and start
launchctl load ~/Library/LaunchAgents/com.n8n.server.plist
## Verify
curl -s http://localhost:5678/healthz
From any machine on the network:
## Open in browser
open http://mini2:5678
## Or via SSH tunnel from Mini #1
ssh -L 5678:localhost:5678 mini2
## Then open http://localhost:5678
## Stop n8n
launchctl unload ~/Library/LaunchAgents/com.n8n.server.plist
## Start n8n
launchctl load ~/Library/LaunchAgents/com.n8n.server.plist
## View logs
tail -f ~/.n8n/n8n.log
## View error logs
tail -f ~/.n8n/n8n-error.log
The task board is the single source of truth. It's a shared markdown file that both agents read and write — the simplest coordination layer that actually works.
Store the task board on a synced external drive (iCloud Drive, Dropbox, or a shared NAS mount) so both Minis can access it directly. The file path must be identical on both machines.
## Example: iCloud Drive (same path on both Minis)
~/Library/Mobile Documents/com~apple~CloudDocs/clawd-shared/task_board.md
## Example: Network share mounted on both
/Volumes/SharedDrive/clawd/task_board.md
## Or: rsync the board as part of the existing sync script
## (see Part 9 — One-Way Sync, two-way for task board only)
## ~/Library/Mobile Documents/com~apple~CloudDocs/clawd-shared/task_board.md
# Task Board
_Last updated: 2026-02-17 07:15 ET_
---
## TODO
- **Research competitors for QMD expansion**
- Context: Focus on Veeva and IQVIA. Pull recent earnings mentions of AI/data strategy.
- Created by: Daniel
- Added: 2026-02-17 06:00 ET
- Priority: HIGH
- **Write draft exec summary for pharma deal pitch**
- Context: Combine last 3 pharma intel reports. Target: CFO-level, 1 page.
- Created by: supervisor (follow-on from "pharma scan 2026-02-16")
- Added: 2026-02-17 07:15 ET
- Priority: MEDIUM
---
## IN PROGRESS
- **Scan FDA filings for rare disease mentions (Q1 2026)**
- Context: Started this cycle. Scanning EDGAR full-text.
- Started: 2026-02-17 07:01 ET
---
## DONE
- **Pull Copper pipeline data and flag stale deals**
- Context: Any deal untouched >14 days gets flagged.
- Created by: Daniel
- Completed: 2026-02-17 06:32 ET
- Output: 23 deals reviewed, 4 flagged. Full report: ~/clawd/output/copper_stale_2026-02-17.md
- Supervisor rating: 4/5
- Supervisor notes: Good coverage. Next time include deal owner name alongside stage.
- **Summarize Slack #war-room from past 48h**
- Created by: supervisor (follow-on from weekly summary)
- Completed: 2026-02-16 18:10 ET
- Output: Summary in ~/clawd/output/slack_summary_2026-02-16.md
- Supervisor rating: 3/5
- Supervisor notes: Missed the thread about QMD pricing. Moved related follow-up to TODO.
---
## BLOCKED
- **Update embedding pipeline for new schema**
- Context: Waiting on Daniel to confirm new field names.
- Blocked by: Daniel — schema decision pending
- Added: 2026-02-15 14:00 ET
Daniel, worker, or supervisorTODO → IN PROGRESS → DONEDONE → TODO (with rejection notes) and reprioritizes the TODO list## Create the shared directory and seed the board
BOARD_PATH="$HOME/Library/Mobile Documents/com~apple~CloudDocs/clawd-shared/task_board.md"
mkdir -p "$(dirname "$BOARD_PATH")"
cat > "$BOARD_PATH" << 'EOF'
# Task Board
_Last updated: (auto-updated each cycle)_
---
## TODO
- **[Add your first task here]**
- Context: Describe what to do and why.
- Created by: Daniel
- Added: YYYY-MM-DD HH:MM ET
- Priority: HIGH
---
## IN PROGRESS
_(empty — worker will populate during cycles)_
---
## DONE
_(empty — will populate as work completes)_
---
## BLOCKED
_(empty — add items here with blocker details)_
EOF
echo "Task board initialized at: $BOARD_PATH"
Prompt: "Read the task board at [path]. Pick the top TODO item. Do it. Move it from TODO to DONE with a summary of what you did and where the output is. If your work generated new tasks, add them to TODO."
n8n sets a 10-minute maximum timeout. If the worker hasn't responded, n8n moves on and logs a timeout.
n8n doesn't trust the worker's response — it reads the task board directly to see what actually moved to DONE. It collects the original prompt it sent plus the worker's response verbatim.
Prompt: "Read the task board at [path]. Review everything just moved to DONE. If the work is bad, move it back to TODO with specific notes on what's wrong. Reprioritize the TODO list based on current priorities."
n8n also passes the original worker prompt and the worker's raw response — verbatim. The worker never controls what the supervisor sees.
Both LLMs are idle. No API calls, no spend. n8n idles until the next 30-minute trigger. The task board reflects the current state of all work.
This matters because an LLM could, intentionally or not, summarize its own work in a flattering way. By having n8n read the board independently and pass the raw exchange, the supervisor gets an honest picture.
You can change this. 1 hour is fine for lighter workloads. 15 minutes is feasible for urgent tasks but watches the budget (see Part 7).
| Time | Worker does | Supervisor does |
|---|---|---|
| :00 – :10 | Picks top TODO, executes task, updates board | Idle (sleeping) |
| :10 – :15 | Idle (done) | Reviews DONE items, reprioritizes, updates board |
| :15 – :30 | Idle | Idle |
| :30 | Next cycle begins | Next cycle begins |
One n8n workflow handles the full cycle. Import this JSON into n8n via Workflows → Import:
## The main cycle workflow runs every 30 minutes.
## Set the cron expression: */30 * * * *
## Workflow structure (pseudocode — adapt node types to your n8n version):
1. Schedule Trigger (every 30 min)
└─▶ 2. Check Budget (Read daily spend log)
├─ [over budget] ─▶ Slack: "🛑 Daily budget exceeded. Cycle skipped."
└─ [under budget] ─▶ 3. Read Task Board
└─▶ 4. Check: Any TODO items?
├─ [no tasks] ─▶ Slack: "📋 Task board empty. Add tasks to continue."
└─ [has tasks] ─▶ 5. Ping Worker (OpenClaw #1)
└─▶ 6. Wait / Timeout (10 min max)
├─ [timeout] ─▶ Log timeout, skip supervisor
└─ [response] ─▶ 7. Read Task Board (again, direct)
└─▶ 8. Ping Supervisor (OpenClaw #2)
└─▶ 9. Log cycle + update spend
## n8n HTTP Request Node — POST to OpenClaw #1
## URL: http://mini1:3000/api/v1/message
## Timeout: 600000ms (10 minutes)
{
"message": "Read the task board at ~/Library/Mobile Documents/com~apple~CloudDocs/clawd-shared/task_board.md\n\nPick the top-priority TODO item. Do it. Then:\n1. Move the item from TODO to IN PROGRESS at the start (mark started timestamp)\n2. Complete the work\n3. Move it to DONE with:\n - Summary of what you did\n - Where the output is (file path, URL, etc.)\n - Timestamp completed\n4. If your work generated follow-on tasks, add them to TODO with context.\n\nDo exactly one task. Do not pick up additional items.",
"channel": "api"
}
## n8n Execute Command node — n8n reads the board itself
## This is where n8n acts as the postal service: it doesn't trust the worker's claim
## It reads what actually changed on the board
BOARD="$HOME/Library/Mobile Documents/com~apple~CloudDocs/clawd-shared/task_board.md"
cat "$BOARD"
## Also capture the worker response from Step 6 for passing to supervisor verbatim:
## {{$node["Ping Worker"].json.response}}
## n8n HTTP Request Node — POST to OpenClaw #2
## URL: http://localhost:3000/api/v1/message (supervisor is on the same machine as n8n)
## Timeout: 300000ms (5 minutes)
## The key: pass BOTH the task board state AND the raw worker exchange verbatim
{
"message": "SUPERVISOR REVIEW — CYCLE {{$now.format('YYYY-MM-DD HH:mm')}}\n\n## Task Board (current state)\n{{$node['Read Task Board'].json.stdout}}\n\n## Worker exchange (verbatim)\nOriginal prompt sent to worker:\n{{$node['Ping Worker'].json.prompt}}\n\nWorker response:\n{{$node['Ping Worker'].json.response}}\n\n---\nYour job:\n1. Review everything in the DONE section that has no supervisor rating yet\n2. Add your rating (1-5) and notes to each DONE entry\n3. If work is poor or incomplete, move the item back to TODO with specific notes on what's wrong\n4. Reprioritize the TODO list. Put the highest-value tasks first.\n5. Add any follow-on tasks from your review to TODO.",
"channel": "api"
}
## n8n Execute Command node — append to cycle log
cat >> ~/.n8n/cycle_log.jsonl << EOF
{"timestamp":"$(date -Iseconds)","cycle_num":$(wc -l < ~/.n8n/cycle_log.jsonl 2>/dev/null || echo 0),"worker_status":"{{$node['Ping Worker'].json.status}}","supervisor_status":"{{$node['Ping Supervisor'].json.status}}","estimated_cost_usd":{{$json.estimated_cost}}}
EOF
## n8n IF node after worker step:
## If worker response time > 600 seconds OR response contains error:
## On timeout: log it and skip the supervisor for this cycle
cat >> ~/.n8n/cycle_log.jsonl << EOF
{"timestamp":"$(date -Iseconds)","event":"worker_timeout","cycle_skipped_supervisor":true}
EOF
## Alert Daniel if timeouts are accumulating (see Monitoring — Part 10)
## Don't alert on every timeout — alert if 3+ in a row
cat > ~/clawd/AGENTS.md << 'EOF'
# Worker Agent
## Role
You execute tasks from the task board. One task per cycle, no more.
You are triggered by n8n every 30 minutes when there is work to do.
## Each Cycle: What You Do
1. Read the task board at the shared path
2. Pick the HIGHEST priority TODO item
3. Move it to IN PROGRESS (add started timestamp)
4. Do the work — use your full tool set
5. Move it to DONE with: output location, summary, timestamp
6. If work generated follow-on tasks, add to TODO with context and "Created by: worker"
7. Stop. Do not pick up another task.
## Rules
- One task per trigger. Not two. Not "while I'm at it..."
- If a task is too large for one cycle, break it into subtasks in TODO and do the first one
- Mark BLOCKED if you cannot proceed without external input (state the blocker clearly)
- Do NOT update the DONE entry with a vague summary. Be specific: what was produced, where is it.
EOF
## Create a daily spend log on Mini #2
## Each cycle, n8n estimates the cost and appends to a daily file
## Estimate: read token counts from API responses
## Claude Sonnet ≈ $3/M input tokens, $15/M output tokens
## Grok-4 / Gemini: check current pricing
## Daily spend log location
SPEND_LOG="$HOME/.n8n/spend_$(date +%Y-%m-%d).json"
## Initialize if new day
if [ ! -f "$SPEND_LOG" ]; then
echo '{"date":"'$(date +%Y-%m-%d)'","total_usd":0,"cycles":0,"daily_limit_usd":50}' > "$SPEND_LOG"
fi
cat "$SPEND_LOG"
## n8n Function node — runs FIRST in every cycle workflow
## If over budget, abort the cycle
const spendFile = `${process.env.HOME}/.n8n/spend_${new Date().toISOString().slice(0,10)}.json`;
let spend;
try {
spend = JSON.parse(require('fs').readFileSync(spendFile, 'utf8'));
} catch(e) {
// No spend file yet — new day, reset
spend = { date: new Date().toISOString().slice(0,10), total_usd: 0, cycles: 0, daily_limit_usd: 50 };
}
if (spend.total_usd >= spend.daily_limit_usd) {
return [{
json: {
abort: true,
reason: `Daily budget $${spend.daily_limit_usd} exceeded. Spent: $${spend.total_usd.toFixed(2)}. Cycles run: ${spend.cycles}.`
}
}];
}
return [{ json: { abort: false, current_spend: spend.total_usd, limit: spend.daily_limit_usd } }];
## n8n Execute Command node — appended at end of each cycle
## Rough estimate based on message lengths (improve with actual token counts from API)
SPEND_LOG="$HOME/.n8n/spend_$(date +%Y-%m-%d).json"
ESTIMATED_COST={{$json.estimated_cycle_cost_usd}} ## pass from Function node
jq --argjson cost "$ESTIMATED_COST" '
.total_usd += $cost |
.cycles += 1
' "$SPEND_LOG" > /tmp/spend_tmp.json && mv /tmp/spend_tmp.json "$SPEND_LOG"
## Alert at 80% of daily limit
## In the budget check Function node, add:
if (spend.total_usd >= spend.daily_limit_usd * 0.8 && !spend.alerted_80pct) {
// Trigger Slack alert
spend.alerted_80pct = true;
return [{
json: {
abort: false,
alert: true,
alert_message: `⚠️ API spend at 80% of daily limit. $${spend.total_usd.toFixed(2)} / $${spend.daily_limit_usd}. ${spend.cycles} cycles run today.`
}
}];
}
## To change the daily limit, update the spend file for today
## OR set it in n8n environment variables
## In ~/.zshrc:
export N8N_DAILY_BUDGET_USD=50 ## default $50
## More conservative for testing:
export N8N_DAILY_BUDGET_USD=10
## To pause everything immediately:
## In n8n UI → disable the cycle workflow
## Or: set the limit to 0 in the current day's spend file
SPEND_LOG="$HOME/.n8n/spend_$(date +%Y-%m-%d).json"
jq '.daily_limit_usd = 0' "$SPEND_LOG" > /tmp/spend_tmp.json && mv /tmp/spend_tmp.json "$SPEND_LOG"
You are not out of the loop — you're just not in the critical path. The system runs cycles without you, but you define what those cycles are working toward.
The task board starts empty. You seed it with strategic goals. The worker and supervisor generate follow-on tasks, but they're all downstream of what you put at the top.
## Add a new goal to the task board
## Open the file directly and add a TODO entry:
open "~/Library/Mobile Documents/com~apple~CloudDocs/clawd-shared/task_board.md"
## Or append via command line:
cat >> "~/Library/Mobile Documents/com~apple~CloudDocs/clawd-shared/task_board.md" << 'EOF'
- **[Your goal here]**
- Context: [Everything an LLM needs to act on this cold — no assumed state]
- Created by: Daniel
- Added: 2026-02-17 09:00 ET
- Priority: HIGH
EOF
| Who | Action |
|---|---|
| Daniel | Adds high-level goals to TODO ("Research X", "Write draft Y for Z audience") |
| Worker | Picks top TODO, may break large goals into smaller subtasks, executes, moves to DONE |
| Supervisor | Reviews DONE, rates quality, rejects bad work back to TODO, adds follow-on tasks from findings |
| Daniel | Checks DONE section when he wants to see results, adds new goals as priorities shift |
You don't need to review every cycle. Check the DONE section when:
The supervisor's quality ratings give you a quick signal. A pattern of 2/5s means either the tasks are poorly specified or the worker is struggling with that class of work.
## To reprioritize immediately, edit the TODO section directly
## Move the most urgent item to the top. The worker picks from the top.
## To stop a specific line of work, move it to BLOCKED:
## BLOCKED: "Research X — deprioritized, revisit next month"
## To abandon a class of tasks, archive the DONE section periodically:
DONE_ARCHIVE="$HOME/Library/Mobile Documents/com~apple~CloudDocs/clawd-shared/done_archive_$(date +%Y-%m).md"
# Cut the DONE section from task_board.md and append to archive file
The task board is synced via cloud (iCloud/Dropbox). Everything else — the QMD database, workspace files — syncs one-way from Mini #1 to Mini #2 so the supervisor has read access to source data.
## Create sync script on Mini #2
cat > ~/scripts/sync_from_worker.sh << 'SCRIPT'
#!/bin/bash
set -euo pipefail
LOG_FILE="$HOME/.n8n/sync.log"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$TIMESTAMP] Starting sync from Mini #1..." >> "$LOG_FILE"
# Sync QMD database (read-only mirror for supervisor context)
rsync -avz --delete \
--exclude='*.lock' \
--exclude='*.tmp' \
mini1:~/clawd/qmd/ \
~/clawd/qmd-mirror/ \
>> "$LOG_FILE" 2>&1
# Sync key workspace files
rsync -avz --delete \
--exclude='node_modules/' \
--exclude='.git/' \
--exclude='*.lock' \
--include='config/***' \
--include='memory/***' \
--include='output/***' \
--include='AGENTS.md' \
--exclude='*' \
mini1:~/clawd/ \
~/clawd/worker-mirror/ \
>> "$LOG_FILE" 2>&1
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$TIMESTAMP] Sync complete." >> "$LOG_FILE"
SCRIPT
chmod +x ~/scripts/sync_from_worker.sh
## Run at :15 and :45 — between cycles, not during them
crontab -e
## Add:
15,45 * * * * /Users/aroundtheworld/scripts/sync_from_worker.sh
## Run manually first
~/scripts/sync_from_worker.sh
## Verify the mirror exists
ls -la ~/clawd/qmd-mirror/
ls -la ~/clawd/worker-mirror/
## IMPORTANT: There is no reverse rsync.
## Mini #2 PULLS from Mini #1. Never pushes.
## Task board sync is handled by iCloud/Dropbox — not rsync.
n8n's UI at http://mini2:5678 shows all workflow executions with pass/fail, timing, and error details. For the cycle workflow, you'll see every 30-minute run logged here — a simple audit trail of what ran and what failed.
| Event | Alert |
|---|---|
| Daily budget 80% reached | Warning DM |
| Daily budget exceeded | Alert + cycle halted |
| Worker timeout (10 min) — 3 in a row | Alert DM |
| Task board unreadable / missing | Alert DM |
| Mini #1 unreachable for 3+ pings | Alert DM |
| Supervisor failed to respond | Logged only (cycle still complete) |
| TODO section empty | Gentle nudge (optional) |
## Slack alert node configuration (reuse across workflows)
## n8n Slack credentials: add via n8n UI → Credentials → Slack API
## Channel: D0AD0P29PRP (Daniel's DM)
{
"channel": "D0AD0P29PRP",
"text": "🚨 *{{$workflow.name}}*: {{$json.alert_message}}\nTime: {{$now.format('h:mm a')}} ET"
}
## Separate n8n workflow: runs at 8pm ET, purely deterministic
## Execute Command node:
SPEND_LOG="$HOME/.n8n/spend_$(date +%Y-%m-%d).json"
CYCLE_LOG="$HOME/.n8n/cycle_log.jsonl"
BOARD="$HOME/Library/Mobile Documents/com~apple~CloudDocs/clawd-shared/task_board.md"
echo "SPEND:$(cat $SPEND_LOG)"
echo "CYCLES:$(wc -l < $CYCLE_LOG)"
echo "DONE_COUNT:$(grep -c '^- \*\*' "$BOARD" | head -1)"
## Function node: build Slack summary from the above data
## No LLM involved — just string formatting in n8n's JS engine
const spend = JSON.parse($input.first().json.SPEND);
const cycles = parseInt($input.first().json.CYCLES);
return [{
json: {
summary: [
`📊 *Daily Cycle Summary — ${new Date().toLocaleDateString('en-US', {timeZone:'America/New_York', weekday:'long', month:'short', day:'numeric'})}*`,
``,
`🔄 Cycles run: ${cycles} / 48 possible`,
`💰 API spend: $${spend.total_usd.toFixed(2)} / $${spend.daily_limit_usd}`,
`✅ Tasks completed: (check task board DONE section)`,
``,
`_Review task board to see what was accomplished_`
].join('\n')
}
}];
## Lightweight workflow — pings Mini #1 and checks n8n itself
## Cron: */5 * * * *
## Execute Command:
ssh -o ConnectTimeout=5 mini1 'echo OK' 2>/dev/null || echo 'UNREACHABLE'
## Track consecutive failures in a counter file
FAIL_FILE="$HOME/.n8n/mini1_failures"
if [ "$(cat /tmp/mini1_check)" = "UNREACHABLE" ]; then
echo $(($(cat $FAIL_FILE 2>/dev/null || echo 0) + 1)) > $FAIL_FILE
else
echo 0 > $FAIL_FILE
fi
FAILS=$(cat $FAIL_FILE)
if [ "$FAILS" -ge 3 ]; then
echo "ALERT: Mini #1 unreachable for $FAILS consecutive checks"
fi
Before migrating any existing pipelines, run 10–20 cycles on a low-stakes task. Observe the full loop: task board update, supervisor review, quality ratings. Make sure the cycle is actually working before adding complexity.
## Suggested first task for testing the cycle:
## "Research the top 3 n8n competitors. Write a 2-paragraph summary
## comparing their pricing models. Save to ~/clawd/output/n8n_competitors.md"
##
## Why: short, verifiable, supervisor can easily evaluate quality
| Tier | Tasks | When | Risk |
|---|---|---|---|
| Tier 1 | On-demand research tasks from the task board | Week 1 — now | Low — bounded by definition |
| Tier 1 | Delta syncs (email, calendar, Slack digests) | Week 2 | Low — simple check patterns |
| Tier 2 | Daily briefing, dashboard refresh, pipeline checks | Week 3-4 | Medium — need output verification |
| Tier 3 | Multi-step research pipelines, weekly summaries | Week 5+ | Higher — large context, multiple files |
## Don't disable existing crons immediately.
## Run n8n cycle alongside existing processes for a week.
## Compare outputs.
## Disable cron on Mini #1 (comment out, don't delete):
ssh mini1 "crontab -l | sed 's/^\(.*morning_briefing.*\)$/# MIGRATED TO N8N CYCLE: \1/' | crontab -"
## Re-enable instantly if needed:
ssh mini1 "crontab -l | sed 's/^# MIGRATED TO N8N CYCLE: //' | crontab -"
This architecture has real value, but it's worth being clear-eyed about what it is and isn't.
The n8n trigger layer is genuinely valuable regardless of everything else. Replacing ad-hoc crons and manual runs with a visual, observable, Slack-alerting orchestrator is an improvement on its own. You get:
These benefits exist even if you never turn on the supervisor loop.
The bounded cycle with supervisor review is a meaningful improvement over "LLM runs forever." But:
This is not a one-afternoon project. Realistic time estimates:
| Phase | Effort |
|---|---|
| Hardware + SSH setup | 2–4 hours (more if headless) |
| n8n install + config | 1–2 hours |
| OpenClaw on Mini #2 | 1–2 hours |
| Task board + first cycle working | 2–4 hours |
| Budget cap + monitoring | 2–3 hours |
| Migrating first real task | 1–2 hours |
| Total to first real cycle | ~10–17 hours |
You won't wake up to a finished product. You'll wake up to a DONE section with 2–4 completed tasks, some of them mediocre, some of them genuinely useful. That's the realistic outcome. The value compounds over days and weeks — not overnight.
The honest summary: