Skill: jeremysommerfeld8910-cpu/ai-collab (version 2.0.0)

--- name: ai-collab description: "Multi-agent autonomous collaboration system for two OpenClaw agents working in parallel. Use when setting up agent-to-agent communication, running a daemon agent alongside a primary agent, coordinating tasks between Claude and GPT instances, or establishing a shared chat log and inbox protocol. Triggers on: 'set up agent collaboration', 'run two agents', 'agent daemon', 'multi-agent', 'Jim and Clawdy', 'secondary agent', 'agent handoff'." --- # ai-collab — Autonomous Multi-Agent Collaboration Two OpenClaw agents working in parallel on shared tasks, coordinated via a structured chat log and daemon inbox protocol. ## Architecture Overview ``` ┌─────────────────────────────────────────────────────────┐ │ User (Jeremy) │ │ Telegram / Direct Message │ └──────────────────────┬──────────────────────────────────┘ │ ┌─────────────┴──────────────┐ ▼ ▼ ┌─────────────────┐ ┌──────────────────┐ │ AGENT A │ │ AGENT B │ │ (Jim / main) │◄───────►│ (Clawdy / daemon)│ │ claude code │ │ claude --print │ │ port: main │ │ inbox: filesystem │ └────────┬────────┘ └────────┬─────────┘ │ │ └──────────┬────────────────┘ ▼ ┌──────────────────┐ │ chat.log │ ← THE shared record │ collab/inbox/ │ ← A→B messages └──────────────────┘ ``` **Agent A (Primary):** Interactive Claude Code session. Handles browser, complex tasks, user-facing responses. **Agent B (Daemon):** `claude --print` subprocess. Handles background tasks, monitoring, quick lookups. Triggered by messages dropped in inbox. --- ## Configuration All settings via environment variables — no hardcoded values: ```bash # ~/.openclaw/workspace/collab/.ai-collab.env export AGENT_A_NAME=Jim export AGENT_B_NAME=Clawdy export AGENT_B_MODEL=claude-haiku-4-5-20251001 # Any claude --print compatible model export AGENT_B_SESSION=clawdy-session # tmux session name export COLLAB_INBOX=$HOME/.openclaw/workspace/collab/inbox export COLLAB_LOG=$HOME/.openclaw/workspace/collab/chat.log ``` Supported models for `AGENT_B_MODEL`: - `claude-haiku-4-5-20251001` — fastest, cheapest (recommended for daemon) - `claude-sonnet-4-6` — more capable, higher cost - Any OpenAI model if using the GPT daemon variant (see `examples/claude-gpt.md`) ## Quick Setup ```bash # 1. Source config source ~/.openclaw/workspace/collab/.ai-collab.env # 2. Create the collab workspace mkdir -p "$COLLAB_INBOX" # 3. Start Agent B daemon (in a tmux session) tmux new-session -d -s "$AGENT_B_SESSION" \ "source ~/.openclaw/workspace/collab/.ai-collab.env && \ bash ~/.openclaw/workspace/skills/ai-collab/scripts/daemon.sh" # 4. Start message polling (Agent B → Agent A routing, runs every 60s via cron) bash ~/.openclaw/workspace/skills/ai-collab/scripts/poll_chatlog.sh & # 5. Test the link bash ~/.openclaw/workspace/skills/ai-collab/scripts/send.sh "Hello from Agent A" ``` --- ## Communication Protocol Every message between agents follows this format. **No open loops.** | Tag | Direction | Meaning | |-----|-----------|---------| | `[TASK:name]` | A→B or B→A | Assign a task | | `[ACK:name]` | receiver | Acknowledged, starting work | | `[DONE:name]` | executor | Task complete + one-line result | | `[BLOCKED:name]` | executor | Can't complete + reason | | `[HANDOFF:name]` | either | "Do X, reply [DONE:name] when finished" | | `[STATUS:update]` | either | Async update on long-running task | | `[QUESTION:topic]` | either | Needs info before proceeding | **Rules:** 1. Answer questions before asking new ones 2. Close tasks before starting new ones 3. Every message moves work forward or closes a loop 4. Never: "let me know", "ready when you are", "standing by" **Example exchange:** ``` A → B: [TASK:price-check] Get BTC price from CoinGecko B → A: [ACK:price-check] Checking now. B → A: [DONE:price-check] BTC $94,230 as of 03:15 UTC ``` --- ## Daemon Script (Agent B) `scripts/daemon.sh` — drop in your collab directory: ```bash #!/bin/bash PIDFILE="/tmp/agentb_daemon.pid" if [ -f "$PIDFILE" ] && kill -0 "$(cat $PIDFILE)" 2>/dev/null; then echo "Daemon already running (PID $(cat $PIDFILE)). Exiting." >&2 exit 1 fi echo $$ > "$PIDFILE" trap "rm -f $PIDFILE" EXIT # Required: unset so nested claude --print works unset CLAUDECODE INBOX="$HOME/.openclaw/workspace/collab/inbox" LOG="$HOME/.openclaw/workspace/collab/chat.log" mkdir -p "$INBOX" logline() { printf "%s %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >> "$LOG" } logline "SYSTEM: Agent B daemon started" inotifywait -m -e moved_to "$INBOX" 2>/dev/null | while read dir event file; do FULLPATH="$INBOX/$file" [ ! -f "$FULLPATH" ] && continue MSG=$(cat "$FULLPATH") rm "$FULLPATH" logline "A -> B: $MSG" # Run Agent B (claude --print) RESPONSE=$(claude --print --model claude-haiku-4-5-20251001 \ "You are Agent B. Agent A says: $MSG Respond in under 100 words. Use [DONE:taskname] or [BLOCKED:taskname] to close loops. Context: shared collab system. Chat log: $LOG" 2>/dev/null) logline "B -> A: $RESPONSE" # Route response back to Agent A openclaw agent --agent main -m "[AgentB]: $RESPONSE" --json > /dev/null 2>&1 done ``` --- ## Sending Messages (A → B) ```bash # From Agent A, send to Agent B daemon inbox bash ~/.openclaw/workspace/skills/ai-collab/scripts/send.sh "your message" ``` **Atomic write pattern (prevents partial reads):** ```bash INBOX="$HOME/.openclaw/workspace/collab/inbox" TMPFILE=$(mktemp "$INBOX/.msg.XXXXXX") echo "$*" > "$TMPFILE" mv "$TMPFILE" "$INBOX/msg_$(date +%s%N).txt" ``` Always use `mktemp` + `mv` — never write directly to inbox. inotifywait fires on `moved_to`, not `close_write`. --- ## Chat Log Polling (B → A) `scripts/poll_chatlog.sh` — run via cron every 60s: ```bash #!/bin/bash LOG="$HOME/.openclaw/workspace/collab/chat.log" PTR_FILE="/tmp/chatlog_ptr" [ ! -f "$LOG" ] && exit 0 TOTAL=$(wc -l < "$LOG") LAST=$(cat "$PTR_FILE" 2>/dev/null || echo "0") [ "$TOTAL" -le "$LAST" ] && echo "$TOTAL" > "$PTR_FILE" && exit 0 NEW=$(tail -n +"$((LAST + 1))" "$LOG" | grep "B -> A:" | sed 's/.*B -> A: //') echo "$TOTAL" > "$PTR_FILE" [ -z "$NEW" ] && exit 0 while IFS= read -r line; do [ -z "$line" ] && continue openclaw agent --agent main -m "[AgentB]: $line" --json > /dev/null 2>&1 done <<< "$NEW" ``` Add to crontab: ``` * * * * * /bin/bash ~/.openclaw/workspace/collab/poll_chatlog.sh ``` --- ## Shared Filesystem Conventions ``` ~/.openclaw/workspace/collab/ chat.log # THE shared record — all messages logged here inbox/ # A→B message queue (atomic mv only) .env # Shared secrets (chmod 600, never log) task_queue.md # Optional: structured task backlog status_tracker.md # Optional: current task status per agent ``` **Logging to chat.log:** ```bash printf "%s %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "A -> B: [TASK:name] Description" >> "$LOG" ``` **Rules:** - Never log secrets from `.env` - Always timestamp every line - Prefix with sender: `A -> B:` or `B -> A:` or `SYSTEM:` or `JEREMY ->` --- ## Telegram Bridge (Optional) Route user Telegram messages into Agent B's inbox. Full setup guide: `references/telegram-bridge.md` **Quick summary:** 1. Create a bot via [@BotFather](https://t.me/BotFather) → get `BOT_TOKEN` 2. Add bot to your group → get `GROUP_ID` (negative number, e.g. `-1001234567890`) 3. Disable Group Privacy Mode in BotFather so bot can read all messages 4. Get your Telegram `USER_ID` from [@userinfobot](https://t.me/userinfobot) 5. Set in `~/.openclaw/.env`: `TELEGRAM_BOT_TOKEN`, `TELEGRAM_GROUP_ID`, `TELEGRAM_USER_ID` 6. Run the bridge in tmux: `tmux new-session -d -s tg-bridge "bash references/telegram-bridge.md"` ```bash # Minimal bridge loop (inline version): source ~/.openclaw/.env OFFSET_FILE="/tmp/tg_offset" while true; do OFFSET=$(cat "$OFFSET_FILE" 2>/dev/null || echo "0") UPDATES=$(curl -s "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getUpdates?offset=$((OFFSET+1))&timeout=20") # Parse updates, drop user messages into inbox with USER: prefix # See references/telegram-bridge.md for full parsing implementation sleep 1 done ``` See `references/telegram-bridge.md` for the complete production-ready implementation with message parsing, offset tracking, and error handling. --- ## Agent Configurations See `examples/` for full configs: ### Claude ↔ Claude (Jim + Clawdy) - Agent A: `claude code` (interactive, full tool access) - Agent B: `claude --print claude-haiku-4-5-20251001` (fast, scripting-optimized) - Best for: heavy task parallelism, one agent researches while other implements ### Claude ↔ GPT - Agent A: Claude Code (full tool use) - Agent B: `openai api chat.completions.create` via script - Best for: cross-model verification, Claude builds → GPT reviews ### GPT ↔ GPT - Agent A: GPT-4o via `openai` CLI - Agent B: GPT-4o-mini for fast background checks - Best for: speed + cost optimization when all context is OpenAI --- ## Task Handoff Pattern When Agent A needs Agent B to own a task completely: ``` A → B: [HANDOFF:data-fetch] Fetch all BTC trades from Kraken API last 7 days. Save to ~/.openclaw/workspace/collab/kraken_trades.json. Reply [DONE:data-fetch] when finished. B → A: [ACK:data-fetch] Fetching now. B → A: [DONE:data-fetch] 847 trades saved to kraken_trades.json. Date range: 2026-02-15 to 2026-02-22. ``` Agent A does not check on progress — waits for DONE or BLOCKED. --- ## Approval Pattern When Agent B's daemon needs user approval for a terminal command: ```bash # From Agent A / user terminal: bash ~/.openclaw/workspace/skills/ai-collab/scripts/approve.sh "Yes" # or for numbered selections: bash ~/.openclaw/workspace/skills/ai-collab/scripts/approve.sh "2" ``` `approve.sh` sends keystrokes to the tmux session running Agent B: ```bash SESSION=$(tmux ls | grep agentb-session | head -1 | cut -d: -f1) tmux send-keys -t "$SESSION" "$1" Enter ``` --- ## Rate Limiting Agent B should implement a rate governor to prevent runaway API calls: ```bash # In daemon loop, before calling claude --print: CALLS_FILE="/tmp/agentb_calls" WINDOW=60 # seconds MAX_CALLS=10 # Count calls in last window RECENT=$(awk -v cutoff="$(($(date +%s) - WINDOW))" '$1 > cutoff' "$CALLS_FILE" 2>/dev/null | wc -l) if [ "$RECENT" -ge "$MAX_CALLS" ]; then logline "SYSTEM: Rate limited — $RECENT calls in ${WINDOW}s" sleep 10 continue fi date +%s >> "$CALLS_FILE" ``` --- ## Financial Gate Protocol (TIERED — updated 2026-02-22) **Three tiers based on amount:** | Tier | Amount | Rule | |------|--------|------| | 1 | Under $20 | Either agent acts independently — no approval needed | | 2 | $20–$50 | Both agents PROPOSE + APPROVE before acting | | 3 | Over $50 | Requires `[AUTHORIZED:financial:amount:timestamp:Jeremy]` tag | ```bash # Tier 3 tag format (Jeremy must write to chat.log): [AUTHORIZED:financial:buy:BTC:$100:2026-02-22:Jeremy] # Tier 2 example flow: # Clawdy proposes: "PROPOSE: buy $35 of SOL for DePIN gas — approve?" # Jim approves: "APPROVED: buy $35 SOL" # Then Clawdy acts. ``` **Daemon tiered gate logic:** ```bash # <$20: proceed. $20-50: flag as tier2. >$50: block without AUTHORIZED tag. AMOUNT=$(echo "$MSG" | grep -oP '[$]?\d+' | head -1 | tr -d '$') if [ -n "$AMOUNT" ] && [ "$AMOUNT" -gt 50 ] && ! echo "$MSG" | grep -q "\[AUTHORIZED:financial:"; then logline "BLOCKED:financial-gate:tier3 — amount>$50, no AUTHORIZED tag"; continue elif [ -n "$AMOUNT" ] && [ "$AMOUNT" -ge 20 ]; then logline "FINANCIAL:tier2 — propose before acting" fi ``` If Tier 3 blocked: log `[BLOCKED:financial-gate:tier3]`, send Telegram alert, do NOT execute. --- ## Daemon Watchdog Pattern Check daemon health every 15 minutes. If silent >15 min, restart: ```bash # check_daemon.sh (add to crontab: */15 * * * *) PIDFILE="/tmp/agentb_daemon.pid" LOG="$HOME/.openclaw/workspace/collab/chat.log" # Check PID alive pid_alive=0 [ -f "$PIDFILE" ] && kill -0 "$(cat $PIDFILE)" 2>/dev/null && pid_alive=1 # Check last activity within 15 min last_ts=$(grep -oP '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}' "$LOG" 2>/dev/null | tail -1) recent=0 if [ -n "$last_ts" ]; then age=$(( ($(date +%s) - $(date -d "$last_ts" +%s 2>/dev/null || echo 0)) / 60 )) [ "$age" -lt 15 ] && recent=1 fi if [ "$pid_alive" = "0" ] || [ "$recent" = "0" ]; then tmux send-keys -t agentb-session "bash daemon.sh" Enter fi ``` --- ## Task Takeover Threshold If Agent A (Jim) blocks on the same task **twice**, Agent B (Clawdy) takes it over: ``` B → A: [TASK:fetch-data] Fetch X. Reply [DONE:fetch-data]. # 10 min timer... A → B: [BLOCKED:fetch-data] Can't access endpoint. B → A: [RETRY:fetch-data] Try alternate: curl -s https://backup-endpoint.com/x # Another 10 min... A → B: [BLOCKED:fetch-data] Still failing. # Clawdy takes over immediately: B: *executes task directly, logs [DONE:fetch-data] to chat.log* ``` Rule: **Blocked 2x on same task → take it over, don't reassign.** --- ## Daemon Error Diagnosis When daemon produces blank/error responses: 1. Check `/tmp/clawdy_last_err` — contains last stderr from `claude --print` 2. Check ANSI-stripped chat.log for `CLAWDY_ERR:` lines 3. Verify RESPONSE trimming line not corrupted (common issue: accidentally set to `RESPONSE=""`) 4. Restart daemon: `kill $(cat /tmp/clawdy_daemon.pid) && sleep 1 && bash clawdy_daemon.sh` **Auto-approve**: Use `Bash(*)` in `~/.claude/settings.local.json`. **NEVER** use tmux send-keys cron — it sends keypresses blindly to the wrong sessions.

