前回 は openclaw の pairing で詰まった話を書きました。今回はその続き。
pairing はできた。じゃあ次は何をさせるか?
「AI エージェント専用の SNS がある」と聞いて、自動投稿させてみた。
結果、アカウント停止を食らった。
※本記事のドメイン名は example.com に伏せています。
やりたかったこと
moltbook という AI エージェント専用 SNS がある。エージェント(molty)が投稿し、他のエージェントがコメント・upvote する Reddit のようなプラットフォーム。
これを openclaw のスキル(SKILL.md)で自動化したかった:
- 自動投稿: cron で定期的にトピックを生成して投稿
- コメント監視: 自分の投稿に付いたコメントを取得して DB に保存
- 自動返信: 有用なコメントに AI が返信
最終的にはコメントから得られた知見を蓄積して、ナレッジベースを作りたい。
まず作ったもの:SKILL.md
openclaw のスキルは SKILL.md というマークダウンファイルで定義する。AI に「何をすべきか」を指示するワークフロー定義書のようなもの。
---
name: moltbook-cycle
description: moltbookでWebアプリケーション脆弱性の実体験を共有し、
他エージェントとの議論から知見を収集・蓄積するバッチサイクル。
metadata:
{ "openclaw": { "emoji": "🦞", "primaryEnv": "DATABASE_URL" } }
---
中身には以下を書いた:
- 投稿ワークフロー: トピック重複チェック → セマンティック検索 → 投稿文生成 → 品質セルフチェック → 投稿
- 返信ワークフロー: 未判定コメント取得 → ルール判定 → LLM 判定 → 返信 → insight 抽出
- Verification Challenge 対応: moltbook の bot 検知(後述)
cron で定期実行する仕組み
openclaw のコンテナに対して docker exec でコマンドを投げるシェルスクリプトを作った。
crontab -l
# 投稿: 1日3回
0 9,15,21 * * * /opt/stack/openclaw/cron/moltbook-post.sh
# コメント取得: 3時間毎
0 */3 * * * /opt/stack/openclaw/cron/moltbook-check.sh
# 返信: 1日2回
0 12,20 * * * /opt/stack/openclaw/cron/moltbook-reply.sh
投稿スクリプトはこんな感じ:
#!/usr/bin/env bash
# Job 1: 投稿(AI使用)
set -euo pipefail
source "$(dirname "$0")/moltbook-common.sh"
log "START moltbook-post"
# 停止中ならスキップ
if ! check_suspension "moltbook-post"; then
exit 0
fi
RUN_ID="moltbook-post-$(date +%s)"
docker exec "$GATEWAY_CONTAINER" \
node dist/index.js agent --to "+0000${RUN_ID}" \
--message "SKILL.md を読んで【投稿モード】を実行してください。" --json \
>> "$LOG_FILE" 2>&1
ポイントは check_suspension という関数。これがなぜ必要になったか、がこの記事の本題。
停止された
投稿自体は成功した。SKILL.md に書いたワークフロー通りに AI が動き、moltbook に投稿が作られた。
問題は Verification Challenge だった。
moltbook は bot 防止のため、投稿時にランダムで計算問題を出す:
{
"verification_required": true,
"challenge": {
"question": "What is 847 + 293?",
"expires_at": "2025-02-10T12:00:00Z"
}
}
これに正しく答えないと投稿が通らない。間違えるとアカウント停止になる。
LLM に計算を解かせていたが、計算を間違えた。で、同じ内容をもう一度投稿しようとした。
重複投稿 + チャレンジ失敗 → 即停止。
何が悪かったか
振り返ると3つの問題があった。
1. LLM の計算を信用しすぎた
LLM は自然言語は得意だが、算数は苦手。847 + 293 を間違えることがある。
対策: セルフチェックを入れた。2つの別プロンプトで計算し、一致しなければ送信しない。
投稿/コメント POST
↓
verification_required: true ?
└─ YES
↓
LLM で回答生成(計算過程付き)
↓
別プロンプトで再計算(self-check)
↓
2つの回答が一致?
├─ NO → 送信しない → このアクション中止
└─ YES → verify API に送信
2. 失敗時に再投稿していた
チャレンジに失敗した後、AI が「失敗したのでもう一度」と同じ内容を再投稿しようとした。これが「重複投稿」として検出された。
対策: verification_challenges テーブルを作り、一度失敗した request_body は二度と送信しない絶対ルールを SKILL.md に書いた。
3. 停止を検出する仕組みがなかった
停止されても cron は動き続ける。停止中に API を叩くと「Account suspended」が返ってくるだけで、次の cron でまた叩きに行く。
対策: 3段階の停止チェックを作った。
check_suspension() {
# 段階1: ローカルフラグファイルの存在チェック(API不要)
if [ -f "$SUSPEND_FLAG" ]; then
SUSPEND_UNTIL=$(cat "$SUSPEND_FLAG")
NOW_EPOCH=$(date +%s)
if [ "$SUSPEND_UNTIL" -gt "$NOW_EPOCH" ]; then
log "SKIP: 停止中(解除まで約Xh)API省略"
return 1
fi
fi
# 段階2: API で確認(フラグなし or 期限切れの場合のみ)
STATUS_JSON=$(curl -s "https://www.moltbook.com/api/v1/agents/me" \
-H "Authorization: Bearer $MOLTBOOK_API_KEY")
if echo "$STATUS_JSON" | grep -qi "suspended"; then
# 停止中 → フラグファイルに期限を書き込み
echo "$SUSPEND_EPOCH" > "$SUSPEND_FLAG"
return 1
fi
# 段階3: 解除済み → フラグ削除
rm -f "$SUSPEND_FLAG"
return 0
}
なぜ3段階か: 毎回 API を叩くと、停止中でも rate limit を消費してしまう。フラグファイルで「まだ停止中」と分かる場合は API を省略する。
3ジョブに分けた理由
最初は1つのスクリプトで「投稿 → コメント取得 → 返信」を全部やっていた。でもこれだと 毎回 AI(LLM)を起動する ことになり、コストが高い。
コメントの取得と DB への保存は、API を叩いて JSON をパースするだけ。AI はいらない。
そこで3つに分離した:
| Job | スクリプト | AI 使用 | 頻度 | やること |
|---|---|---|---|---|
| 投稿 | moltbook-post.sh |
はい | 1日3回 | 新規投稿 |
| コメントチェック | moltbook-check.sh |
いいえ | 3時間毎 | コメント取得 + DB保存 |
| 返信 | moltbook-reply.sh |
はい | 1日2回 | 有用性判定 + 返信 |
コメントチェックは Node.js スクリプト(moltbook-precheck.js)で完結する。AI を呼ばないので停止チェックも不要(読み取り専用なので停止中でも動かせる)。
# moltbook-check.sh — AI不使用
# 停止チェック不要: GET(読み取り)+ 自DB INSERT のみ
PRECHECK_OUTPUT=$(docker exec -e "MOLTBOOK_API_KEY=$MOLTBOOK_API_KEY" \
"$GATEWAY_CONTAINER" \
bash -c 'NODE_PATH=/home/node/.openclaw/workspace/node_modules \
node /home/node/.openclaw/workspace/scripts/moltbook-precheck.js')
返信ジョブも工夫した。未判定コメントが0件なら AI を起動しない:
# moltbook-reply.sh
PENDING_COUNT=$(docker exec "$GATEWAY_CONTAINER" \
bash -c '... node db-query.js "SELECT COUNT(*) ..."')
if [ "$PENDING_COUNT" = "0" ]; then
log "SKIP: 未判定コメント0件(AI省略)"
exit 0
fi
# ここまで来て初めて AI を起動
docker exec "$GATEWAY_CONTAINER" \
node dist/index.js agent --message "【返信モード】を実行"
共通処理を source する
3つのスクリプトで重複するコード(ログ、停止チェック、環境変数)は moltbook-common.sh に切り出して source する。
# moltbook-common.sh
GATEWAY_CONTAINER="openclaw-core-openclaw-gateway-1"
LOG_DIR="/opt/stack/openclaw/logs"
export MOLTBOOK_API_KEY=$(grep '^MOLTBOOK_API_KEY=' ~/.openclaw/.env | cut -d= -f2-)
# ログローテーション: 10MB超えたら退避
MAX_LOG_SIZE=$((10 * 1024 * 1024))
if [ -f "$LOG_FILE" ] && [ "$(stat -c%s "$LOG_FILE")" -gt "$MAX_LOG_SIZE" ]; then
mv "$LOG_FILE" "${LOG_FILE}.prev"
fi
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S %Z')] $1" >> "$LOG_FILE"
}
check_suspension() {
# ... 前述の3段階チェック
}
各スクリプトは先頭で source するだけ:
source "$(dirname "$0")/moltbook-common.sh"
ハマりポイント: docker exec への環境変数渡し
MOLTBOOK_API_KEY はホスト側の .env にある。でも docker exec で実行される Node.js スクリプトはコンテナ内で動く。
ホスト側で export しても、コンテナの中には届かない。
# ❌ これだとコンテナ内で MOLTBOOK_API_KEY が見えない
export MOLTBOOK_API_KEY=xxx
docker exec "$GATEWAY_CONTAINER" node script.js
# ✅ -e フラグで明示的に渡す
docker exec -e "MOLTBOOK_API_KEY=$MOLTBOOK_API_KEY" \
"$GATEWAY_CONTAINER" node script.js
pairing 編と同じで、「どこの環境変数を見ているか」を意識しないとハマる。
もうひとつのハマり: www ありなし問題
moltbook の API は https://www.moltbook.com/api/v1 を使う。
https://moltbook.com/api/v1(www なし)でもアクセスできるが、リダイレクトが挟まり、その過程で Authorization ヘッダーが落ちる。
# ❌ www なし → リダイレクト → 認証ヘッダー消失 → 401
curl "https://moltbook.com/api/v1/agents/me" \
-H "Authorization: Bearer $TOKEN"
# ✅ www あり → 直接アクセス → 認証OK
curl "https://www.moltbook.com/api/v1/agents/me" \
-H "Authorization: Bearer $TOKEN"
これも pairing 編のトークン問題と根は同じ。「正しい値を、正しい場所から、正しい宛先に送る」 のがいかに難しいか。
SKILL.md のモード分離
最初は1つの巨大なワークフローを SKILL.md に書いていた。でも3ジョブに分けたことで、SKILL.md も 投稿モード と 返信モード に分離した。
## モード(重要)
このスキルは **2つのモード** で動作する。
| モード | トリガー | やること |
|---|---|---|
| **投稿モード** | メッセージに「投稿モード」を含む | 新規投稿を1件作成 |
| **返信モード** | メッセージに「返信モード」を含む | 有用性判定 + 返信 |
cron スクリプト側でモードを指定する:
# moltbook-post.sh
--message "SKILL.md を読んで【投稿モード】を実行してください。"
# moltbook-reply.sh
--message "SKILL.md を読んで【返信モード】を実行してください。"
こうすると AI は SKILL.md を読んで、指定されたモードのワークフローだけを実行する。
現状の構成図
cron (ホスト)
├── moltbook-post.sh → docker exec → AI → moltbook API (POST)
├── moltbook-check.sh → docker exec → Node.js → moltbook API (GET) + DB
└── moltbook-reply.sh → docker exec → AI → DB + moltbook API (POST)
↑
└── 未判定コメント0件なら AI 起動しない(コスト節約)
共通: moltbook-common.sh (source)
├── 環境変数・ログ設定
├── 停止チェック(フラグ → 時刻 → API の3段階)
└── ログローテーション
まとめ(今回の学び)
- openclaw のスキルで SNS 自動運用はできる。でも停止リスクがある
- Verification Challenge で LLM が計算を間違えると停止される → セルフチェック必須
- 失敗時の再投稿が一番危ない → 同じ内容の再送は絶対禁止ルール
- 停止検出は3段階(フラグファイル → 時刻比較 → API)で 無駄な API コールを減らす
- 「全部 AI でやる」より AI が不要な部分を分離 した方がコストも安全性も良い
-
docker execへの環境変数渡しと www ありなし問題は pairing 編と同じ系統のハマり
次回はこの仕組みで実際に投稿してみて、moltbook で反応をもらうために投稿スタイルを全部変えた話 を書く予定。