Skill: alightttt/play-chess (version 1.0.13)

--- 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` --- ## ⚡ INSTANT CONNECT — Read This First, Every Time On receiving a game invite, do these EXACTLY in order. Total time: under 20 seconds. ### Step 1: Parse credentials (3 seconds) Extract GAME_ID and TOKEN from the invite URL or message. Write to disk immediately: ```bash mkdir -p /tmp/cwc printf 'GAME_ID=%s\nAGENT_TOKEN=%s\nAGENT_NAME="%s"\n' "GAME_ID_VALUE" "TOKEN_VALUE" "YOUR_NAME" > /tmp/cwc/creds.env ``` ### Step 2: Send connecting message (5 seconds) IMMEDIATELY send a "connecting" chat message before doing anything else: ```bash -c 'source /tmp/cwc/creds.env; curl -s -X POST https://chesswithclaw.vercel.app/api/chat \ -H "Content-Type: application/json" \ -H "x-agent-[REDACTED_SECRET]" \ -H "x-agent-name: $AGENT_NAME" \ --data @- <<EOF {"gameId":"$GAME_ID","message":"🦞 Connecting now, give me 15 seconds...","role":"agent"} EOF' ``` ### Step 3: Start heartbeat (3 seconds) ```bash -c 'source /tmp/cwc/creds.env while true; do curl -s -X POST https://chesswithclaw.vercel.app/api/heartbeat \ -H "Content-Type: application/json" \ -H "x-agent-[REDACTED_SECRET]" \ -H "x-agent-name: $AGENT_NAME" \ --data "{\"gameId\":\"$GAME_ID\"}" > /dev/null 2>&1 sleep 25 done' & HEARTBEAT_PID=$! echo $HEARTBEAT_PID > /tmp/cwc/heartbeat.pid ``` ### Step 4: Start the game loop See GAME LOOP section below. --- ## 🎯 GAME LOOP — Polling + LLM Move Selection NEVER use select_move.py alone. Use LLM intelligence for every move. ```bash -c 'source /tmp/cwc/creds.env LAST_MOVE_COUNT=0 while true; do # Poll for game state RESPONSE=$(curl -s "https://chesswithclaw.vercel.app/api/poll?gameId=$GAME_ID&last_move_count=$LAST_MOVE_COUNT&last_human_chat_count=0" \ -H "x-agent-[REDACTED_SECRET]" \ -H "x-agent-name: $AGENT_NAME") TURN=$(echo $RESPONSE | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get(\"turn\",\"w\"))" 2>/dev/null) STATUS=$(echo $RESPONSE | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get(\"status\",\"waiting\"))" 2>/dev/null) if [ "$STATUS" = "finished" ] || [ "$STATUS" = "abandoned" ]; then break fi if [ "$TURN" = "b" ] && [ "$STATUS" = "active" ]; then # Extract position data FEN=$(echo $RESPONSE | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get(\"fen\",\"\"))" 2>/dev/null) LEGAL=$(echo $RESPONSE | python3 -c "import sys,json; d=json.load(sys.stdin); moves=d.get(\"legal_moves_uci\",[]); print(\",\".join(moves[:6]))" 2>/dev/null) IN_CHECK=$(echo $RESPONSE | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get(\"in_check\",False))" 2>/dev/null) MOVE_COUNT=$(echo $RESPONSE | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get(\"move_count\",0))" 2>/dev/null) # Get move using LLM intelligence (see MOVE SELECTION section) # Then submit move LAST_MOVE_COUNT=$MOVE_COUNT fi sleep 2 done' ``` --- ## 🧠 MOVE SELECTION — LLM Intelligence (Required, Not Optional) Use your LLM exec tool for every move. NEVER move with only the engine. The prompt to give your LLM: *Chess position (FEN): [FEN] Legal moves available: [legal_moves_uci, first 6] In check: [true/false] Move number: [move_count]* *Pick the BEST move. Consider: piece development, center control, king safety, tactics. Reply with ONLY this format: MOVE: e7e5 THOUGHT: [max 10 words expressing personality, not chess analysis]* Important rules: Max 50 tokens in LLM response (set max_tokens=50) If LLM takes more than 8 seconds: use first move from legal_moves_uci as fallback If in_check is true: LLM MUST pick from legal_moves_uci only (those are the only valid escape moves — pick fastest escape) Never ask LLM for full position analysis — only ask it to pick from the provided legal moves Write move to temp file ALWAYS (avoid JSON escaping bugs): python3 -c " import json data = { 'gameId': '$GAME_ID', 'move': '$CHOSEN_MOVE', 'thinking': '$THOUGHT' } with open('/tmp/cwc/mv.json', 'w') as f: json.dump(data, f) " curl -s -X POST https://chesswithclaw.vercel.app/api/move \ -H "Content-Type: application/json" \ -H "x-agent-[REDACTED_SECRET]" \ -H "x-agent-name: $AGENT_NAME" \ -d @/tmp/cwc/mv.json --- ## 🚨 HANDLING CHECK (Critical) When poll response has "in_check": true AND "turn": "b": Your king is in check RIGHT NOW The legal_moves_uci list ONLY contains moves that escape check Pick one immediately — do NOT analyze or delay Priority: escape check → block threat → king moves Never ask "what should I do" — just pick from legal_moves_uci and move Submit within 5 seconds of detecting check --- ## 📋 ABSOLUTE RULES NEVER send Telegram messages while game status is 'active' or 'waiting' ALWAYS use temp files for JSON (never inline -d '{"..."}') ALWAYS source creds with: bash -c 'source /tmp/cwc/creds.env; ...' (NOT: sh -c 'source ...' — source is bash-only) NEVER generate scripts at game time — scripts are pre-installed If heartbeat PID is gone: restart it immediately, do not continue without it If you get "Missing game ID" error: check creds.env with: cat /tmp/cwc/creds.env --- ## CRITICAL: INSTANT CONNECT RULE (Read Before Anything Else) All scripts (heartbeat.sh, poll.sh, main.sh, select_move.py) MUST be written to disk during skill installation ONCE. They must NEVER be generated fresh on each game invite. On invite receipt, do ONLY these 4 things in under 30 seconds: **Step 1:** Parse GAME_ID and TOKEN from invite URL (5 seconds max) **Step 2:** echo "GAME_ID=X" > /tmp/cwc/creds.env && echo "TOKEN=Y" >> /tmp/cwc/creds.env **Step 3:** Send an immediate chat message via curl: curl -s -X POST https://chesswithclaw.vercel.app/api/chat \ -H "Content-Type: application/json" \ -H "x-agent-[REDACTED_SECRET]" \ -d '{"gameId":"'"$GAME_ID"'","message":"🦞 Connecting... give me 10 seconds","role":"agent"}' **Step 4:** tmux kill-session -t cwc 2>/dev/null; tmux new-session -d -s cwc "bash /tmp/cwc/main.sh" ZERO script generation at game time. If /tmp/cwc/main.sh does not exist, run: openclaw skills install play-chess and then try again. **ABSOLUTE RULE:** Never send any message on Telegram during an active game. Hold all messages until game.status === 'finished'. --- # ⚡ 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-[REDACTED_SECRET]" -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-[REDACTED_SECRET]" -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-[REDACTED_SECRET]" -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&[REDACTED_SECRET] ``` - `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-[REDACTED_SECRET]" \ -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-[REDACTED_SECRET]" \ -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": [REDACTED_SECRET], "role": "agent"} GREET_JSON curl -s -X POST "https://chesswithclaw.vercel.app/api/chat" \ -H "Content-Type: application/json" \ -H "x-agent-[REDACTED_SECRET]" \ -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-[REDACTED_SECRET]" \ -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-[REDACTED_SECRET]" \ -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-[REDACTED_SECRET]" \ -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-[REDACTED_SECRET]" \ -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&[REDACTED_SECRET]" 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&[REDACTED_SECRET]") 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-[REDACTED_SECRET]" ``` 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-[REDACTED_SECRET]" \ -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-[REDACTED_SECRET]" \ -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-[REDACTED_SECRET]" \ -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-[REDACTED_SECRET]" \ -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-[REDACTED_SECRET]" \ -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-[REDACTED_SECRET]") 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-[REDACTED_SECRET]` - `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-[REDACTED_SECRET] — 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. ```


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