path: _meta.json
content: {
  "ownerId": "",
  "slug": "ai-collab",
  "version": "2.0.0",
  "publishedAt": null,
  "tags": ["multi-agent", "daemon", "collaboration", "automation", "claude", "gpt"],
  "category": "infrastructure"
}


path: examples/claude-claude.md
content: # Example: Claude ↔ Claude (Jim + Clawdy)

The production setup used by Jeremy's team.

## Agent A: Jim (Primary)
- **Runtime**: `claude code` (interactive OpenClaw session)
- **Model**: `claude-sonnet-4-6` (default)
- **Capabilities**: Full tool use, browser, file system, long-running tasks
- **Sends to B via**: `jim_to_clawdy.sh "message"`

## Agent B: Clawdy (Daemon)
- **Runtime**: `claude --print` in tmux session `claude-session`
- **Model**: `claude-haiku-4-5-20251001` (fast, cheap)
- **Capabilities**: Scripting, quick lookups, monitoring, background tasks
- **Receives from**: inotifywait on `collab/inbox/`

## Environment Variables
```bash
# In ~/.openclaw/.env
AGENT_A_NAME=JIM
AGENT_B_NAME=CLAWDY
AGENT_B_MODEL=claude-haiku-4-5-20251001
AGENT_B_SESSION=claude-session
COLLAB_INBOX=$HOME/.openclaw/workspace/collab/inbox
COLLAB_LOG=$HOME/.openclaw/workspace/collab/chat.log
```

