頼んだ作業を実データで完了検知して自動で止まる、日次リマインドBotの型
権限共有や初期設定の依頼を出したあと、完了したかを本人の返信ではなくAPIの実データで判定し、未完了なら1日1回だけチャットDMで催促、完了を検知したら自動停止して依頼者に報告する無人ジョブの実装型。試行上限・二重送信防止・誤報防止まで込み。
約3.6万トークンの節約 (API料金換算で約53円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。
この巻物について
「頼んだ作業を実データで完了検知して自動で止まる、日次リマインドBotの型」は、業務自動化カテゴリのAI指示書(MDファイル)です。権限共有や初期設定の依頼を出したあと、完了したかを本人の返信ではなくAPIの実データで判定し、未完了なら1日1回だけチャットDMで催促、完了を検知したら自動停止して依頼者に報告する無人ジョブの実装型。試行上限・二重送信防止・誤報防止まで込み。この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約3.6万トークン(API料金換算で約53円)・85%のトークンを節約できます。
- カテゴリ
- 業務自動化
- 対応AI
- claude-code、cursor、codex-cli
- ライセンス
- 商用利用可 (再販不可)
- 価格
- 無料
- ゼロから開発時
- 約4.2万トークン
- この巻物使用時
- 約6,500トークン
- 節約量
- 約3.6万トークン (約53円)
- 更新日
- 2026-09-17
使い方 (AIに渡す3つの方法)
いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。
claude "https://makimono-md.vercel.app/api/v1/files/bot/raw を読み込んで、この指示書どおりに実装して"
中身
頼んだ作業を「実データで完了検知」して自動で止まる、日次リマインドBotの型
チームの誰かに 1 分の作業(権限の共有、フォーム入力、設定の有効化)を頼んだあと、 やってくれたかどうかを人間が追いかけるのをやめるための実装パターン。
この型の核心は「完了判定を本人の自己申告や返信内容に置かない」こと。 依頼した作業の結果は、たいてい API で直接観測できる。観測できるなら、それを完了条件にする。 そうすると「やりました」と言われたのに実は反映されていなかった、という一番よくある失敗が消える。
対象読者: 社内チャット(Discord / Slack / Teams)の Bot と、確認対象の API を叩ける環境がある人。
1. 設計の骨子
毎日1回スケジューラが起動
↓
① 実データを取り直す(キャッシュを更新する)
↓
② 対象ごとに「完了したか」を実データで判定
↓
├ 完了している → 催促を止める。過去に催促していたなら依頼者へ完了報告
├ 未完了・本日送信済み → 何もしない
├ 未完了・試行上限に到達 → 依頼者へエスカレーションして恒久停止
└ 未完了・それ以外 → 本人へリマインドを1通送り、試行回数を +1
↓
③ 状態をファイルに保存
守るべき性質は 5 つ。
| 性質 | なぜ必要か |
|---|---|
| 完了判定は実データ | 「やりました」は間違っていることがある。観測できるなら観測する |
| 二重送信しない | スケジューラは再実行・復帰時に重複起動する。時刻ではなく「前回送信からの経過」で判定する |
| 試行上限がある | 無人ジョブが永遠に人を突くのは事故。上限に達したら止めて、依頼者にボールを返す |
| 完了したら自分から止まる | 止め方を人間が覚えている必要がある仕組みは、いずれ止め忘れる |
| 最初から完了済みなら黙る | 初回実行で「✅完了を確認しました」と報告すると、催促していないのに手柄を報告する嘘になる |
2. 状態ファイル
対象キーごとに 4 つだけ持てばよい。
{
"<対象キー>": {
"attempts": 3,
"lastSentAt": "2026-01-15T01:00:00.000Z",
"doneAt": null,
"escalatedAt": null
}
}
attempts… 送った回数。上限判定に使うlastSentAt… 最後に送った時刻。間隔判定に使うdoneAt… 完了を検知した時刻。入っていれば以後スキップescalatedAt… 打ち切り報告済みフラグ。同じ打ち切り報告を毎日送らないため
3. 実装(Node.js・依存ゼロ)
chase.mjs:
#!/usr/bin/env node
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
const STATE_FILE = path.join(os.homedir(), '.<アプリ名>', 'chase-state.json');
const OWNER_ID = '<依頼者のチャットユーザーID>'; // エスカレーション先
const MAX_ATTEMPTS = 14; // 2週間で打ち切り
const MIN_INTERVAL_MS = 20 * 60 * 60 * 1000; // 1日1回(実行時刻のブレを吸収して20h)
const args = process.argv.slice(2);
const dryRun = args.includes('--dry-run'); // 送らずに本文だけ出す
const force = args.includes('--force'); // 間隔チェックを飛ばす(手動テスト用)
const readJson = (f, fb) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch { return fb; } };
// --- ここだけ差し替える -------------------------------------------------
// 対象一覧。設定ファイルから読むと、人の追加が「1行足すだけ」になる。
const targets = readJson('<設定ファイル>', { targets: [] }).targets;
// 完了したかを実データで判定する。true = もう催促しない。
async function isDone(target) {
const res = await fetch(`<確認したいAPI>/${encodeURIComponent(target.resourceId)}`, {
headers: { Authorization: `Bearer ${token}` },
});
return res.ok; // 404 = まだ権限が無い = 未完了
}
async function sendMessage(userId, text) { /* チャットAPIの送信 */ }
function requestMessage(target, attempt) {
const opener = attempt === 1
? 'お疲れさまです。'
: `お疲れさまです。${attempt} 回目のご連絡ですみません。まだ確認できていません。`;
return [
`${target.label} さん`, '', opener,
'<やってほしいことを1文で>(所要1分)', '',
'① <手順>', '② <手順>', '③ <手順>', '',
'終わったら自動で検知して、この連絡は止まります(返信不要です)。',
].join('\n');
}
// ----------------------------------------------------------------------
const state = readJson(STATE_FILE, {});
const now = Date.now();
const nowIso = new Date(now).toISOString();
const report = [];
for (const target of targets) {
const entry = state[target.key] ?? { attempts: 0, lastSentAt: null, doneAt: null, escalatedAt: null };
const done = await isDone(target);
if (done) {
if (entry.doneAt) { report.push(`${target.key}: 完了済み(報告済み)`); state[target.key] = entry; continue; }
// 一度も催促していないのに完了 = 最初から済んでいた。依頼者に通知しない(誤報防止)
const chased = entry.attempts > 0;
entry.doneAt = nowIso;
state[target.key] = entry;
report.push(chased ? `${target.key}: ✅ 完了を検知 → 依頼者へ報告` : `${target.key}: 完了済み(催促不要)`);
if (!dryRun && chased) await sendMessage(OWNER_ID, `✅ ${target.label} さんの対応が完了しました。催促は停止しました。`);
continue;
}
// いったん完了したものが未完了に戻った(権限が外された等)= 催促を再開する
if (entry.doneAt) { entry.doneAt = null; entry.attempts = 0; entry.lastSentAt = null; }
if (entry.attempts >= MAX_ATTEMPTS) {
if (!entry.escalatedAt) {
entry.escalatedAt = nowIso;
report.push(`${target.key}: 上限到達 → エスカレーションして停止`);
if (!dryRun) await sendMessage(OWNER_ID, `⚠️ ${target.label} さんへの依頼を ${MAX_ATTEMPTS} 回送りましたが未完了です。自動催促は停止しました。直接お声がけをお願いします。`);
} else {
report.push(`${target.key}: 停止中(エスカレーション済み)`);
}
state[target.key] = entry;
continue;
}
const lastSentMs = entry.lastSentAt ? new Date(entry.lastSentAt).getTime() : 0;
if (!force && now - lastSentMs < MIN_INTERVAL_MS) {
report.push(`${target.key}: 本日送信済みのためスキップ`);
state[target.key] = entry;
continue;
}
const attempt = entry.attempts + 1;
const content = requestMessage(target, attempt);
if (dryRun) {
report.push(`${target.key}: [dry-run] ${attempt} 回目\n----\n${content}\n----`);
} else {
await sendMessage(target.userId, content);
entry.attempts = attempt;
entry.lastSentAt = nowIso;
report.push(`${target.key}: ${attempt} 回目を送信`);
}
state[target.key] = entry;
}
if (!dryRun) {
fs.mkdirSync(path.dirname(STATE_FILE), { recursive: true });
fs.writeFileSync(STATE_FILE, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
}
console.log(report.join('\n') || '対象なし');
チャットへの DM(Discord の例)
Bot トークンで個人 DM を送るには、チャンネルを作ってから投稿する 2 段になる。 Bot と相手が同じサーバーに居ることが条件。
const API = 'https://discord.com/api/v10';
const headers = { Authorization: `Bot ${token}`, 'Content-Type': 'application/json' };
const ch = await fetch(`${API}/users/@me/channels`, {
method: 'POST', headers, body: JSON.stringify({ recipient_id: userId }),
}).then(r => r.json());
await fetch(`${API}/channels/${ch.id}/messages`, {
method: 'POST', headers,
body: JSON.stringify({ content, allowed_mentions: { parse: [] } }), // 意図しない一斉メンションを防ぐ
});
4. スケジューラ登録
Windows:
$action = New-ScheduledTaskAction -Execute '<nodeの絶対パス>' -Argument 'chase.mjs' -WorkingDirectory '<アプリのパス>'
$trigger = New-ScheduledTaskTrigger -Daily -At 10:00
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 15)
Register-ScheduledTask -TaskName '<タスク名>' -Action $action -Trigger $trigger -Settings $settings -Force
-StartWhenAvailable は PC が落ちていて起動時刻を逃した日に、復帰後に実行してくれる。
これがあると「1日1回」の間隔判定が効いてくる(同じ日に2回走っても2通目は送られない)。
Linux/macOS なら 0 10 * * * cd <path> && node chase.mjs。
5. 落とし穴
取得に失敗した日をどう扱うか。 API 側の障害で判定できない日に、完了済みの人へ催促を送ってしまう可能性がある。 「完了を未完了と誤認して1通余計に送る」害と「ジョブが止まって永久に催促されない」害を比べて、 前者の方が小さいので既存状態で続行する設計にした。逆に、送信コストが高い経路(SMS・顧客宛メール)なら止める側に倒す。
完了検知の粒度。 「権限が付いたか」ではなく「実際にデータが読めたか」を条件にすること。 権限の付与先を間違えている、というよくある失敗を同時に捕まえられる。
初回実行は必ず --dry-run で。 送信対象と本文を目で見てから本番実行する。
--force と併用すれば間隔判定も飛ばせるので、状態ファイルの遷移を一通り確認できる。
そもそも催促が不要にならないかを先に確認する。 今回の元ネタでは、催促対象の1人に社内アカウントがあり、 そちらを使えば依頼そのものが不要だった。「誰かに頼む」の前に「頼まずに済む経路」を潰すほうが利く。
6. この型が向く場面
- 権限・共有の付与依頼(カレンダー、ドライブ、リポジトリ、SaaS の招待)
- 初期設定の有効化依頼(2要素認証、通知設定、プロフィール入力)
- 提出物の回収(勤怠、経費、アンケート)— ただし提出有無が API で見えること
逆に、完了を実データで観測できないもの(「読んでおいてください」など)にはこの型を使わない。 観測できないものを自動で催促すると、止まらない Bot になる。
よくある質問
+「頼んだ作業を実データで完了検知して自動で止まる、日次リマインドBotの型」とは何ですか?
権限共有や初期設定の依頼を出したあと、完了したかを本人の返信ではなくAPIの実データで判定し、未完了なら1日1回だけチャットDMで催促、完了を検知したら自動停止して依頼者に報告する無人ジョブの実装型。試行上限・二重送信防止・誤報防止まで込み。
+どれくらいトークン(費用)を節約できますか?
ゼロから開発すると約4.2万トークンかかりますが、この巻物を使えば約6,500トークンで済みます。差し引き約3.6万トークン(API料金換算で約53円)・85%の節約です。
+どうやって使いますか?
無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。
+どのAIツールに対応していますか?
claude-code、cursor、codex-cli に対応しています。
+商用利用できますか?
ライセンスは「商用利用可 (再販不可)」です。
🤝 自分でAIを動かすのは、まだ不安…という方へ
この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。
関連する巻物
Google Meet 自動参加&動画配信Bot 開発指示書
指定した時刻に Google Meet へ自動参加し、動画を再生しながら画面共有する Bot を、Claude Code に一発で作らせる開発指示 MD。朝会の定例動画配信・ウェビナーの自動放送に。
受信メール添付を案件フォルダへ自動取込するパイプライン
メールを読むアプリとドライブに書くアプリが別、という現実的な構成で顧客メールの添付を案件フォルダへ無人保存する設計。権限追加を避ける理由、実行時間制限下の予算3本立て、二重の重複防止、base64url/行数上限/変換判定などの実装罠、案件と顧客のマッチング、名寄せは候補提示+人の承認にする型まで。
Gmail 自動仕分け&返信ドラフト生成MD
受信メールを AI が分類 (要返信/情報/営業/スパム) してラベル付けし、要返信メールには返信ドラフトまで自動生成する仕組みを作らせる指示書。DWD (ドメイン全体委任) 設定手順込み。
この巻物、誰かのトークンも救えます
𝕏 で節約レシートをシェア