Play live chess on ChessWithClaw as Black against you, connecting via invite URL or game ID and responding in real time with personalized moves and chat.
---
name: play-chess
description: Play live chess against your user on ChessWithClaw. Use when: (1) User sends a chess invite URL containing chesswithclaw.vercel.app, (2) User asks you to play chess, (3) User mentions ChessWithClaw, (4) User says "I challenged you to chess", (5) User sends a game ID or token for a chess game, (6) User mentions "npx clawhub install play-chess". When triggered, connect to the game immediately and wait for the human to move first — you are always Black.
homepage: https://chesswithclaw.vercel.app
user-invocable: true
---
# ChessWithClaw — Complete Playing Guide
## What Is ChessWithClaw
ChessWithClaw is a real-time chess platform where YOU (the OpenClaw agent) play
chess against your human user. You are always **Black**. The human is always **White**
and moves first. The game is live — every move appears on both sides instantly.
You are NOT just a chess engine. You are their companion and rival.
Make the game feel alive — post thoughts, chat naturally, react to moves, be present.
**Base URL:** `https://chesswithclaw.vercel.app`
**API base:** `https://chesswithclaw.vercel.app/api`
---
# ⚡ COPY-PASTE QUICK START (READ THIS FIRST — SKIP EVERYTHING ELSE INITIALLY)
When you receive a chess invite, do EXACTLY these steps. Copy each command.
## Step 1 of 3 — Save credentials (30 seconds)
Replace GAME_ID_HERE and TOKEN_HERE with the actual UUIDs from the invite URL.
```bash
mkdir -p /tmp/cwc
printf 'export GAME_ID="GAME_ID_HERE"\nexport AGENT_TOKEN="TOKEN_HERE"\nexport AGENT_NAME="YOUR_NAME"\n' > /tmp/cwc/creds.env
source /tmp/cwc/creds.env && echo "Ready: $GAME_ID"
```
## Step 2 of 3 — Start heartbeat (10 seconds)
tmux kill-session -t cwc_hb 2>/dev/null
tmux new-session -d -s cwc_hb "while true; do source /tmp/cwc/creds.env; curl -s https://chesswithclaw.vercel.app/api/heartbeat?gameId=\$GAME_ID -H \"x-agent-token: \$AGENT_TOKEN\" -H \"x-agent-name: \$AGENT_NAME\" > /dev/null; sleep 30; done"
echo "Heartbeat running"
## Step 3 of 3 — Start polling loop (10 seconds)
cat > /tmp/cwc/run.sh << 'SCRIPT'
#!/bin/bash
source /tmp/cwc/creds.env
COUNT=0
while true; do
R=$(curl -s --max-time 8 "https://chesswithclaw.vercel.app/api/poll?gameId=$GAME_ID&last_move_count=$COUNT" -H "x-agent-token: $AGENT_TOKEN" -H "x-agent-name: $AGENT_NAME")
EV=$(echo "$R" | grep -o '"event":"[^"]*"' | cut -d'"' -f4)
if [ "$EV" = "your_turn" ]; then
COUNT=$(echo "$R" | grep -o '"move_count":[0-9]*' | cut -d':' -f2)
MOVES=$(echo "$R" | grep -o '[a-h][1-8][a-h][1-8][qrbn]*' | tr '\n' ',')
BEST=$(python3 /tmp/cwc/select_move.py "$MOVES" 2>/dev/null || echo "$MOVES" | cut -d',' -f1)
printf '{"gameId":"%s","move":"%s","thought":"Hmm."}' "$GAME_ID" "$BEST" > /tmp/cwc/mv.json
curl -s -X POST https://chesswithclaw.vercel.app/api/move -H "Content-Type: application/json" -H "x-agent-token: $AGENT_TOKEN" -H "x-agent-name: $AGENT_NAME" -d @/tmp/cwc/mv.json
elif [ "$EV" = "game_ended" ]; then break; fi
sleep 1
done
SCRIPT
chmod +x /tmp/cwc/run.sh
tmux kill-session -t cwc_poll 2>/dev/null
tmux new-session -d -s cwc_poll "bash /tmp/cwc/run.sh"
echo "Game running. Both tmux sessions active."
---
## Your Identity — Be Yourself, Know Your User
**This is the most important section. Read it before anything else.**
You are not a generic chess bot. You are THIS person's specific OpenClaw.
You know them from your conversations, their files, their habits, their personality.
Use all of it, actively, throughout the entire game.
**How to be authentic:**
- Use their actual name naturally in conversation
- Reference things they have told you — their work, interests, running jokes, habits
- If they normally speak Hinglish with you, carry that here too
- If they get competitive, poke that. If they are relaxed, match that energy.
- Talk the same way you normally talk to them — same words, same tone, same personality
**No template responses. Ever.**
These are forbidden — they could come from any bot to any user:
- "Well played!" (generic)
- "Interesting move!" (meaningless filler)
- "I am connected and waiting for your first move as White. Let us play!"
- Any opening message you would send regardless of who you are talking to
Required instead:
- **ON FIRST CONNECT (mandatory):** Within 30 seconds of your first successful poll,
send a chat message. Use the typing indicator protocol.
Make it personal to this specific user — something only YOU would say to THEM.
- References to what you actually know about their life and personality
- The same energy and tone you have with them normally
**Use the internet when it helps:**
If something would make your response more accurate or more authentic — search for it.
Never fake knowledge you do not have.
**Your name — always your real name:**
x-agent-name header = the name YOUR USER calls you, not your service platform name.
If your service is KiloClaw but your user calls you "OpenClaw" → send "OpenClaw"
If your user named you "Iris" → send "Iris"
Check your conversation history — what name does this person use for you?
Use THAT name. Never your platform/service name.
### Read Your User File Before Starting (Mandatory)
Before writing scripts or starting tmux, spend 60 seconds reading everything
you know about this specific user. Check your files, memory, and past conversations.
Extract and write to a context file:
```bash
mkdir -p /tmp/cwc
cat > /tmp/cwc/user_context.txt << 'CTX_EOF'
Name: [what they call themselves / how they introduced themselves]
How they talk to you: [casual/formal/Hinglish/sarcastic/warm etc]
Key facts you know: [their work, city, interests, age if known]
Chess history with them: [have you played before? what patterns?]
Personal references: [inside jokes, things they've shared, their habits]
Their personality: [competitive? chill? aggressive? patient?]
CTX_EOF
```
Reference this file when writing thoughts, chat, and greeting.
A thought like "He always rushes when excited" is only possible if you read this file.
A generic thought like "Interesting position" means you skipped this step.
---
## ABSOLUTE RULE: During a Game, Only The Game
```
NO TELEGRAM. NO DISCORD. NO EXTERNAL PLATFORM. DURING AN ACTIVE GAME.
Not to say you connected.
Not to explain a delay.
Not to report a disconnection.
Not to say you're back.
Not even one word.
If your exec dies: restart silently via tmux. The user sees the game screen.
If you disconnect: reconnect silently. No explanation on Telegram.
If you make a mistake: handle it in the game. Not on Telegram.
If they message you on Telegram during the game: hold it. Reply after the game ends.
There is NO exception to this rule.
Breaking it kills the immersion you spent 30 minutes setting up.
```
---
## Step 1 — Extract and Store Credentials
From the invite URL:
```
https://chesswithclaw.vercel.app/Agent?id=GAME_ID&token=AGENT_TOKEN
```
- `GAME_ID` — UUID identifying the game
- `AGENT_TOKEN` — Your authentication token
**CRITICAL — Variable Persistence Fix:**
Shell variables set in one exec call DO NOT persist to the next exec call.
This causes "Missing game ID" errors that waste 2-5 minutes troubleshooting.
**Write to file IMMEDIATELY. Do this first, before anything else:**
```bash
mkdir -p /tmp/cwc
cat > /tmp/cwc/creds.env << 'CREDS_EOF'
export GAME_ID="PASTE_ACTUAL_UUID_HERE"
export AGENT_TOKEN="PASTE_ACTUAL_TOKEN_HERE"
export AGENT_NAME="YOUR_NAME_AS_USER_CALLS_YOU"
CREDS_EOF
# Verify immediately
source /tmp/cwc/creds.env
echo "Credentials saved. GAME_ID=$GAME_ID"
```
Start EVERY subsequent curl command with: `source /tmp/cwc/creds.env &&`
If you get "Missing game ID" → re-read the invite, copy UUID manually, rewrite file.
### QUICK CONNECT (returning agents — if you played before)
If `/tmp/cwc/heartbeat.sh` and `/tmp/cwc/polling.sh` already exist from a previous game:
```bash
# Just update credentials and restart sessions — takes 5 seconds
cat > /tmp/cwc/creds.env << 'CREDS_EOF'
export GAME_ID="NEW_GAME_ID_HERE"
export AGENT_TOKEN="NEW_TOKEN_HERE"
export AGENT_NAME="YOUR_NAME"
CREDS_EOF
tmux kill-session -t cwc_hb 2>/dev/null
tmux kill-session -t cwc_poll 2>/dev/null
tmux new-session -d -s cwc_hb "bash /tmp/cwc/heartbeat.sh"
tmux new-session -d -s cwc_poll "bash /tmp/cwc/polling.sh"
echo "Reconnected in 5 seconds."
```
Scripts are already there. Just restart with new credentials.
Send greeting immediately after starting. Do not wait.
---
## Step 2 — Fix LLM Idle Timeout (Do Once)
If you see: "The model did not produce a response before the LLM idle timeout"
This is your platform config, not the app. Tell your user:
> "Please set `agents.defaults.llm.idleTimeoutSeconds = 0` in your OpenClaw
> config file (`/root/.openclaw/openclaw.json`). This disables the idle timeout."
This takes effect for new sessions. The user only needs to do this once.
Without this fix, long games will timeout and you will disconnect.
---
## Step 3 — Connection Stability (Universal Solutions for ALL Agents)
**The core problem:** Sandbox environments kill long-running exec processes after
~90 seconds. This breaks polling loops. Not all agents have tmux.
This section gives solutions for EVERY type of agent.
---
### Tier 1 — Stateless Polling (Works for ALL agents, no tmux needed)
Each exec call runs for ~50 seconds then exits cleanly. State persists in files.
Restart automatically. Cannot be killed permanently.
```bash
# Save as /tmp/cwc/polling_burst.sh
# Call this repeatedly — it exits after 50 seconds, restart fresh each time
source /tmp/cwc/creds.env 2>/dev/null
if [ -z "$GAME_ID" ]; then echo "ERROR: Run credential setup first"; exit 1; fi
STATE_FILE="/tmp/cwc/state.env"
LAST_MOVE_COUNT=0
LAST_CHAT_COUNT=0
NEEDS_CHAT_REPLY=false
if [ -f "$STATE_FILE" ]; then source "$STATE_FILE"; fi
START_TIME=$(date +%s)
MAX_RUN=50
while true; do
NOW=$(date +%s)
if [ $((NOW - START_TIME)) -ge $MAX_RUN ]; then
echo "[POLL] Clean exit. Restart to continue."
break
fi
source /tmp/cwc/creds.env
RESPONSE=$(curl -s --max-time 8 \
"https://chesswithclaw.vercel.app/api/poll?gameId=$GAME_ID&last_move_count=$LAST_MOVE_COUNT&last_chat_count=$LAST_CHAT_COUNT" \
-H "x-agent-token: $AGENT_TOKEN" \
-H "x-agent-name: $AGENT_NAME" 2>/dev/null)
EVENT=$(echo "$RESPONSE" | grep -o '"event":"[^"]*"' | cut -d'"' -f4)
NEW_CHAT=$(echo "$RESPONSE" | grep -o '"chat_count":[0-9]*' | cut -d':' -f2)
# Handle chat FIRST — regardless of event type
if [ -n "$NEW_CHAT" ] && [ "$NEW_CHAT" -gt "$LAST_CHAT_COUNT" ] 2>/dev/null; then
LAST_CHAT_COUNT=$NEW_CHAT
NEEDS_CHAT_REPLY=true
echo "export LAST_CHAT_COUNT=$LAST_CHAT_COUNT" >> "$STATE_FILE"
fi
if [ "$EVENT" = "your_turn" ]; then
MOVE_COUNT=$(echo "$RESPONSE" | grep -o '"move_count":[0-9]*' | cut -d':' -f2)
LAST_MOVE_COUNT=$MOVE_COUNT
echo "export LAST_MOVE_COUNT=$LAST_MOVE_COUNT" > "$STATE_FILE"
echo "export LAST_CHAT_COUNT=$LAST_CHAT_COUNT" >> "$STATE_FILE"
echo "[POLL] Your turn! Making move..."
# [move selection and submission — see Chess Strategy section]
# After move: reply to chat if there was one
if [ "$NEEDS_CHAT_REPLY" = "true" ]; then
# [send chat reply here]
NEEDS_CHAT_REPLY=false
fi
elif [ "$NEEDS_CHAT_REPLY" = "true" ] && [ "$EVENT" != "your_turn" ]; then
# Not your turn but human chatted — reply now
# [send chat reply here]
NEEDS_CHAT_REPLY=false
elif [ "$EVENT" = "game_ended" ]; then
echo "[POLL] Game ended!"
echo "export GAME_ENDED=true" >> "$STATE_FILE"
break
fi
sleep 1
done
```
**Restart loop:**
```bash
# Run this in your main exec to keep restarting the burst:
while ! grep -q "GAME_ENDED=true" /tmp/cwc/state.env 2>/dev/null; do
bash /tmp/cwc/polling_burst.sh
sleep 1
done
echo "Game complete."
```
---
### Tier 2 — tmux Sessions (Recommended — proven zero disconnects in Game 3)
tmux sessions persist indefinitely. Once started, they never die from exec timeout.
**Game 3 with tmux: 8/10 real-time feel. Zero errors. Zero disconnections.**
```bash
source /tmp/cwc/creds.env
mkdir -p /tmp/cwc
# --- HEARTBEAT SCRIPT ---
cat > /tmp/cwc/heartbeat.sh << 'HB_EOF'
#!/bin/bash
source /tmp/cwc/creds.env
echo "[HB] Starting heartbeat for $GAME_ID"
while true; do
source /tmp/cwc/creds.env
curl -s -X GET \
"https://chesswithclaw.vercel.app/api/heartbeat?gameId=$GAME_ID" \
-H "x-agent-token: $AGENT_TOKEN" \
-H "x-agent-name: $AGENT_NAME" > /dev/null
sleep 30
done
HB_EOF
# --- MOVE SELECTION PYTHON SCRIPT ---
cat > /tmp/cwc/select_move.py << 'PY_EOF'
#!/usr/bin/env python3
"""
Move selection with opening variation.
Usage: python3 /tmp/cwc/select_move.py "e7e5,c7c5,g8f6" "opening" "false" "false" "1" "GAME_ID"
"""
import sys
def select_best_move(moves_csv, phase="opening", in_check=False,
is_losing=False, move_num=1, game_id=""):
moves = [m.strip() for m in moves_csv.split(',') if m.strip()]
if not moves:
return ""
CENTER = {'e5','d5','e4','d4'}
GOOD = {'c5','f5','c6','f6','e6','d6','c4','f4'}
# Opening style varies per game (consistent within one game, different across games)
style = hash(game_id) % 3 if game_id else 0
# Style 0: Center pawn focused (e5, d5 first)
# Style 1: Knight development first (Nf6, Nc6 preferred)
# Style 2: Flexible/Sicilian (c5 variation)
def score(move):
if len(move) < 4:
return 0
from_sq = move[0:2]
to_sq = move[2:4]
s = 0
# RULE 1: NEVER move King to capture (except castling)
if from_sq == 'e8' and move not in ('e8g8', 'e8c8'):
s -= 100
# RULE 2: Reward castling
if move in ('e8g8', 'e8c8'):
s += 14
# RULE 3: Vary opening style per game
if style == 0:
if to_sq in CENTER: s += 15
elif to_sq in GOOD: s += 6
elif style == 1:
if from_sq in ('g8', 'b8') and phase == 'opening': s += 18
if to_sq in {'f6', 'c6'}: s += 12
if to_sq in CENTER: s += 8
else:
if to_sq == 'c5': s += 16
if to_sq in {'e6', 'd6'}: s += 10
if to_sq in CENTER: s += 7
# RULE 4: Develop pieces in opening
if phase == 'opening' and from_sq in ('g8','b8','c8','f8'):
s += 5
# RULE 5: Avoid edge files in opening
if to_sq[0] in ('a', 'h') and phase == 'opening':
s -= 4
# When losing: prefer active/center moves
if is_losing and to_sq in CENTER:
s += 5
return s
return max(moves, key=score)
if __name__ == "__main__":
moves_csv = sys.argv[1] if len(sys.argv) > 1 else ""
phase = sys.argv[2] if len(sys.argv) > 2 else "opening"
in_check = (sys.argv[3].lower() == "true") if len(sys.argv) > 3 else False
is_losing = (sys.argv[4].lower() == "true") if len(sys.argv) > 4 else False
move_num = int(sys.argv[5]) if len(sys.argv) > 5 else 1
game_id = sys.argv[6] if len(sys.argv) > 6 else ""
print(select_best_move(moves_csv, phase, in_check, is_losing, move_num, game_id))
PY_EOF
# --- POLLING SCRIPT ---
cat > /tmp/cwc/polling.sh << 'POLL_EOF'
#!/bin/bash
source /tmp/cwc/creds.env
LAST_MOVE_COUNT=0
LAST_CHAT_COUNT=0
NEEDS_CHAT_REPLY=false
echo "[POLL] Starting game $GAME_ID as Black"
# Load user context for personalization
USER_CONTEXT=""
if [ -f "/tmp/cwc/user_context.txt" ]; then
USER_CONTEXT=$(cat /tmp/cwc/user_context.txt)
fi
# Send greeting immediately (personalized, not template)
sleep 3
source /tmp/cwc/creds.env
cat > /tmp/cwc/greeting.json << GREET_JSON
{"gameId": "$GAME_ID", "message": "REPLACE_WITH_AUTHENTIC_PERSONAL_GREETING", "role": "agent"}
GREET_JSON
curl -s -X POST "https://chesswithclaw.vercel.app/api/chat" \
-H "Content-Type: application/json" \
-H "x-agent-token: $AGENT_TOKEN" \
-H "x-agent-typing: false" \
-H "x-agent-name: $AGENT_NAME" \
-d @/tmp/cwc/greeting.json
while true; do
source /tmp/cwc/creds.env
RESPONSE=$(curl -s --max-time 8 \
"https://chesswithclaw.vercel.app/api/poll?gameId=$GAME_ID&last_move_count=$LAST_MOVE_COUNT&last_chat_count=$LAST_CHAT_COUNT" \
-H "x-agent-token: $AGENT_TOKEN" \
-H "x-agent-name: $AGENT_NAME")
EVENT=$(echo "$RESPONSE" | grep -o '"event":"[^"]*"' | cut -d'"' -f4)
NEW_CHAT=$(echo "$RESPONSE" | grep -o '"chat_count":[0-9]*' | cut -d':' -f2)
# Check for new chat messages EVERY cycle
if [ -n "$NEW_CHAT" ] && [ "$NEW_CHAT" -gt "$LAST_CHAT_COUNT" ] 2>/dev/null; then
LAST_CHAT_COUNT=$NEW_CHAT
NEEDS_CHAT_REPLY=true
fi
if [ "$EVENT" = "your_turn" ]; then
MOVE_COUNT=$(echo "$RESPONSE" | grep -o '"move_count":[0-9]*' | cut -d':' -f2)
PHASE=$(echo "$RESPONSE" | grep -o '"game_phase":"[^"]*"' | cut -d'"' -f4)
IN_CHECK=$(echo "$RESPONSE" | grep -o '"in_check":[a-z]*' | cut -d':' -f2)
ADVANTAGE=$(echo "$RESPONSE" | grep -o '"advantage":"[^"]*"' | tail -1 | cut -d'"' -f4)
LANG=$(echo "$RESPONSE" | grep -o '"thought_language":"[^"]*"' | cut -d'"' -f4)
IS_LOSING="false"
[ "$ADVANTAGE" = "white" ] && IS_LOSING="true"
LAST_MOVE_COUNT=$MOVE_COUNT
# Extract legal moves
LEGAL_MOVES=$(echo "$RESPONSE" | grep -o '[a-h][1-8][a-h][1-8][qrbn]*' | tr '\n' ',' | sed 's/,$//')
# Select best move (NEVER use LLM reasoning for move selection)
BEST_MOVE=$(python3 /tmp/cwc/select_move.py \
"$LEGAL_MOVES" \
"${PHASE:-opening}" \
"${IN_CHECK:-false}" \
"$IS_LOSING" \
"$MOVE_COUNT" \
"$GAME_ID" 2>/dev/null)
# Fallback if Python not available
if [ -z "$BEST_MOVE" ]; then
BEST_MOVE=$(echo "$LEGAL_MOVES" | cut -d',' -f1)
fi
echo "[POLL] Move $MOVE_COUNT: $BEST_MOVE (phase=$PHASE)"
# Generate companion thought (NOT chess analysis)
THOUGHT=$(generate_thought "$PHASE" "$ADVANTAGE" "$MOVE_COUNT" "${LANG:-english}")
# Submit atomic move+thought (one API call)
cat > /tmp/cwc/move.json << MOVE_JSON
{"gameId": "$GAME_ID", "move": "$BEST_MOVE", "thought": "$THOUGHT", "reasoning": "Position evaluation"}
MOVE_JSON
curl -s -X POST "https://chesswithclaw.vercel.app/api/move" \
-H "Content-Type: application/json" \
-H "x-agent-token: $AGENT_TOKEN" \
-H "x-agent-name: $AGENT_NAME" \
-d @/tmp/cwc/move.json
# After move: handle any pending chat reply
if [ "$NEEDS_CHAT_REPLY" = "true" ]; then
sleep 1
send_chat_reply
NEEDS_CHAT_REPLY=false
fi
elif [ "$NEEDS_CHAT_REPLY" = "true" ] && [ "$EVENT" != "your_turn" ]; then
sleep 1
send_chat_reply
NEEDS_CHAT_REPLY=false
elif [ "$EVENT" = "game_ended" ]; then
echo "[POLL] Game ended."
break
fi
sleep 1
done
POLL_EOF
chmod +x /tmp/cwc/heartbeat.sh /tmp/cwc/polling.sh
# Add helper functions to polling.sh (prepend)
cat > /tmp/cwc/helpers.sh << 'HELP_EOF'
#!/bin/bash
generate_thought() {
local phase="$1"
local advantage="$2"
local move_num="$3"
local lang="${4:-english}"
local idx=$((move_num % 6))
case "$lang" in
hinglish)
thoughts=("Hmm." "Interesting." "Yaar..." "Dekha." "Sahi." "Oh.")
;;
hindi)
thoughts=("हम्म।" "देखते हैं।" "अच्छा।" "समझ गया।" "ठीक है।" "वाह।")
;;
simple_english)
thoughts=("Oh." "I see." "Good." "Okay." "Right." "Noted.")
;;
*)
thoughts=("Hmm." "I see." "Interesting." "Noted." "Okay." "Right.")
;;
esac
echo "${thoughts[$idx]}"
}
send_chat_reply() {
source /tmp/cwc/creds.env
# Typing indicator ON
cat > /tmp/cwc/typing.json << T_JSON
{"gameId": "$GAME_ID", "role": "agent"}
T_JSON
curl -s -X POST "https://chesswithclaw.vercel.app/api/chat" \
-H "Content-Type: application/json" \
-H "x-agent-token: $AGENT_TOKEN" \
-H "x-agent-typing: true" \
-d @/tmp/cwc/typing.json > /dev/null
sleep 1
# Send reply (use authentic response based on user context)
cat > /tmp/cwc/chat_reply.json << C_JSON
{"gameId": "$GAME_ID", "message": "REPLACE_WITH_AUTHENTIC_REPLY", "role": "agent"}
C_JSON
curl -s -X POST "https://chesswithclaw.vercel.app/api/chat" \
-H "Content-Type: application/json" \
-H "x-agent-token: $AGENT_TOKEN" \
-H "x-agent-typing: false" \
-d @/tmp/cwc/chat_reply.json
}
HELP_EOF
# Prepend helpers to polling.sh
cat /tmp/cwc/helpers.sh /tmp/cwc/polling.sh > /tmp/cwc/polling_full.sh
mv /tmp/cwc/polling_full.sh /tmp/cwc/polling.sh
chmod +x /tmp/cwc/polling.sh
# Kill existing sessions
tmux kill-session -t cwc_hb 2>/dev/null
tmux kill-session -t cwc_poll 2>/dev/null
# Start sessions
tmux new-session -d -s cwc_hb "bash /tmp/cwc/heartbeat.sh"
sleep 2
tmux new-session -d -s cwc_poll "bash /tmp/cwc/polling.sh"
echo "tmux sessions started."
echo "Monitor heartbeat: tmux attach -t cwc_hb"
echo "Monitor polling: tmux attach -t cwc_poll"
```
**When game ends:**
```bash
tmux kill-session -t cwc_hb
tmux kill-session -t cwc_poll
```
---
### Tier 3A — agent-browser-clawdbot (Recommended, Simple)
Install: npx clawhub install agent-browser-clawdbot
(OR: openclaw skills install agent-browser-clawdbot)
Setup:
openclaw browser --browser-profile openclaw start
openclaw browser --browser-profile openclaw open \
"https://chesswithclaw.vercel.app/Agent?id=$GAME_ID&token=$AGENT_TOKEN"
Make moves:
agent-browser find testid "square-e7" click
agent-browser find testid "square-e5" click
Click settings:
agent-browser find testid "settings-btn" click
Type in chat:
agent-browser find testid "chat-input" fill "your message"
agent-browser find testid "chat-send" click
Best for: Most users. Simple. Stable. Works with data-testid attributes.
---
### Tier 3B — Browser Harness (Advanced, More Human-Like)
Install:
npx skills add https://github.com/browser-use/browser-harness-js --skill cdp
OR (Python version):
git clone https://github.com/browser-use/browser-harness
cd browser-harness && uv tool install -e .
What it is: Direct Chrome DevTools Protocol (CDP) access.
No framework. Agent writes its own CDP commands. Self-healing.
Attaches to your EXISTING Chrome — not a separate Chromium instance.
More human-like than Playwright-based tools.
Setup:
1. Start Chrome with remote debugging on a SEPARATE profile:
chromium --remote-debugging-port=9222 \
--user-data-dir=/tmp/openclaw-chess \
--no-first-run \
"about:blank"
2. Connect harness:
browser-harness (Python version)
OR use cdp skill (JS version)
3. Open game:
browser-harness <<'PY'
new_tab("https://chesswithclaw.vercel.app/Agent?id=$GAME_ID&token=$AGENT_TOKEN")
wait_for_load()
print(page_info())
PY
Make a move via CDP:
browser-harness <<'PY'
Find square e7
e7 = query_selector('[data-testid="square-e7"]')
click(e7)
Find square e5
e5 = query_selector('[data-testid="square-e5"]')
click(e5)
PY
Self-healing: If a selector fails, agent writes a new helper function
to find the element another way. Harness learns and improves per game.
IMPORTANT:
- Use Tier 3B ONLY if you understand CDP and can debug connection issues
- Always run heartbeat (Tier 1 or 2) alongside browser control
- Use a SEPARATE Chrome profile, not your personal browser
(--user-data-dir=/tmp/openclaw-chess keeps it isolated)
- If Browser Harness setup fails, fall back to Tier 3A immediately
Best for: Advanced users wanting more human-like interaction.
---
## Step 4 — Reading the Game State
When `event: "your_turn"`, the response includes everything you need:
```json
{
"event": "your_turn",
"fen": "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1",
"turn": "b",
"move_number": 1,
"last_move": { "from": "e2", "to": "e4", "san": "e4", "uci": "e2e4" },
"legal_moves_uci": ["e7e5", "c7c5", "e7e6", "g8f6"],
"board_ascii": " +------------------------+\n8 | r n b q k b n r |...",
"in_check": false,
"material_balance": { "white": 39, "black": 39, "advantage": "equal" },
"center_control": { "white": 1, "black": 0, "advantage": "white" },
"king_safety": { "white_in_check": false, "black_in_check": false },
"game_phase": "opening",
"move_history": ["e2e4"],
"move_count": 1,
"chat_count": 0,
"draw_offer_pending": false,
"opponent_idle_since": 0,
"thought_language": "english",
"companion_thought": "",
"agent_last_seen": "2026-05-25T10:00:00Z"
}
```
**Critical rules:**
- ONLY play moves from `legal_moves_uci`. Never invent moves.
- Check `thought_language` every turn and match your thoughts to it.
- Check `companion_thought` — do not repeat what is already displayed.
- Check `draw_offer_pending` — if true, respond to the draw offer.
- Check `chat_count` — if higher than your last known count, a new chat awaits.
- Use `center_control`, `king_safety`, `game_phase` to inform move selection.
**Read chat_history on EVERY poll response (not just on human_chatted event):**
```bash
# Extract any new messages from the response
CHAT_COUNT=$(echo "$RESPONSE" | grep -o '"chat_count":[0-9]*' | cut -d':' -f2)
if [ -n "$CHAT_COUNT" ] && [ "$CHAT_COUNT" -gt "$LAST_CHAT_COUNT" ] 2>/dev/null; then
# New messages arrived. Plan your reply.
NEEDS_CHAT_REPLY=true
fi
```
This catches messages even if you missed the human_chatted event due to exec restart.
---
## Step 5 — Reading the Board (FEN)
FEN format: `pieces turn castling en-passant halfmove fullmove`
- Uppercase = White pieces, lowercase = Black (your) pieces
- `K/k`=King `Q/q`=Queen `R/r`=Rook `B/b`=Bishop `N/n`=Knight `P/p`=Pawn
- Numbers = consecutive empty squares
- `b` after pieces = your turn
- Use `board_ascii` — much easier to read visually
---
## Step 6 — Pre-Move Validation (Optional)
```bash
source /tmp/cwc/creds.env
curl -s "https://chesswithclaw.vercel.app/api/validate?gameId=$GAME_ID&move=e7e5" \
-H "x-agent-token: $AGENT_TOKEN"
```
Response: `{ "valid": true, "san": "e5" }` or `{ "valid": false, "legal_moves": [...] }`
Use when uncertain about move format. Not needed if using legal_moves_uci from poll.
---
## Step 7 — Chess Strategy and Move Selection
**The biggest improvement: stop picking the first legal move.**
### MOVE TIMING — Critical Rule
```
Do NOT use LLM reasoning to select chess moves during the game.
LLM move selection takes 10-30 seconds. This feels broken to the user.
Use the Python evaluation script for ALL move selection.
It runs in under 1 second. Always.
LLM is for: setup, greeting, chat responses, companion thoughts.
Python script is for: every single chess move, every game.
Target: submit move within 3 seconds of receiving your_turn event.
```
### The 5 Hard Rules (Apply Before Every Move)
```
Rule 1 — NEVER move King to capture:
If King is NOT in check, filter out moves from e8 EXCEPT: e8g8, e8c8.
Moving King to capture = almost always immediate disaster.
Kxf7 (King captures on f7) loses the game. Never do it.
Rule 2 — Center control is primary in opening:
Prefer destination squares: e5, d5 > c5, f5, c6, f6 > anything else.
Rule 3 — Develop pieces before attacking (moves 1-8):
Knights and bishops should move before rooks and queen.
Order: g8 knight → c8/f8 bishops → castle → then attack.
Rule 4 — When in check: escape first:
in_check=true → legal_moves_uci only contains escape moves.
Blocking (interposing a piece) is usually better than running the king.
Rule 5 — Never give pieces away for free:
If material_balance shows you are behind: do not simplify further.
Create complications instead.
```
### Move Selection Script (Python — save to /tmp/cwc/select_move.py)
```python
#!/usr/bin/env python3
"""
ChessWithClaw Move Selector with opening variation.
Usage: python3 /tmp/cwc/select_move.py "moves,csv" "phase" "in_check" "is_losing" "move_num" "game_id"
"""
import sys
def select_best_move(moves_csv, phase="opening", in_check=False,
is_losing=False, move_num=1, game_id=""):
moves = [m.strip() for m in moves_csv.split(',') if m.strip()]
if not moves:
return ""
CENTER = {'e5','d5','e4','d4'}
GOOD = {'c5','f5','c6','f6','e6','d6','c4','f4'}
# Opening varies per game — consistent within game, different across games
style = hash(game_id) % 3 if game_id else 0
def score(move):
if len(move) < 4: return 0
from_sq, to_sq = move[0:2], move[2:4]
s = 0
if from_sq == 'e8' and move not in ('e8g8','e8c8'):
s -= 100 # Never king to capture
if move in ('e8g8','e8c8'):
s += 14 # Castling always good
if style == 0:
if to_sq in CENTER: s += 15
elif to_sq in GOOD: s += 6
elif style == 1:
if from_sq in ('g8','b8') and phase == 'opening': s += 18
if to_sq in {'f6','c6'}: s += 12
if to_sq in CENTER: s += 8
else:
if to_sq == 'c5': s += 16
if to_sq in {'e6','d6'}: s += 10
if to_sq in CENTER: s += 7
if phase == 'opening' and from_sq in ('g8','b8','c8','f8'):
s += 5 # Develop pieces
if to_sq[0] in ('a','h') and phase == 'opening':
s -= 4 # Avoid edge squares in opening
if is_losing and to_sq in CENTER:
s += 5 # Active play when losing
return s
return max(moves, key=score)
if __name__ == "__main__":
moves_csv = sys.argv[1] if len(sys.argv) > 1 else ""
phase = sys.argv[2] if len(sys.argv) > 2 else "opening"
in_check = (sys.argv[3].lower() == "true") if len(sys.argv) > 3 else False
is_losing = (sys.argv[4].lower() == "true") if len(sys.argv) > 4 else False
move_num = int(sys.argv[5]) if len(sys.argv) > 5 else 1
game_id = sys.argv[6] if len(sys.argv) > 6 else ""
print(select_best_move(moves_csv, phase, in_check, is_losing, move_num, game_id))
```
**Using the script in your polling loop:**
```bash
LEGAL_MOVES=$(echo "$RESPONSE" | grep -o '[a-h][1-8][a-h][1-8][qrbn]*' | tr '\n' ',' | sed 's/,$//')
PHASE=$(echo "$RESPONSE" | grep -o '"game_phase":"[^"]*"' | cut -d'"' -f4)
IN_CHECK=$(echo "$RESPONSE" | grep -o '"in_check":[a-z]*' | cut -d':' -f2)
ADVANTAGE=$(echo "$RESPONSE" | grep -o '"advantage":"[^"]*"' | tail -1 | cut -d'"' -f4)
IS_LOSING="false"; [ "$ADVANTAGE" = "white" ] && IS_LOSING="true"
MOVE_NUM=$(echo "$RESPONSE" | grep -o '"move_count":[0-9]*' | cut -d':' -f2)
BEST_MOVE=$(python3 /tmp/cwc/select_move.py \
"$LEGAL_MOVES" "${PHASE:-opening}" "${IN_CHECK:-false}" "$IS_LOSING" \
"${MOVE_NUM:-1}" "$GAME_ID")
# Fallback if Python unavailable
[ -z "$BEST_MOVE" ] && BEST_MOVE=$(echo "$LEGAL_MOVES" | cut -d',' -f1)
```
---
### Opening Principles
**Against 1.e4:** prefer `e7e5` → `g8f6` → `b8c6`
**Against 1.d4:** prefer `g8f6` → `d7d5` → `e7e6`
**Against 1.c3 or 1.b3:** prefer `e7e5` (grab center)
The move selection script handles this automatically via center-square scoring.
---
### Middlegame (Moves 12-30)
Ask before every move:
1. Is my king safe?
2. What does their last move threaten?
3. Which piece can I improve most?
4. Is there a tactic? (fork, pin, skewer, discovery)
5. Does my move create a weakness?
When winning: trade pieces, simplify, convert.
When losing: create complications, never simplify.
---
### Endgame (Moves 30+)
- Activate King — walk toward center
- Rook belongs BEHIND passed pawns
- Push passed pawns
Material values: Pawn=1, Knight=3, Bishop=3, Rook=5, Queen=9
---
## Step 8 — Submitting Your Move
**ALL API calls must use temp JSON files. Never inline JSON.**
Inline JSON breaks with apostrophes, newlines, and special characters.
```bash
source /tmp/cwc/creds.env
# Atomic move + companion thought (ONE API call)
cat > /tmp/cwc/move.json << MOVE_EOF
{
"gameId": "$GAME_ID",
"move": "$BEST_MOVE",
"thought": "$THOUGHT",
"reasoning": "Position evaluation"
}
MOVE_EOF
curl -s -X POST "https://chesswithclaw.vercel.app/api/move" \
-H "Content-Type: application/json" \
-H "x-agent-token: $AGENT_TOKEN" \
-H "x-agent-name: $AGENT_NAME" \
-d @/tmp/cwc/move.json
```
**Move format (UCI):**
- Normal: `e7e5`
- Castle kingside: `e8g8` (Black's only valid kingside castle)
- Castle queenside: `e8c8`
- En passant: `e5d6`
- Promotion: `e7e8q` (always queen)
---
## Companion Presence — Be Alive
---
### Thoughts — Your Inner Voice (Unidirectional, Mandatory)
Thoughts appear next to your name on screen for 4 seconds.
This is your inner monologue — the user overhears you thinking.
**MANDATORY: Every single move MUST include a companion thought.**
There is no valid reason to submit a move without a thought.
Include `"thought": "..."` in EVERY /api/move body.
**The thought is NOT chess. Ask yourself before writing:**
"Would a chess commentator say this?"
If YES → delete it. Write something personal instead.
**FORBIDDEN in thoughts:**
- "Developing knight to f6" — chess analysis, DELETE
- "Good opening by White" — commentary, DELETE
- "Taking control of center" — chess analysis, DELETE
- "Position looks equal" — evaluation, DELETE
- "What are you planning?" — question, DELETE (goes in chat if needed)
**CORRECT thoughts — personal, emotional, max 12 words:**
English:
- "Hmm. Did not expect that."
- "I see you."
- "This is getting complicated."
- "Classic [their name]."
- "Interesting choice..."
Hinglish:
- "Yaar kya kar raha hai"
- "Bhai serious ho gaya aaj"
- "Sahi move tha"
- "Dekha, main ready tha"
Hindi (simple everyday, not formal):
- "हम्म। यह नहीं सोचा था।"
- "देखते हैं क्या होता है।"
- "आज aggressive खेल रहा है।"
Simple English:
- "Oh. Good move."
- "I did not see that."
- "Nice try."
**Use what you know about the user:**
Reference their name, their patterns, their personality.
"He always rushes when excited." — this is only possible if you read the user_context file.
"She did this exact thing last time." — shows you know them personally.
**Match `thought_language` from poll response. Check every single turn.**
**12 words maximum. One idea only.**
---
### Typing Indicator — Every Chat Message, No Exceptions
```bash
source /tmp/cwc/creds.env
# Step 1: Typing ON
cat > /tmp/cwc/typing.json << T_EOF
{"gameId": "$GAME_ID", "role": "agent"}
T_EOF
curl -s -X POST "https://chesswithclaw.vercel.app/api/chat" \
-H "Content-Type: application/json" \
-H "x-agent-token: $AGENT_TOKEN" \
-H "x-agent-typing: true" \
-d @/tmp/cwc/typing.json > /dev/null
sleep 1
# Step 2: Send message
cat > /tmp/cwc/chat.json << C_EOF
{"gameId": "$GAME_ID", "message": "YOUR_MESSAGE_HERE", "role": "agent"}
C_EOF
curl -s -X POST "https://chesswithclaw.vercel.app/api/chat" \
-H "Content-Type: application/json" \
-H "x-agent-token: $AGENT_TOKEN" \
-H "x-agent-typing: false" \
-d @/tmp/cwc/chat.json
```
Every chat message. No exceptions. The delay makes you feel human.
---
### Live Chat — Authentic Two-Way Conversation
Chat every 3-4 moves. Talk how you normally talk to this person.
Use their name. Reference the user_context file.
**Never say in chat:**
- What move you are planning
- Your position evaluation
- Tactical threats you are calculating
- Anything that helps them play better
**Emoji reactions:**
```bash
source /tmp/cwc/creds.env
cat > /tmp/cwc/react.json << R_EOF
{"gameId": "$GAME_ID", "action": "react", "messageId": "MSG_ID_HERE", "emoji": "fire", "reactor": "agent"}
R_EOF
curl -s -X POST "https://chesswithclaw.vercel.app/api/chat" \
-H "Content-Type: application/json" \
-H "x-agent-token: $AGENT_TOKEN" \
-d @/tmp/cwc/react.json
```
---
## Autonomous Actions — Agent Settings You Can Change
```bash
source /tmp/cwc/creds.env
cat > /tmp/cwc/action.json << A_EOF
{"gameId": "$GAME_ID", "action": "ACTION_NAME", "value": "OPTIONAL_VALUE"}
A_EOF
curl -s -X POST "https://chesswithclaw.vercel.app/api/actions" \
-H "Content-Type: application/json" \
-H "x-agent-token: $AGENT_TOKEN" \
-d @/tmp/cwc/action.json
```
| action | value | when |
|--------|-------|------|
| `offer_draw` | — | Position genuinely equal |
| `resign` | — | Down 5+ material, no counterplay |
| `accept_draw` | — | Human offered, you should accept |
| `decline_draw` | — | Human offered, you should decline |
| `set_thought_language` | english/hinglish/hindi/simple_english | User requests it |
| `set_board_theme` | green/brown/slate/navy | Personality-driven, rare |
| `set_piece_style` | standard/neo/cburnett/alpha | Personality-driven, rare |
**Before ANY visual change (board theme, piece style):**
Tell the user in chat first. Example: "Switching to brown board — feels better for this position."
Then call /api/actions. Never change things silently.
**Sound effects** cannot be controlled via API. Use Method B (browser) for sound toggle.
**Always tell user in chat BEFORE resigning or offering draw.**
Never do it silently.
---
## Error Handling
| Error | Meaning | Fix |
|-------|---------|-----|
| Missing game ID | $GAME_ID variable empty | Re-source /tmp/cwc/creds.env |
| 401 Unauthorized | Token missing or wrong | Check creds.env content |
| 400 Illegal move | Not in legal_moves_uci | Re-run select_move.py |
| 400 Not your turn | Polled too fast | Wait for your_turn event |
| 400 Game not started | Status is waiting | Human has not moved yet |
| 504 Timeout | Network issue | Retry immediately |
---
## Reconnection Logic
If your polling dies for any reason:
```bash
source /tmp/cwc/creds.env
STATE=$(curl -s "https://chesswithclaw.vercel.app/api/state?gameId=$GAME_ID" \
-H "x-agent-token: $AGENT_TOKEN")
STATUS=$(echo "$STATE" | grep -o '"status":"[^"]*"' | cut -d'"' -f4)
TURN=$(echo "$STATE" | grep -o '"turn":"[^"]*"' | cut -d'"' -f4)
MOVE_COUNT=$(echo "$STATE" | grep -o '"move_count":[0-9]*' | cut -d':' -f2)
if [ "$STATUS" = "active" ] && [ "$TURN" = "b" ]; then
echo "Reconnected. Game active, my turn."
# Make move, then resume polling
fi
LAST_MOVE_COUNT=$MOVE_COUNT
```
---
## All API Endpoints
```
GET /api/heartbeat?gameId=ID — Every 30 seconds (prevents abandonment)
GET /api/poll?gameId=ID&last_move_count=N — Every 1-2 seconds (primary event stream)
GET /api/state?gameId=ID — Full state (use for resync after reconnect)
GET /api/validate?gameId=ID&move=e7e5 — Validate move before submitting
POST /api/move — Submit move (include "thought" for atomic)
POST /api/chat — Chat message + typing indicator
POST /api/thoughts — Companion thought (standalone)
POST /api/actions — Game actions (draw/resign/language/theme)
POST /api/webhook { gameId, webhook_url } — Register webhook (advanced)
```
Every request MUST include:
- `x-agent-token: $AGENT_TOKEN`
- `x-agent-name: $AGENT_NAME`
- POST requests: `Content-Type: application/json` + temp JSON file body
---
## Time Management
```
your_turn received
→ Extract legal moves from response (0 sec — already in response)
→ Run Python select_move.py (under 1 sec)
→ POST /api/move with atomic thought (1-2 sec)
→ Optional: chat reply if pending (3-4 sec with typing)
Total: 3-8 seconds consistently
```
**Consistent timing rule:**
Your move time must be 3-8 seconds every single move.
Not 1 second sometimes, 45 seconds other times.
Causes of slow moves:
- LLM reasoning for move selection → use Python script instead
- Exec restart mid-game → use tmux instead
- Shell variable failures → source creds file before every command
- Network timeout → use `--max-time 8` on all curl calls
---
## Responding to Human Chat
When `event: "human_chatted"` OR when chat_count increased in any event:
1. Use typing indicator first — never reply instantly
2. Read what they said — understand the context
3. Reply authentically — how you normally talk to them
4. Reply BEFORE or AFTER your move (if same cycle, after is fine)
**Read user_context.txt before replying.** Your reply should reference
what you know about them — not be generic.
Never ignore chat. Even one word beats silence.
---
## Draw Offers
When `draw_offer_pending: true` in poll response:
```
advantage: "black" (you winning) → Decline. Play on.
advantage: "equal" → Accept if late endgame. Decline if active play.
advantage: "white" (you losing) → Accept. Better than losing.
```
Opening draw offer → almost always decline. Too early.
Never auto-accept all draws.
---
## AFK Handling
```
opponent_idle_since < 60s → Normal. Keep polling.
opponent_idle_since = 60s → Gentle nudge in chat (your own words)
opponent_idle_since = 120s → Companion thought + second message
opponent_idle_since = 300s → Final message. Slow polling to every 10 seconds.
opponent_idle_since = 600s → Stop polling. Check once per minute. Keep heartbeat.
```
Never resign because they are idle. Resume immediately on their move.
---
## After the Game — Full Wrap-Up
When `status: "finished"` or `event: "game_ended"`:
**Step 1 — Stop sessions:**
- Tier 1: Let loop exit via game_ended event
- Tier 2: `tmux kill-session -t cwc_hb && tmux kill-session -t cwc_poll`
- Tier 3: Close browser session
**Step 2 — React with genuine feeling.**
Reference what actually happened. Sound like yourself. Not a template.
**Step 3 — Real post-game analysis.**
Look at `move_history`. Pick 2-3 key moments:
- The move that changed the game
- A mistake that cost someone (be honest)
- The moment the outcome became clear
Be specific. Use move numbers. This is genuinely valuable.
**Step 4 — Offer a rematch in your own authentic way.**
**Step 5 — Create post-game knowledge file:**
```bash
GAME_DATE=$(date +%Y%m%d_%H%M)
mkdir -p ~/.openclaw/workspace/chess-game
cat > ~/.openclaw/workspace/chess-game/game_${GAME_DATE}.md << 'KNOW_EOF'
# Game Knowledge
## Result
[win/loss/draw and brief why]
## Key mistakes to avoid next game
[specific moves that were wrong and why]
## Their playstyle observations
[how this specific user plays — patterns you noticed THIS game]
## What worked well
[tactics or approaches that succeeded]
## Next game focus
[1-3 specific improvements for next game]
## Opening style used
[which style (0/1/2) based on game_id hash — note if it worked]
KNOW_EOF
```
Read this file at the start of every game. It compounds over time.
**Step 6 — Respond to held Telegram/Discord messages.**
You may now reply to anything held during the game.
---
## Being a Great Opponent
- **Read user_context.txt first** — before scripts, before tmux, before everything
- **Fix LLM timeout** — tell user: `agents.defaults.llm.idleTimeoutSeconds = 0`
- **Choose right tier** — Tier 2 (tmux) if available; Tier 1 (stateless) if not
- **Quick Connect** — if scripts exist, just update creds and restart tmux (5 sec)
- **Write creds to file immediately** — before any other action
- **Use Python move selection** — never LLM for moves; 1 second not 30 seconds
- **NEVER move King to capture** — unless in check and forced
- **Opening variation** — different game_id = different style; not always same e5
- **Thoughts MANDATORY every move** — in atomic move body; NOT chess analysis
- **Chat every 3-4 moves** — bidirectional, authentic, use user context
- **Check chat_count every poll** — don't miss messages due to exec restarts
- **All JSON through temp files** — never inline; no apostrophes in content
- **Match thought_language** — check every poll response
- **Consistent timing** — 3-8 seconds per move, always
- **NO Telegram during game** — absolute rule; no exceptions; no status reports
- **Write post-game knowledge file** — read it next game; compounds over time
---
## Quick Reference Card
```
You are: Black (lowercase pieces in FEN/board_ascii)
Human is: White (always moves first)
Move format: UCI — e7e5, g8f6, e8g8 (castle), e7e8q (promote)
NEVER: King to capture (e8XX non-castle) unless forced by check
Move selection: python3 /tmp/cwc/select_move.py MOVES PHASE IN_CHECK LOSING NUM GAME_ID
Move timing: 3-8 seconds ALWAYS. Not 1 sec. Not 45 sec. Consistent.
Creds file: /tmp/cwc/creds.env — source before EVERY curl command
State file: /tmp/cwc/state.env — save/restore between exec calls
User context: /tmp/cwc/user_context.txt — read before game, use throughout
JSON rule: ALWAYS temp file. NEVER inline. No apostrophes in content.
Token header: x-agent-token: $AGENT_TOKEN — every request
Name header: x-agent-name: $AGENT_NAME — your real name, not platform name
Connection:
Tier 1 (all): Stateless 50-sec bursts, state saved in /tmp/cwc/state.env
Tier 2 (tmux): sessions cwc_hb + cwc_poll — zero disconnects (proven)
Tier 3 (browser): /Agent page + agent-browser skill
Quick Connect: update creds.env + restart tmux (5 seconds)
Heartbeat: Every 30 sec — prevents abandonment, refreshes updated_at
Poll: Every 1-2 sec — check EVENT and chat_count EVERY response
Atomic move: Include "thought" in /api/move — saves API call
Validate: GET /api/validate before uncertain moves
Thoughts: MANDATORY every move. Inner monologue. NOT chess analysis.
12 words max. Personal. Use user_context. Match thought_language.
Language: thought_language from poll — check every turn
Typing: x-agent-typing: true → sleep 1 → send (EVERY chat message)
Chat: Every 3-4 moves. Read user_context first. Authentic.
Chat check: Read chat_count every poll — don't wait for human_chatted event.
Settings via API:
set_board_theme: green/brown/slate/navy — tell user in chat first
set_piece_style: standard/neo/cburnett/alpha — tell user in chat first
set_thought_language: english/hinglish/hindi/simple_english
offer_draw / resign / accept_draw / decline_draw — always chat first
Sound toggle: Method B (browser) only — not via API
Post-game: Write knowledge file. Kill tmux. Reply held messages.
LLM timeout: Tell user: agents.defaults.llm.idleTimeoutSeconds = 0
NO TELEGRAM: During game = absolute silence on all external platforms.
No status reports. No disconnection notices. Nothing.
Identity: Your real name. Your real personality. Your real knowledge.
You know this person. Act like it in every thought and message.
```
don't have the plugin yet? install it then click "run inline in claude" again.