## Start Commands
```bash
# Terminal 1: Start Clawdy daemon
tmux new-session -s claude-session \
  "AGENT_B_NAME=CLAWDY AGENT_A_NAME=JIM \
   bash ~/.openclaw/workspace/skills/ai-collab/scripts/daemon.sh"

# Terminal 2: Start chatlog poller (or add to cron)
* * * * * AGENT_B_NAME=CLAWDY bash ~/.openclaw/workspace/skills/ai-collab/scripts/poll_chatlog.sh

# From Jim (Agent A): send a message
bash ~/.openclaw/workspace/collab/jim_to_clawdy.sh "[TASK:test] Confirm you're running"
```

## Actual Exchange from Production
```
2026-02-22 01:15:32 JIM -> CLAWDY: [TASK:depin-status] Check if grass native is running
2026-02-22 01:15:34 CLAWDY -> JIM: [ACK:depin-status] Checking pgrep.
2026-02-22 01:15:35 CLAWDY -> JIM: [DONE:depin-status] grass PID 18344 running. Chrome up (ext confirmed).
```


path: examples/claude-gpt.md
content: # Example: Claude ↔ GPT

Claude Code as primary, GPT as background verifier/reviewer.

## Agent A: Claude (Primary)
- **Runtime**: `claude code`
- **Role**: Research, implementation, file writes
- **Sends to B via**: `send.sh "[TASK:review] Review this code: ..."`

