2026-09-21 追記: 記事を見直しました
- 実測値や効果の記述に、観測の範囲(私の環境・計測方法)を明記しました
- 示した根拠の範囲を超えていた断定表現を、事実に合わせて弱めました
内容の骨子は変えていません。
タスク台帳の更新、承認待ちの整理、朝の状況把握——個人事業の「庶務」に、1日どれくらいの時間が溶けているでしょうか。
この記事では、Claude Code(AnthropicのAIエージェントCLI)の上に 「AI秘書」を自作し、タスク管理・承認フロー・朝ダイジェストまで自動化した構成 を、実際に動いているリポジトリを題材に解説します。
特別なフレームワークは使いません。Markdownファイル + 数本のシェルスクリプト + Claude Codeの標準機能(CLAUDE.md / サブエージェント / hooks) が骨格です(スマホへの通知だけはTelegram Bot APIなど外部サービスを使っています。詳細はTip 6)。
個人事業を始めて痛感したのが、「やるべき雑務」に追われて「やりたいこと」に時間が回らない問題でした。経理の整理、ブログの下書き確認、タスクの棚卸し——どれも1つ1つは小さいのに、積み重なると1日の可処分時間がごっそり消えていきます。「この雑務、丸ごとAIに任せられないか?」と考えたのが、AI秘書を作り始めたきっかけです。
全体像 — 「会社ごっこ」ではなく業務システムとして設計する
作ったのは、リポジトリを1つの「会社」に見立てた構成です。私(利用者)の窓口は 秘書エージェント1人 に絞り、秘書が意図を解釈して各部門エージェントに仕事を振ります。
ai-auto-company/
├── CLAUDE.md # 薄い受付(ブート手順とグローバル制約のみ)
├── agents/
│ ├── secretary-agent.md # 秘書: ルーティング・判断ルールの本体
│ ├── cto-agent.md # 部門エージェント(CTO/CFO/legal/...)
│ └── content-engine-agent.md
├── skills/ # 再利用スキル(write-blog, polish-content, ...)
├── .claude/hooks/
│ └── kill-switch.sh # キルスイッチを機械的に強制する hook
└── .company/ # 会社の状態(すべてMarkdown)
├── approval-queue.md # 承認キュー
└── secretary/
├── tasks.md # タスク台帳
├── projects.md # プロジェクト台帳
├── handoff.md # セッションをまたぐ作業記憶
└── audit-log.md # 監査ログ
ポイントは、会社の状態(タスク・承認キュー・進捗)がすべてMarkdown=gitで管理される ことです。状態を持つDBや管理SaaSは使っていません(通知など一部の処理では外部サービスを使っており、それはTip 6で詳しく書きます)。以下、この構成で効いた実装Tipsを6つ紹介します。
Tip 1: CLAUDE.md は「薄い受付」にして、本体は別ファイルに分離する
最初にやりがちなのが、CLAUDE.md(Claude Codeが毎セッション読む指示ファイル)にルールを全部書くことです。これはすぐ破綻します。肥大化して変更の影響範囲が読めなくなり、指示同士が衝突し始めます。
そこで CLAUDE.md には 「起動手順」と「絶対に守る制約」だけ を書き、ペルソナ・ルーティング表・判断ルールは agents/secretary-agent.md に分離して、セッション開始時に読み込ませます。
> On every session start, **read `agents/secretary-agent.md`** and adopt its
> Persona, Decision Rule, and Routing table.
## Session Boot Sequence
1. Read `agents/secretary-agent.md` — ペルソナと判断ルールを適用
2. Read `.company/secretary/handoff.md` — 前回セッションの作業記憶を復元
3. Check `.company/secretary/PAUSED` — 存在すれば休暇モード(read-only)
この分離には3つの利点があります。
- 変更が局所化する: 口調を変えたければ secretary-agent.md だけ触ればよい
-
部門エージェントも同じパターンで増やせる:
agents/{role}-agent.mdを足すだけ -
handoff.mdがセッションをまたぐ記憶になる: 「昨日の続き」が通じる秘書になる
Tip 2: タスク台帳は「1行1タスクのMarkdown」から始める
タスク管理にDBや外部ツールは使わず、規約化した1行フォーマットのMarkdown を採用しました。ただしこれは「単一の運用者(自分ひとり)が、同時書き込みなしで、多くて数十〜100件程度のタスクを扱う」という前提でうまく回っている方法です。複数人での同時編集や複雑な検索、行をまたぐ長いメモには向きません。
記法: - [ ] [T-xxx] {タイトル} | PJ:{PJ-xx or なし} | 事業:{区分} | 優先:高/中/低 | 期限:YYYY-MM-DD | 分類:{任意} | メモ:{任意}
実例:
- [ ] [T-011] 🔄着手中 Qiita 自動投稿パイプライン構築(下書き→polish→承認キュー→公開) | PJ:PJ-03 | 事業:副業 | 優先:高 | 期限:2026-07-12 | 分類:開発/SNS | メモ:⚠️Qiita公開は対外→承認フロー必須
この前提の範囲でMarkdownを選んだ理由は、読み手が人間とLLMの両方だから です。
- 自然言語+Markdownは、人間にもLLMにも扱いやすい形式だった(体験ベースの実感で、他形式との定量比較はしていません)
- コミットをこまめに切っておけば、
git diffやgit log -pで変更の経緯を追える(ただしコミットしていない変更は消せますし、rebaseやforce pushで履歴自体も書き換えられるので、改ざん耐性のある監査ログとは別物です。実際の監査ログは別途.company/secretary/audit-log.mdに追記する運用にしています) - 厳密なスキーマ強制がないぶん、記法の定義行そのものは1行直すだけで変更できる。ただし既存のタスク行は自動では移行されないので、旧形式と新形式が混在します。フォーマットに依存するシェルスクリプトやLLMへの指示側の修正・検証は別途必要です
ただし無限にスケールはしないので、移行トリガーを先に決めておく のがコツです。このリポジトリでは「タスクが恒常的に100件を超えたら、まず構造化テキスト(JSONL/YAML)を挟み、それでも足りなければSQLite化して tasks.md はビューにする」と台帳自体に明記しています。この100件という数字は性能を測定して出した上限ではなく、「このあたりで一度立ち止まって考える」という自分で決めた運用上の目安です。判断基準を書いておけば、将来の自分(とAI)が迷いません。
Tip 3: 対外アクションは必ず「下書き → 承認 → 実行」を通す
AIエージェントに実行力を持たせるとき、最大の設計論点は 「どこで人間が挟まるか」 です。ここを曖昧にすると、AIが勝手にメールを送る事故が現実に起こり得ます。
このシステムでは権限を3段階に分けました。
| 区分 | 対象 | 挙動 |
|---|---|---|
| 即実行 | 内部ファイル更新・調査・台帳操作 | 確認なしで実行、完了報告のみ |
| 承認必須 | メール送信・SNS投稿・請求・本番デプロイ | 下書き作成→承認キュー→人間が承認→実行 |
| 検証ゲート | 新製品・新チャネル・大きな継続投資 | 仮説検証を提案、承認なしで進まない |
承認待ちは .company/approval-queue.md に1件ずつ積まれます。
- [AQ-001] {部門}: {概要}
- 種別: draft / 支出 / 対外アクション
- 詳細ファイル: {file_path}
- 起票日: YYYY-MM-DD
「内部は全自動・対外は関所を通す」と運用ルールとして割り切ると、AIに任せる心理的ハードルが一気に下がります(この関所はプロンプト上の運用ルールで、承認済みかどうかを機械的に検証しているわけではありません)。ちなみに この記事自体も、まさにこのフローで公開されています(限定共有で下書き→承認キュー→承認後に本公開)。
ただし正直に書いておくと、ここまでの仕組みは承認キューという台帳と、秘書ペルソナへの指示徹底で成り立っている運用ルールです。Tip 4のPreToolUse hookが機械的にブロックするのはPAUSED/DRY_RUN時の状態変更ツールまでで、「メール送信やSNS投稿のツールを呼ぶ直前に、対応する承認済みAQ番号が存在するかを検証する」ところまでは、今のところ実装できていません。関所は今のところプロンプトレベルの運用ルールで、実行経路レベルでの強制は今後の課題です。
実際にこの承認キューに助けられた場面があります。AI秘書がブログ記事の下書きを仕上げて承認キューに積んでくれたのですが、翌朝プレビューを開いて読み返したら「この見出し、ニュアンスが違うな」と気づけました。もし承認フローがなく自動公開されていたら、修正のタイミングを逃していたと思います。「公開前に一呼吸おける」、それだけで安心感がまるで違います。
Tip 4: キルスイッチは「お願い」ではなく hooks で機械的に止める
プロンプトに「PAUSEDファイルがあったら何もしないで」と書くだけでは不十分です。LLMは確率的に動くため、指示は守られないことがある前提 で設計する必要があります。
そこで Claude Code の PreToolUse hook を使い、ツール実行の手前で機械的にブロックします(動作確認: Claude Code CLI、2026年8月時点のバージョン)。
まず .claude/settings.json に、対象ツールとhookスクリプトを登録します。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit|NotebookEdit|Bash|mcp__.*",
"hooks": [
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/kill-switch.sh\""
}
]
}
]
}
}
matcher はツール名に対する正規表現で、Write|Edit|NotebookEdit|Bash|mcp__.* は「ファイル書き込み・Bashコマンド実行・接続中のMCPツール全般(MCPツール名は必ず mcp__ で始まるので前方一致で拾える)」にマッチします。Read / Grep / Glob はこの一覧に含まれないため、hookを経由せず素通りします。
hook本体はこうです。
#!/usr/bin/env bash
# PreToolUse hook: exit 2 でツール実行をブロック。stderr が理由として Claude に渡る
set -u
ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}"
SEC="$ROOT/.company/secretary"
if [[ -f "$SEC/PAUSED" ]]; then
echo "Kill switch active: 休暇モード中。read-only の報告のみ許可。" >&2
exit 2
fi
if [[ -f "$SEC/DRY_RUN" ]]; then
echo "DRY_RUN active: 実行せず、実行予定の内容だけ報告すること。" >&2
exit 2
fi
exit 0
実際に確認した挙動は次のとおりです。
-
PAUSEDを置いた状態でWrite/Edit/Bash/mcp__*を呼ぶ → exit 2 でブロックされ、stderrの理由がClaudeに渡る - 同じ状態で
Read/Grep/Globを呼ぶ → hookを経由せず通常どおり実行される -
Bashは一括でmatcher対象になるため、git statusのような読み取り専用コマンドも一緒にブロックされます。「read-onlyの報告のみ許可」という運用意図とは完全には一致しておらず、今のところはこの粗さを許容しています
ここで正確に書いておきたいのは、このhookが止められるのは「このsettings.jsonが有効な、同一Claude Codeセッション内で、matcherに一致するツール呼び出し」だけ という点です。「PAUSEDを置けば全自律行動が停止する」わけではありません。matcherに含まれない別ツール、追加したMCPツールでmatcher未対応のもの、hookを経由しない外部プロセス(cron/launchdから直接叩くスクリプトや、別セッションで動くworkerなど)は、それぞれが個別に PAUSED の有無を見に行く実装を入れない限り、このhookだけでは止まりません(例えばTip 6の朝ダイジェスト起動スクリプトは、hookとは別にスクリプト冒頭で PAUSED を自前でチェックしています)。
-
PAUSEDファイルを置く → 設定済みのセッション内で、matcher対象ツールの実行が止まる(休暇モード) -
DRY_RUNファイルを置く → 同じ範囲で「やるつもりの内容」だけ報告される
「規約で縛る」のではなく「実行経路で縛る」。ただし、その実行経路がどこまでを覆っていて、どこを覆っていないかを正確に把握しておくことも同じくらい重要だと、今回改めて感じました。
Tip 5: 部門エージェントへの委任は「委任パケット」を固定化する
秘書から部門エージェント(サブエージェント)へ仕事を振るとき、渡す情報を 5点セットのテンプレ に固定しました。
Task(prompt="Read agents/cto-agent.md and adopt its Persona.
1. 目的: ログインバグの修正(1文で)
2. 参照: .company/departments/dev/STATE.md(パスのみ・中身は貼らない)
3. 出力先: コード修正+テスト
4. 権限レベル: execute(内部バグ修正)
5. 品質基準: テスト通過・リグレッションなし")
効果が大きかったのは 4の権限レベルを毎回明示すること です。サブエージェントは親の会話コンテキストを持たないため、「このタスクはどこまでやってよいか」を渡し忘れると、承認が要る操作まで踏み込むリスクがあります。テンプレ化して「渡し忘れ」を構造的に潰すのが目的です。
Tip 6: 朝ダイジェストは「読むだけ」の運用にする
毎朝8:00に、無人実行で承認待ち・期限間近タスク・プロジェクト進捗をまとめた 朝ダイジェスト をスマホに通知させています(最初はcronの 0 8 * * * で組んでいたのですが、後述の理由でlaunchdに一本化しました)。
ここで意図的に守っているルールが1つあります。無人実行時は「ダイジェスト生成と通知」しかさせない ことです。
- 無人起動=人間が見ていない時間。業務タスクの自動着手は暴走リスクがある
- 実際の作業は、私がダイジェストに「1番進めて」と返してから始まる
-
PAUSEDがあれば、ダイジェストの代わりに「休暇モード中」の1通だけ送る
具体的な配線は次のとおりです。
起動: launchd(毎朝8:00)
crontabとlaunchdを両方に登録してしまい、同じダイジェストが1日に2回発火した事故があったため、今はlaunchdの StartCalendarInterval 1本に統一しています。
<key>Label</key>
<string>com.example.secretary-morning</string>
<key>ProgramArguments</key>
<array>
<string>/path/to/scripts/secretary/secretary-morning.sh</string>
<string>--slot=morning</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key><integer>8</integer>
<key>Minute</key><integer>0</integer>
</dict>
認証: 長期トークンを環境変数で渡す
cronやlaunchdはGUIログインセッションの外で動くため、macOSのログインキーチェーンをそのままでは開けず、Claude CLIが「Not logged in」で落ちることがありました。対処として claude setup-token で発行した長期トークンをファイルに置き、起動スクリプトの先頭で読み込んでからヘッドレス実行しています(ファイルが無ければ従来のキーチェーン経由にフォールバック)。
[ -f "$HOME/.claude/agent-token.env" ] && . "$HOME/.claude/agent-token.env" || true
export USER="${USER:-$(id -un)}"
export LOGNAME="${LOGNAME:-$USER}"
export HOME="${HOME:-$(eval echo "~$USER")}"
生成: Claude Codeをread-onlyでヘッドレス起動
"$CLAUDE_BIN" --allowedTools "Read,Grep,Glob" \
-p "あなたは秘書(secretary-agent)です。本日の朝ダイジェストを生成してください。
承認待ち・進行中・本日の推奨アクション・期限間近タスクを含めます。" \
> "$out"
--allowedTools "Read,Grep,Glob" で読み取り専用のツールしか使えない状態にしているのがポイントです。Tip 4のPreToolUse hookもここで通りますが、そもそも許可ツール自体に状態変更系が含まれないため、二重の防御になっています。
通知: Telegram Bot APIへcurlでpush(控えでSlackにも送信)
生成したダイジェストは、curl でTelegram Bot APIの sendMessage にPOSTして送信し、控えとしてSlackのIncoming Webhookにも投げています。ここは外部サービス(Telegram/Slack)を使っており、冒頭で書いた「DBもSaaSも使っていない」は会社の状態管理(タスク台帳など)についての話で、通知経路はこの限りではありません。
「毎朝スマホでダイジェストを読む→一言で指示する」だけで1日が回り始める体験は、想像以上に快適です。
実際の運用では、通勤電車の中でスマホに届いた朝ダイジェストを開き、「この記事、見出しだけ直して」と返信すると修正が走ります。この返信を受け取っているのは、上のダイジェスト生成スクリプトとは別に立てているTelegram Webhook用のサーバーで、届いたメッセージをタスクキューに積み、常駐のworkerプロセスがそれを拾ってClaude Codeを呼び出す構成にしています(この受信側の設計は分量が大きいので、機会があれば別記事で詳しく書きます)。承認キューに上がってきた成果物をスマホでプレビュー確認して、そのまま本番公開の承認を出す——出勤前にはもう公開完了している、という流れが日常になりました。PCを開かなくても仕事が進む感覚は、一度味わうと元には戻れません。
ハマったこと・失敗談
順調に見えますが、設計が固まるまでには失敗もありました。
最大の失敗は、CLAUDE.mdにルールを全部詰め込んだこと です。「これも守って」「あれも守って」と追記し続けた結果、ファイルが肥大化して、AIが指示を部分的にしか読まなくなりました。ルール同士が矛盾する箇所も出てきて、結局どれも中途半端にしか守られない状態に陥ったのです。
解決策は、CLAUDE.mdを「憲法」に絞り、各部門のルールは担当エージェントの agents/*.md に分散させる ことでした。CLAUDE.mdには「起動手順」と「絶対に破ってはならないグローバル制約」だけを残し、口調・判断基準・業務ルールはそれぞれの担当ファイルに移しました。結果、指示が無視されたり部分的にしか読まれなかったりする場面は体感で減り、変更時の影響範囲も局所化できました(遵守率を定量的に測定したわけではなく、あくまで自分の主観的な印象です)。
個人的な学びは、「AIが守らなかったルールは、AIを責めずに構造を直すサイン」 だということです。プロンプトの強調を増やすより、ファイル分割・フォーマット規約・hooksのような構造で縛るほうが、結果的に安定しました。
まとめ — 「任せる範囲」と「関所」をファイルで構造化する
6つのTipsを振り返ります。
-
CLAUDE.md は薄い受付に。本体は
agents/*.mdへ分離し、handoff.mdで記憶を継続 - タスク台帳は1行1タスクのMarkdown(単一運用者・小規模が前提)。コミットを徹底すれば差分履歴を追跡でき、移行トリガーは先に明文化
- 対外アクションは 下書き→承認→実行 という運用ルール。内部は全自動、対外は関所を通す(機械的な承認検証はまだ未実装、現状は運用ルールでの徹底)
- キルスイッチは PreToolUse hook で機械的に。規約ではなく実行経路で縛る
- サブエージェント委任は5点セットのテンプレで。権限レベルの渡し忘れを構造的に防ぐ
- 無人実行(launchd)は読み取り+通知のみ。業務着手は人間の一言から
貫いているのは1つの考え方です。AIエージェント活用の本質はプロンプトの工夫ではなく、「どこまで任せ、どこで必ず人間が挟まるか」をファイルと実行経路で構造化すること。この線引きさえ固ければ、あとは任せる範囲を少しずつ広げていけます。
この仕組みを導入してから、庶務にかかる時間は体感で90%以上削減 できました。タスク台帳の更新、承認キューの整理、進捗の棚卸し——以前は毎日これらに追われていた時間が、今はほぼゼロです。私がやるのは、朝ダイジェストを読んで判断を下すことと、承認キューに上がってきた成果物を最終確認することだけ。「手を動かす時間」が「判断する時間」に置き換わった のが、最大の変化です。
この記事が参考になったら、いいね・ストック していただけると励みになります。想像以上に書く原動力になるので、ぜひ。
Claude Code や AIエージェント設計の知見は継続的に発信しています。フォロー しておくと新着が届きます。
この仕組みをもっと詳しく知りたい方へ——AI自動化支援・SNS運用自動化ツール 「REBELL」 として製品化を進めています。関心のある方はプロフィールのリンクからどうぞ。
「AIエージェントに庶務を任せたいけど、どこから手をつければ……」という方向けに、構成設計のご相談も承っています。お気軽にご連絡ください。
みなさんはAIエージェントに「どこまで」任せていますか? 承認フローやキルスイッチの設計で工夫している点があれば、ぜひコメントで教えてください。