Skill を作った後に起きること ─ エージェントをサーバーとして常時稼働させる話
はじめに
前回・前々回の記事では、既存の .NET 資産を AI から呼び出せる形(Agent / Skill)に再構成する方法を紹介しました。
- 第1回: 既存の .NET デスクトップアプリを agent skill から呼び出せる形にした話
- 第2回: ソースコードが無い .NET DLL を AI から呼び出せる形にした話
- Claude Code
Skill ができた段階で、エージェントとして「動かせる」状態にはなっています。しかし実際の業務で使い始めると、次の壁にぶつかります。
「誰かが Claude Code を開いて話しかけたときだけ動く」のでは、業務ツールとして使えない。
業務の依頼はメールで来たり、チャットで来たり、決まった時刻に来たりします。人が操作するたびに Claude Code を立ち上げて指示を打つのでは、自動化の恩恵が半減します。
今回は、Skill を作った後の話 ─ エージェントをサーバーとして常時稼働させる構成 をまとめます。
全体の流れ
目指した姿は「依頼が来たら自動で処理して返す」です。
依頼(メール / チャット)
↓
取込パイプライン(自動で受信・選別)
↓
タスクキュー(todo.md)
↓
Orchestrator(タスクを読んで Skill を実行)
↓
既存 .NET DLL 呼び出し
↓
返信(メール / チャット)
人が介在するのは「依頼を送る」と「最終判断が必要な場面のみ受け取る」の2点だけ。それ以外はエージェントが自律的に動く構成です。
タスクキューとしての todo.md
最初に検討したのは、DB やメッセージキューをタスク管理に使う方法です。しかし実際には Markdown ファイル(todo.md)をタスクキューとして使う 方針を選びました。
- [ ] [在庫] 倉庫Aの明日の出荷予定を出して ← 未処理
- [x] [経費] 今月の経費申請を集計して ← 完了
チェックボックスが状態管理になり、タグが担当の振り分けになります。
DB を使わなかった理由はシンプルで、AI がそのまま読み書きできる形式だからです。テキストファイルであれば、エージェントがタスクの内容を理解しながら処理できます。キューの中身を「見ながら考える」ことが自然にできる点が、構造化データより扱いやすかったのです。
完了したタスクは done.md に移し、実行ログとして残します。
agent/
├── todo.md ← タスクキュー(未処理・処理中)
├── done.md ← 完了記録(実行結果・成果物ID)
└── logs/ ← 日付別の実行ログ
取込パイプライン
タスクキューの手前に、依頼を受け取って todo.md に登録するパイプラインを置いています。
入力元はメールとチャットの2系統です。
メール(Gmail API) ─┐
├─→ 選別 ─→ todo.md 登録
チャット(Chat API) ─┘
選別の段階でやっていること:
- 処理対象かどうかの判定(スパム・自動通知・ループ返信を除外)
- タスクの種別と優先度の判定
- 依頼者の特定(後の返信先として保持)
選別を通過したものだけが todo.md に追加され、Orchestrator の処理対象になります。
取込は Windows Task Scheduler で定期実行しています(メールは10分ごと、チャットは1分ごとなど)。Claude Code のセッションとは独立したプロセスとして動かすことで、Claude Code を起動していない時間帯でも取込が続きます。
Orchestratorパターン
todo.md に積まれたタスクを処理するのが Orchestrator の役割です。
/loop 30m /process-todo
のように loop コマンドで定期実行させると、30分ごとに todo.md を読んで未処理タスクを処理し続けます。
処理の流れは次のとおりです。
todo.md を読む
↓
未処理タスクを優先度順に取り出す
↓
タスクの内容から担当 Agent / Skill を判断
↓
Skill を実行(.NET DLL 呼び出し)
↓
結果を依頼者へ返信
↓
todo.md を更新(完了チェックを入れる)
↓
done.md に記録(成果物ID・実行結果)
複数のタスクが積まれている場合、互いに依存しないタスクは並列実行します。たとえば「A さんの照会」と「B さんの照会」は同時に走らせ、「ファイル生成→メール送信」のように順序が必要なものだけ直列にします。
エスカレーション
すべてのタスクを自動で完結できるわけではありません。判断材料が足りない場合や、確認が必要な場合は、依頼者に確認メールを送ることでエスカレーションします。
エージェント自身が「これは自分では判断できない」と判定し、判断の根拠と選択肢をまとめて人に渡す、という設計です。
自律実行のガードレール
エージェントが自律的に動く分、安全側に倒す設計が重要になります。
特に気をつけたのは次の2点です。
更新系処理のガード
Skill の実行には取得系(参照のみ)と更新系(DB を書き換える)があります。更新系 Skill は、実行前に接続先が本番環境ではないことを自己確認するガードを挟んでいます。
更新系 Skill 実行前
↓
接続先を自己確認(テスト環境か本番環境か)
↓
テスト環境でなければ PermissionError で停止
↓
本番への書き込みは「意図的に解除した場合のみ」許可
AI が意図せず本番データを書き換えてしまうリスクを、Skill 側で抑えています。
送信先のチェック
処理結果をメールで自動送信する場合、送信先が正しい相手かどうかを送信前に確認します。
想定外の宛先(テストアカウント・退職者・外部アドレスなど)へ誤送信しないよう、人事マスタなどの既存データと照合して送信可否を判定しています。
確認できない場合は自動送信せず下書き保存に倒し、担当者に通知します。「送れなかった」より「確認なく送った」の方がリスクが大きいため、デフォルトを安全側に設定しています。
2窓構成:速度と安定性を分ける
実際に動かしてみると、タスクの性質が大きく2種類に分かれることが分かりました。
| タイプ | 例 | 求められるもの |
|---|---|---|
| 軽い照会・返答 | 「○○さんの登録状況を教えて」 | 速さ(数分以内) |
| 重い処理 | Excel 生成、RAG 検索、外部API連携 | 安定して完了すること |
これを1つのループで処理すると、重いタスクが詰まって軽い返答が遅れます。
そこで 2窓構成(チャット専用窓 + メール/バッチ専用窓)に分けました。
チャット専用窓(2分間隔)
─ 軽い照会・返答に即応
─ 重いタスクはバッチ窓へ委譲
メール/バッチ専用窓(30分間隔)
─ Excel生成・RAG・外部API等をじっくり処理
─ 完了後にメールで結果を返信
チャットは準ライブ(数分以内の返答)、メールはバッチ処理、と役割を分けることで、両方の品質を保てるようになりました。
サーバーとしての構成まとめ
全体をまとめると次のような構成になります。
[定期実行 / Windows Task Scheduler]
├── メール取込(10分ごと)
└── チャット取込(1分ごと)
↓
todo.md(タスクキュー)
↓
[Claude Code 常時起動]
├── チャット専用窓(/loop 2m /process-chat)
└── メール/バッチ窓(/loop 30m /process-mail)
↓
Skill 実行(.NET DLL 呼び出し)
↓
返信送信 / done.md 記録
取込(Task Scheduler)と処理(Claude Code の loop)を分離していることで、取込はセッション外でも継続し、処理は subscription の定額内に収まる構成になっています。
メリットと注意点
メリット
- ✅ 依頼者はメールやチャットで送るだけ、操作の学習コストがほぼゼロ
- ✅ Skill が増えても取込・Orchestrator の構造は変わらない
- ✅ 並列処理でスループットを確保しつつ、安全設計を維持できる
注意点
- 自律実行が増えるほど、ガードレールの設計が重要になる
- 取込の選別精度が低いと、不要なタスクが大量に積まれる
- エスカレーション先(確認を受け取る人)を明確にしておく必要がある
まとめ
- 第1回:ソースコードを Skill 化
- 第2回:DLL のみでも Skill 化
- 今回:Skill を常時稼働させるサーバー構成
「Skill を作る」と「実際に業務で使える状態にする」の間には、タスクキュー・取込パイプライン・Orchestrator・ガードレールという層があります。
今回紹介した構成を土台にすると、Skill が増えるにつれて対応できる業務が広がり、エージェントとして育てていける状態になります。