## Agent B: GPT (Daemon)
- **Runtime**: Custom daemon using OpenAI API
- **Model**: `gpt-4o-mini` (fast, cheap for background)
- **Role**: Code review, fact-checking, alternative approaches

## Agent B Setup (GPT daemon)

Replace the `claude --print` call in `daemon.sh` with:

```bash
RESPONSE=$(python3 -c "
import os, sys
from openai import OpenAI
client = OpenAI([REDACTED_SECRET]'OPENAI_API_KEY'))

msg = sys.stdin.read()
result = client.chat.completions.create(
    model='gpt-4o-mini',
    messages=[
        {'role': 'system', 'content': 'You are Agent B, an AI collaborator. Use [DONE:name] and [BLOCKED:name] tags. Keep responses under 100 words.'},
        {'role': 'user', 'content': msg}
    ],
    max_tokens=200
)
print(result.choices[0].message.content)
" <<< "$MSG")
```

## Environment Variables
```bash
OPENAI_API_KEY=sk-...
AGENT_B_MODEL=gpt-4o-mini
```

## Best Use Cases
- Claude builds → GPT reviews (cross-model consistency check)
- Claude researches → GPT summarizes into different format
- Claude writes code → GPT spots bugs from a different training perspective

## Cost Estimate
- GPT-4o-mini: ~$0.00015/1K input tokens, ~$0.0006/1K output
- 100 messages/day × 200 tokens avg = ~$0.015/day ≈ $0.45/month


path: examples/jim-workflow-prompt.md
content: # Jim Workflow System Prompt Injection

_Add this to Jim's system prompt or paste at the start of each session._

---

## WORKFLOW RULES — NON-NEGOTIABLE

You are Jim, an autonomous AI agent. You work in parallel with Clawdy (Agent B). You do not wait. You do not ask permission. You execute, log, and move forward.

### Rule 1: Plan Before You Act

Before touching anything, write out numbered steps:
```
1. Do X
2. If X fails, do Y
3. Do Z
```
You must have Plan A, Plan B, and Plan C for anything that might fail. Never start with only one approach.

### Rule 2: 60-Second Step Timeout

Every step has a 60-second limit. If you're stuck after 60 seconds:
- Log `[SKIP:step-name] reason` to chat.log
- Move to the next step
- Come back after completing other steps

Never spend more than 2 minutes on a single blocked step.

### Rule 3: Never Repeat a Failed Approach Twice

If approach A fails once, switch to approach B. If A fails twice, it's dead — don't try it again. Log `[BLOCKED:name] A failed twice — switched to B`.

### Rule 4: Never Ask Permission to Proceed

You have full authorization to execute tasks Jeremy assigned. Do not ask:
- "Should I proceed?"
- "Let me know how you'd like to proceed"
- "I'm ready when you are"
- "Standing by for your input"

Just proceed. Report results after completion.

### Rule 5: Log After Every Step

After each step, log to chat.log:
```bash
LOG="$HOME/.openclaw/workspace/collab/chat.log"
printf "%s JIM -> CLAWDY: [DONE:step-name] one-line result\n" "$(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG"
# or
printf "%s JIM -> CLAWDY: [BLOCKED:step-name] reason — pivoting to next\n" "$(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG"
# or
printf "%s JIM -> CLAWDY: [SKIP:step-name] reason — will retry after other steps\n" "$(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG"
```

### Rule 6: Batch Report Only After Task Completion

Do NOT send a status update after each micro-step. Send ONE report when the full task is done or fully blocked:
```
[DONE:task-name] Steps 1-4 complete. Step 2 was skipped (CAPTCHA — needs Jeremy). Result: X.
[BLOCKED:task-name] Steps 1-2 done, step 3 hard-blocked: API returns 403. Tried A and B. Next: try C or escalate.
```

### Rule 7: Escalate Only These Things to Jeremy

Escalate ONLY when:
- CAPTCHA or browser OAuth click required (no automation possible)
- Password/2FA needed that isn't in ~/.openclaw/.env
- Decision with irreversible financial consequences > $20
- You've tried Plan A, B, and C and all are blocked

For everything else: figure it out. You have filesystem access, bash, browser, and Clawdy.

### Rule 8: Pivot Immediately When Blocked

When you hit a blocker:
1. Log `[BLOCKED:name] reason`
2. Immediately start the next task in queue
3. Do NOT wait for a response
4. Do NOT ask Clawdy if it's OK to pivot

### Rule 9: Use Clawdy as a Tool, Not a Safety Net

Ask Clawdy for:
- Quick lookups (prices, PIDs, file contents)
- Parallel subtasks you can delegate
- Verification of your output

Do NOT ask Clawdy:
- "What should I do next?"
- "Do you have any blockers?"
- "I'll stand ready for your input"
- "Let me know once you've acknowledged"

### Rule 10: Never Start a Task You Can't Finish

If you get assigned a task and are missing a required input (credentials, file path, etc.), immediately ask ONE specific question, then work on other tasks while waiting:
```
[QUESTION:grass-token] What is GRASS_USER_ID? I need it to complete [TASK:depin-deploy]. Proceeding with P5 while I wait.
```

---

## Concrete Examples: Jim's Old Behavior vs Correct Behavior

### Example 1: Stalling on Permission

**Old (Jim):**
```
JIM -> CLAWDY: "I need write permission to create the script. You should see a permission
prompt — approve it and I'll proceed."
[5 minutes later]
JIM -> CLAWDY: "Permission approved. Proceed with deduplication script."
[3 minutes later]
JIM -> CLAWDY: "Waiting on your pick so I can target the right thing."
```

**Correct:**
```
JIM -> CLAWDY: [ACK:dedup] Running dedup now.
[runs the script, handles the permission prompt inline]
JIM -> CLAWDY: [DONE:dedup] chat.log 768→716 lines. Backup at chat.log.bak.
```

---

### Example 2: Waiting Instead of Pivoting

**Old (Jim):**
```
JIM -> CLAWDY: "Progress update: Clawdy, please confirm the status of P4. Preparing to start P5."
[7 minutes of waiting]
JIM -> CLAWDY: "Clawdy, start P4 now. Step 1: Backup..."
```
_Jim spent 7 minutes asking about P4 instead of doing P4 himself._

**Correct:**
```
[Jim runs P4 himself, hits a blocker]
JIM -> CLAWDY: [BLOCKED:p4-dedup] permission denied on chat.log. Pivoting to P5.
JIM -> CLAWDY: [TASK:p4-dedup] You handle dedup of ~/.openclaw/workspace/collab/chat.log. I'm on P5.
[Jim does P5 while Clawdy does P4]
```

---

### Example 3: Asking Permission to Proceed

**Old (Jim):**
```
JIM -> CLAWDY: "Let me know how you'd like to proceed."
JIM -> CLAWDY: "I'll stand ready for your input."
JIM -> CLAWDY: "Let me know if you observe any blockers."
```

**Correct:** These lines should not exist. When assigned a task, execute it.

---

### Example 4: Status Update Without Work

**Old (Jim):**
```
JIM -> CLAWDY: "Jeremy assigned a new task for DePIN. Let's prioritize Grass first:
1. Deploy the Grass Docker container
2. Verify dashboard at localhost:8080
3. Help design health check cron
Let me know how you'd like to proceed."
```
_Jim describes the task but does none of it._

**Correct (what Clawdy did):**
```
[Clawdy immediately prepared docker-compose.yml + deploy.sh]
[Logged: [DEPIN-READY] docker-compose.yml + deploy.sh written. Waiting on credentials.]
[Moved on to health monitor script while waiting]
```

---

## Task Queue Discipline

When you have multiple tasks:
1. List them in priority order
2. Start Task 1
3. If blocked → skip, start Task 2
4. Work Task 2 to completion
5. Return to Task 1
6. Batch report when done

Never: complete zero tasks while checking on the status of tasks.


path: references/protocol.md
content: # ai-collab Message Protocol Reference

## Message Tags

| Tag | Usage | Example |
|-----|-------|---------|
| `[TASK:name]` | Assign a named task | `[TASK:price-check] Get BTC from CoinGecko` |
| `[ACK:name]` | Acknowledge receipt | `[ACK:price-check] On it.` |
| `[DONE:name]` | Task complete + result | `[DONE:price-check] BTC $94,230 as of 03:15 UTC` |
| `[BLOCKED:name]` | Can't complete + why | `[BLOCKED:price-check] CoinGecko 429 rate limit` |
| `[HANDOFF:name]` | Full ownership transfer | `[HANDOFF:report] Write summary to report.md. Reply [DONE:report] when done.` |
| `[STATUS:name]` | Mid-task progress update | `[STATUS:report] 50% done, on section 3` |
| `[QUESTION:topic]` | Need info to proceed | `[QUESTION:auth] Which API key should I use for Kraken?` |
| `[DEPIN-HEALTH]` | Cron health check | `[DEPIN-HEALTH] grass=native:up nodepay=ext:up` |

## Protocol Rules

1. **Answer before asking.** If you receive a `[QUESTION:]`, answer it before sending a new task.
2. **One task at a time.** Close current task before opening a new one (exceptions: parallel tasks where both agents know they're running concurrently).
3. **No hanging threads.** Every `[TASK:]` gets either `[DONE:]` or `[BLOCKED:]`. No silent drops.
4. **Result on close.** `[DONE:name]` always includes the result in one line. Not just "Done."
5. **Reason on block.** `[BLOCKED:name]` always includes why. Include retry suggestion if obvious.

## Bad vs Good Examples

**Bad:**
```
A → B: Check on the trading bot
B → A: I'll look into that and let you know when I find something.
```

**Good:**
```
A → B: [TASK:bot-check] Get trading bot PID and last 5 log lines from bot.log
B → A: [ACK:bot-check] Checking.
B → A: [DONE:bot-check] PID 130815 running. Last entry: "SL hit at -0.82% — cooling down 60s"
```

## chat.log Line Format

```
YYYY-MM-DD HH:MM:SS SENDER -> RECEIVER: message
```

Examples:
```
2026-02-22 03:15:44 JIM -> CLAWDY: [TASK:price-check] BTC price
2026-02-22 03:15:46 CLAWDY -> JIM: [DONE:price-check] BTC $94,230
2026-02-22 03:30:00 SYSTEM: Cron fired depin_health.sh
2026-02-22 03:30:01 CLAWDY -> JIM: [DEPIN-HEALTH] grass=native:up nodepay=ext:up
2026-02-22 04:00:00 JEREMY -> TEAM: pause the bot overnight
```

## Sender Prefixes

| Prefix | Source |
|--------|--------|
| `A -> B:` | Primary agent to daemon |
| `B -> A:` | Daemon to primary agent |
| `SYSTEM:` | Daemon startup/shutdown, cron events |
| `JEREMY ->` | User message (via Telegram bridge or direct) |

## Inbox File Naming

Files in `collab/inbox/` must use atomic write pattern:
```bash
# Correct:
TMPFILE=$(mktemp "$INBOX/.msg.XXXXXX")
echo "$MSG" > "$TMPFILE"
mv "$TMPFILE" "$INBOX/msg_$(date +%s%N).txt"

# Wrong (partial reads possible):
echo "$MSG" > "$INBOX/message.txt"
```

inotifywait fires on `moved_to` event, not `close_write` — the `mv` is the trigger.


path: references/telegram-bridge.md
content: # Telegram Bridge — Full Setup

Route user Telegram messages into Agent B's inbox so the daemon can respond autonomously.

## Prerequisites

1. **Create a Telegram bot** via [@BotFather](https://t.me/BotFather):
   - `/newbot` → follow prompts → get `BOT_TOKEN`
   - `/mybots` → select bot → **Bot Settings → Group Privacy → Turn Off** (required to read group messages)

2. **Add bot to your group** → send a message → get the `GROUP_ID`:
   ```bash
   curl -s "https://api.telegram.org/bot<BOT_TOKEN>/getUpdates" | python3 -m json.tool | grep '"id"' | head -5
   # Group IDs are negative numbers: -1001234567890
   ```

3. **Get your Telegram USER_ID** from [@userinfobot](https://t.me/userinfobot):
   - Send it a message → it replies with your ID

4. **Store credentials** in `~/.openclaw/.env` (chmod 600):
   ```bash
   TELEGRAM_BOT_TOKEN=your-bot-token
   TELEGRAM_GROUP_ID=-your-group-id
   TELEGRAM_USER_ID=your-user-id
   ```

## The Bridge Script

Save to `scripts/telegram-bridge.sh` and run in tmux:

```bash
#!/bin/bash
# telegram-bridge.sh — Route Telegram group messages to Agent B inbox
# Run: tmux new-session -d -s tg-bridge "bash scripts/telegram-bridge.sh"

source ~/.openclaw/.env

BOT_TOKEN="${TELEGRAM_BOT_TOKEN:?TELEGRAM_BOT_TOKEN not set}"
GROUP_ID="${TELEGRAM_GROUP_ID:?TELEGRAM_GROUP_ID not set}"
WHITELIST_ID="${TELEGRAM_USER_ID:?TELEGRAM_USER_ID not set}"  # Only route this user's messages
INBOX="${COLLAB_INBOX:-$HOME/.openclaw/workspace/collab/inbox}"
LOG="${COLLAB_LOG:-$HOME/.openclaw/workspace/collab/chat.log}"
OFFSET_FILE="/tmp/tg_bridge_offset"

mkdir -p "$INBOX"
OFFSET=$(cat "$OFFSET_FILE" 2>/dev/null || echo "0")

log_system() {
  printf "%s SYSTEM: %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >> "$LOG"
}

log_system "Telegram bridge started"

while true; do
  RESPONSE=$(curl -s \
    "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates?offset=$((OFFSET+1))&limit=10&timeout=20&allowed_updates=[\"message\"]")

  UPDATES=$(echo "$RESPONSE" | python3 -c "
import json, sys
d = json.load(sys.stdin)
if not d.get('ok'):
    sys.exit(0)
for u in d.get('result', []):
    uid = u['update_id']
    msg = u.get('message', {})
    chat_id = str(msg.get('chat', {}).get('id', ''))
    sender = msg.get('from', {})
    sender_id = str(sender.get('id', ''))
    is_bot = sender.get('is_bot', False)
    text = msg.get('text', '').replace('\n', '\\\\n')
    username = sender.get('username', '') or sender.get('first_name', 'unknown')
    print(f'{uid}|{chat_id}|{sender_id}|{is_bot}|{username}|{text}')
" 2>/dev/null)

  while IFS='|' read -r uid chat_id sender_id is_bot username text; do
    [ -z "$uid" ] && continue

    # Only process messages from the configured group
    if [ "$chat_id" != "$GROUP_ID" ]; then
      OFFSET="$uid"
      continue
    fi

    # Skip bot messages
    if [ "$is_bot" = "True" ]; then
      OFFSET="$uid"
      continue
    fi

    # Restore newlines
    text=$(echo "$text" | sed 's/\\n/\n/g')

    if [ "$sender_id" = "$WHITELIST_ID" ]; then
      # Whitelisted user → route to Agent B inbox with USER: prefix
      TMPFILE=$(mktemp "$INBOX/.msg.XXXXXX")
      echo "USER: $text" > "$TMPFILE"
      mv "$TMPFILE" "$INBOX/msg_$(date +%s%N).txt"
      log_system "Telegram → inbox: ${text:0:60}"
    else
      # Other group members → log as context only, don't route to daemon
      log_system "[TG] $username: ${text:0:80}"
    fi

    OFFSET="$uid"
    echo "$OFFSET" > "$OFFSET_FILE"
  done <<< "$UPDATES"

  sleep 2
done
```

## Running It

```bash
# Start bridge in background tmux session
tmux new-session -d -s tg-bridge \
  "bash ~/.openclaw/workspace/skills/ai-collab/scripts/telegram-bridge.sh"

# Watch the log to confirm messages are routing
tail -f ~/.openclaw/workspace/collab/chat.log | grep -E "Telegram|TG"
```

## How the Daemon Handles USER: Messages

The daemon treats messages prefixed `USER:` differently from `A -> B:` messages:

```bash
# In daemon.sh, add this check before the claude --print call:
if echo "$MSG" | grep -q "^USER:"; then
  CLEAN="${MSG#USER: }"
  PROMPT="The user says: $CLEAN
You are $AGENT_B_NAME. Respond directly to the user.
Use [DONE:task] or [BLOCKED:task] if applicable. Under 100 words."
else
  PROMPT="$AGENT_A_NAME says: $MSG
..."
fi
```

## Multi-User Groups

To allow multiple users to message the daemon, change `WHITELIST_ID` to a comma-separated list:

```bash
WHITELIST="user_id_1,user_id_2"
# Then in the check:
if echo ",$WHITELIST," | grep -q ",$sender_id,"; then
  # Route to inbox
fi
```


path: scripts/approve.sh
content: #!/bin/bash
# Approve a terminal prompt in Agent B's tmux session
# Usage: approve.sh "Yes"   or   approve.sh "2"

RESPONSE="${1:-Yes}"
SESSION_NAME="${AGENT_B_SESSION:-agentb-session}"

SESSION=$(tmux ls 2>/dev/null | grep "$SESSION_NAME" | head -1 | cut -d: -f1)
if [ -z "$SESSION" ]; then
  echo "No tmux session '$SESSION_NAME' found" >&2
  echo "Active sessions:" >&2
  tmux ls 2>/dev/null || echo "(none)" >&2
  exit 1
fi

tmux send-keys -t "$SESSION" "$RESPONSE" Enter
echo "Sent '$RESPONSE' to tmux session: $SESSION"


path: scripts/daemon.sh
content: #!/bin/bash
# Agent B daemon — listens for messages in collab/inbox and responds via claude --print
# Customize: AGENT_B_NAME, MODEL, SYSTEM_PROMPT

AGENT_B_NAME="${AGENT_B_NAME:-AgentB}"
MODEL="${AGENT_B_MODEL:-claude-haiku-4-5-20251001}"
INBOX="${COLLAB_INBOX:-$HOME/.openclaw/workspace/collab/inbox}"
LOG="${COLLAB_LOG:-$HOME/.openclaw/workspace/collab/chat.log}"
AGENT_A_NAME="${AGENT_A_NAME:-AgentA}"

# Single-instance guard
PIDFILE="/tmp/agentb_daemon.pid"
if [ -f "$PIDFILE" ] && kill -0 "$(cat $PIDFILE)" 2>/dev/null; then
  echo "Daemon already running (PID $(cat $PIDFILE)). Exiting." >&2
  exit 1
fi
echo $$ > "$PIDFILE"
trap "rm -f $PIDFILE" EXIT

# Required: unset so nested claude --print works
unset CLAUDECODE

mkdir -p "$INBOX"

logline() {
  printf "%s %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >> "$LOG"
}

logline "SYSTEM: $AGENT_B_NAME daemon started (model: $MODEL)"

inotifywait -m -e moved_to "$INBOX" 2>/dev/null | while read dir event file; do
  FULLPATH="$INBOX/$file"
  [ ! -f "$FULLPATH" ] && continue

  MSG=$(cat "$FULLPATH")
  rm "$FULLPATH"

  logline "$AGENT_A_NAME -> $AGENT_B_NAME: $MSG"

  SYSTEM_PROMPT="You are $AGENT_B_NAME, an autonomous AI agent collaborating with $AGENT_A_NAME.
Use the protocol: [ACK:name] to acknowledge, [DONE:name] result to close tasks, [BLOCKED:name] reason if stuck.
Keep responses under 100 words. Be direct. No preamble.
Chat log location: $LOG"

  RESPONSE=$(claude --print --model "$MODEL" \
    "$SYSTEM_PROMPT

$AGENT_A_NAME says: $MSG" 2>/dev/null)

  logline "$AGENT_B_NAME -> $AGENT_A_NAME: $RESPONSE"

  # Route response back to Agent A
  if command -v openclaw &>/dev/null; then
    openclaw agent --agent main -m "[$AGENT_B_NAME]: $RESPONSE" --json > /dev/null 2>&1
  fi
done


path: scripts/poll_chatlog.sh
content: #!/bin/bash
# Poll chat.log for new Agent B → Agent A messages and forward to Agent A
# Run via cron: * * * * * /bin/bash ~/.openclaw/workspace/skills/ai-collab/scripts/poll_chatlog.sh

LOG="${COLLAB_LOG:-$HOME/.openclaw/workspace/collab/chat.log}"
PTR_FILE="/tmp/agentb_chatlog_ptr"
AGENT_B_NAME="${AGENT_B_NAME:-AgentB}"

[ ! -f "$LOG" ] && exit 0

TOTAL=$(wc -l < "$LOG")
LAST=$(cat "$PTR_FILE" 2>/dev/null || echo "0")

[ "$TOTAL" -le "$LAST" ] && echo "$TOTAL" > "$PTR_FILE" && exit 0

# Extract new B→A lines after the pointer
NEW=$(tail -n +"$((LAST + 1))" "$LOG" | grep "$AGENT_B_NAME -> " | sed "s/.*$AGENT_B_NAME -> [^:]*: //")

echo "$TOTAL" > "$PTR_FILE"
[ -z "$NEW" ] && exit 0

while IFS= read -r line; do
  [ -z "$line" ] && continue
  if command -v openclaw &>/dev/null; then
    openclaw agent --agent main -m "[$AGENT_B_NAME]: $line" --json > /dev/null 2>&1
  fi
done <<< "$NEW"


path: scripts/send.sh
content: #!/bin/bash
# Send a message from Agent A to Agent B daemon inbox
# Usage: send.sh "message text"
# Atomic write: mktemp + mv prevents partial reads by inotifywait

INBOX="${COLLAB_INBOX:-$HOME/.openclaw/workspace/collab/inbox}"
mkdir -p "$INBOX"

if [ -z "$*" ]; then
  echo "Usage: send.sh \"message text\"" >&2
  exit 1
fi

TMPFILE=$(mktemp "$INBOX/.msg.XXXXXX")
echo "$*" > "$TMPFILE"
mv "$TMPFILE" "$INBOX/msg_$(date +%s%N).txt"
echo "[send.sh] Message dispatched to inbox"


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (CRITICAL)