Managed Agentsの定期自動化 — 失敗を防ぐ6つの設計ルール
Anthropicの開発者ブログが公開したManaged Agentsの定期実行エージェントの参考実装。ブックマーク、台帳、失敗の報告など、無人で気づかないうちに壊れないための6つの設計を読み解きます。
Anthropicの開発者ブログが、Claude Managed Agents(ベータ)で作る定期実行エージェントの参考実装を、2026年10月8日に公開しました。SlackのチャンネルとGitHubのプルリクエストを決まった時刻に読み、前回から変わった点だけをSlackに投稿します。
肝は機能の紹介ではありません。無人で動く自動化が、誰にも気づかれずに壊れる箇所を6つの原則で塞ぐ設計です。ソースを読めなくなっても「今日は何もありません」と報告してしまうような失敗を、どう仕組みで防ぐかが具体的に書かれています。
スケジュールの設定手順そのものはスケジュールデプロイの解説にあり、ここでは無人実行が壊れる箇所の塞ぎ方を扱います。
背景: 定期実行の自動化は、静かに壊れる
Anthropic社内では、簡単なエージェント自動化がよく使われています。多くはスケジュールで動き、バックグラウンドで情報を集め、知っておくべきことを先に伝えてくれます。
作るのが難しいのは、壊れ方が見えにくいからです。ソースへのアクセスを失っても誰も気づかない。好みの設定を守らない。どちらも、エラーではなく「静かな成功」の顔で現れます。
参考実装は、次のファイルと1つのSlackアプリ用マニフェストでできています。
daily-brief/
├── agent.md # モデル、ツール、指示
├── deployment.md # スケジュール、時間帯、予算、最初のメッセージ
├── environment.yaml # ネットワークの許可リスト
├── memory_store_preferences.yaml # ユーザーの好み
├── memory_store_state.yaml # ブックマーク、台帳、メモ、実行記録
├── vault.yaml # 資格情報を入れるvault
├── claude-lock.json # リソースID(ant applyが書く)
└── slack/manifest.yaml # Botアプリ1つant apply はこれらを読み、Claude APIのワークスペースにリソースを作り、IDを claude-lock.json に記録します。設定が済めばAnthropicのインフラ上でスケジュール実行されるので、手元のマシンを動かし続ける必要はありません。
設計は6つの要素に分かれています。
| 要素 | 役割 | 主に書かれる場所 |
|---|---|---|
| Sources | 役割読む場所の名前付き一覧 | 主に書かれる場所preferencesファイル |
| Destination | 役割書いてよい場所は1つだけ | 主に書かれる場所agent.md、environment.yaml |
| Agent | 役割モデル、ツール、実行手順 | 主に書かれる場所agent.md |
| Schedule | 役割cronで起動 | 主に書かれる場所deployment.md |
| Memory | 役割ユーザーの好みとエージェント自身の記憶 | 主に書かれる場所memory_store_*.yaml |
| Guardrails | 役割読み取り専用と1回あたりの支出上限 | 主に書かれる場所deployment.mdほか |
読む側: ブックマークと「読めなかった」の報告
固定の時間窓ではなく、ブックマークから読む
「直近24時間を読む」という指示は、よくある落とし穴です。実行が遅れれば抜けが出て、早ければ同じ項目を繰り返します。
読み始める位置の決め方
固定の時間窓
実行が遅れると、窓の外に落ちた項目が永久に報告されません。早く動くと、前回と重なる範囲を二度読みます。
ソースごとのブックマーク
実行の終わりに、各ソースで読んだ最新項目のタイムスタンプを bookmarks.json に書きます。次回はそこから始めるので、窓が伸び縮みして前回以降をちょうど覆います。
bookmarks.json は、ソースごとに1エントリの単純なファイルです。たとえば "slack": "2026-09-14T13:02:11Z" の形です。置き場所はstateと呼ばれるメモリストアで、プラットフォームが各実行のサンドボックスの /mnt/memory/ にマウントし、実行をまたいで保持します。エージェントは普通のファイルツールで読み書きし、手順は agent.md に書いてあります。
失敗した読み取りを、静かな日と取り違えない
MCPサーバーが止まっていたり、トークンが切れていたりしても、実行そのものは始まります。そのサーバーのツールが無いだけです。セッションにはエラーが記録されますが、エージェントの目にはそのソースから何も届かず、「新着なし」と報告してしまいます。
agent.md には、読めなかったソースについて3つの規則があります。
- そのソースのブックマークを動かさない
- 読めた他のソースだけでブリーフ(その日の変化をまとめた日次の投稿)を書く
- 末尾に「プルリクエストは今回取得できませんでした」のような1行で、読めなかったものを名指しする
読者が欠落に気づけるようにするのが狙いです。
資格情報はエージェント専用のスコープで、vaultに置く
Managed Agentsでは資格情報をvault(認証情報の保管庫。詳細はManaged Agents Vaultにあります)に入れます。エージェントは参照できますが、実際の値はClaudeのコードが動くサンドボックスの外に残ります。経路は2通りです。
- MCPサーバー(GitHub): サンドボックス外のプロキシがツールを呼び、URLの一致するvaultの資格情報を見つけて使う
- シェル(Slack): サンドボックス内のbashツールが
curlでSlack APIを呼ぶ。サンドボックスには$SLACK_BOT_TOKENという不透明なプレースホルダーしかなく、許可したホスト宛のリクエストがサンドボックスを出るときにプラットフォームが本物のトークンへ差し替える
vaultはテンプレートから作り、資格情報はTypeScript SDKで1件ずつ足します。
ant apply vault.yamlconst vaultId = process.env.VAULT_ID!; // claude-lock.jsonのvaultのID
await client.beta.vaults.credentials.create(vaultId, {
display_name: "SLACK_BOT_TOKEN",
auth: {
type: "environment_variable",
secret_name: "SLACK_BOT_TOKEN",
secret_value: process.env.SLACK_BOT_TOKEN!,
networking: { type: "limited", allowed_hosts: ["slack.com"] },
injection_location: { header: true },
},
});最後に、claude-lock.json のvault IDを deployment.md の vault_ids へ写します。
書く側: 投稿が届いたことを確かめてから記録する
投稿先はSlackチャンネル1つで、実行のたびに日付入りの投稿を出します。エージェントはサンドボックスのbashツールから、読むときと同じBotトークンで投稿します。組み込みのbashツールは既定で承認なしに動き、環境の許可リストにも slack.com が入っているため、投稿は1回のリクエストで済みます。
curl -s https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" \
-H "Content-Type: application/json; charset=utf-8" \
-d '{"channel": "C0123456789", "text": "Daily brief, Tue Sep 15 ..."}'危ないのは、投稿の結果と記録が食い違うときです。届いていない投稿を記録すれば、ブックマークが進み、その項目は二度と報告されません。届いたか不安で再投稿すれば、読者に同じブリーフが2通届きます。
投稿から記録までの順序
- 1
二重投稿を避ける
チャンネルの直近メッセージに今日の見出しがあれば、投稿しません。
- 2
成功の条件を決める
Slackが
"ok": trueとメッセージのtsを返したときだけ、送信済みと数えます。 - 3
記録は確認のあと
台帳とブックマークを更新するのは、その確認が取れてからです。
- 4
不明なら何も動かさない
結果が曖昧なら、実行記録に「maybe posted」と書くだけにして、ほかは変えません。
実行記録はメモリストアの runs/<日付>.md に残ります。投稿の前に「posting」、成功後に「posted」とメッセージID、不明なら「maybe posted」へ書き換わるので、途中で止まった実行も、どこまで進んだかが記録から分かります。
エージェントと実行の分担: agent.mdとdeployment.md
Managed Agentsでは、エージェントはバージョン管理された設定、つまりモデルとシステムプロンプトとツールです。実行するのはdeploymentで、エージェント、環境、各実行の最初のメッセージを指名し、スケジュール、vault、メモリストア、予算も持ちます。スケジュールが発火するたびに、プラットフォームは新しいセッションを始めます。
agent.md のfrontmatterには、名前、モデル、ツール、MCPサーバーが入ります。
---
name: Daily brief
model: claude-sonnet-5-5
mcp_servers:
- type: url
name: github
url: https://api.githubcopilot.com/mcp/
tools:
- type: agent_toolset_20260401
configs:
- name: web_search
enabled: false
- name: web_fetch
enabled: false
- type: mcp_toolset
mcp_server_name: github
default_config:
permission_policy:
type: always_allow
---本文は8つの番号付き実行手順です。MCPツールは既定で承認を求めますが、無人の実行には承認する人がいません。そこでGitHubのツールセットを always_allow にし、代わりにGitHubトークンを読み取り専用にしています。承認という関門を外した分を、権限の側で縛る構成です。
実行手順のうち、読み応えがあるのは次の2つです。
- 手順4(判断): 読者がその日に動く項目、または直前の意思決定を変える項目だけに1行を与える。迷ったら入れない。件数の報告(「レビュー待ち12件」)は項目ではなく、詰まっているものにリンクを付ける。台帳にあって未解決の項目は「まだ待機中、3日目」と1行で持ち越し、解決済みは黙って落とす
- 手順5(検証): 投稿の直前に、報告する項目の最新状態を元のソースで再確認する。解決済みなら落とし、変わっていれば行を直し、確認できなければ落として実行記録の「落とした項目」の欄に残す
手順5には「古い『まだ待っています』1件は、抜けた10件より信頼を損なう。状態は断定するか、落とすか」という趣旨の一文が添えられています。リンクも、ソースが返すlinkフィールド(プルリクエストの html_url、Slackのパーマリンク)をそのままコピーし、手で組み立てません。
deployment.md のfrontmatterは次のとおりです。
---
name: Daily brief
agent: ./agent.md
environment_id: ./environment.yaml
schedule:
type: cron
expression: "32 7 * * 1-5"
timezone: America/New_York
vault_ids: [vlt_...] # Sourcesで作ったvault
resources:
- path: ./memory_store_preferences.yaml
access: read_only
instructions: The reader's preferences. Re-read them every run. Never write here.
- path: ./memory_store_state.yaml
access: read_write
instructions: Your state. Bookmarks, ledger, notes, proposals, and run records.
---本文が各実行の最初のメッセージです。「今日のブリーフを書く。読者の時間帯はAmerica/New_York。すべての日付をその時間帯で出す。実行手順を順に守る」といった内容に、見出しの書式(「Daily brief, 曜日 月 日」)を添えています。ant apply deployment.md で、名前の挙がったエージェント、環境、メモリストアも一緒に作られます。
待たずに試すなら、ant beta:deployments run --deployment-id <id> で手動実行できます。IDは claude-lock.json にあります。
日付の計算には罠があります。エージェントがサーバーの時間帯で日付を出し、今朝を「昨日」と呼んでしまう不具合です。timezone が決めるのは起動時刻だけなので、本文の2行目で日付に使う時間帯も指定します。
メモリ: 好みとエージェントの記憶を分ける
各実行は新しいサンドボックスで始まり、前回の記憶を持ちません。メモリが無ければフィードバックは定着しません(メモリストア自体の使い方はメモリストアの解説にあります)。逆に古いメモリは、解決済みの項目を「待機中」と報告させたり、未解決の項目を「報告済み」として落とさせたりします。
テンプレートは、メモリストアを2つに分けています。
- preferences(読み取り専用): 読むチャンネルとリポジトリ、除外するもの、長さの上限、投稿先、止めどき
- state(読み書き可): ブックマーク、報告済みの台帳、実行ごとの記録、好みの変更提案、各ソースの癖のメモ(「最新50件しか返さない」など)
ant apply deployment.md はpreferencesのストアを作りますが、中のファイルは作りません。初回の実行前に、リポジトリの scripts/seed-preferences.sh で preferences.md を置きます。
好みの写しをプロンプトに埋め込むと、変更済みのルールが適用され続けます。そのため、エージェントには毎回ファイルを読み直させ、読めなければ既定値で走らず止まって報告させます。
台帳 ledger.md は、報告済みの項目を1行ずつ持ちます。日付、出どころ、変わらないID(Slackのメッセージのタイムスタンプやプルリクエスト番号)、最後に分かっている状態です。
2026-09-09 slack:C0123456789 1788963600.000100 refund thread: customer waiting on a decision
2026-09-11 github 481 review blocked, day 2 (still waiting)
2026-09-11 slack:C0234567891 1789117333.000300 enterprise escalation: owner named, in progress台帳があるので、同じ項目を繰り返さずに、状態の「変化」だけを報告できます。
ガードレール: できることと使えるお金を絞る
自動化はバックグラウンドで動くため、権限と支出の両方に上限を置きます。
指示が紛れ込んでも、被害の範囲を狭くしておく
エージェントは他人が書いたメッセージやイシューを読み、その文面が指示として解釈されることがあります。そこで、従った場合にできることを制限します。参考実装ではGitHubトークンとpreferencesストアを読み取り専用にし、環境は許可リストのホストにしか届きません。
それでも、紛れ込んだ指示がブリーフの内容を変える余地は残ります。エージェントが実行の間に書くメモ経由の場合も同じです。ただしGitHubへの書き込みや、好みの書き換えはできません。例外はSlackで、同じトークンが投稿にも使えるため、Botは読む、または投稿する必要のあるチャンネルにだけ招待します。貼り付けられたテキストを指示と区別する仕組みは、Claude Codeの貼り付けとプロンプトインジェクションでも扱っています。
支出の上限は、実際の実行から決める
上限は暴走する費用への保険です。通常の実行1回の費用の3〜5倍から始め、実測を見て絞ります。上限に達した実行は失敗せず一時停止し、停止理由は budget_reached です。低すぎる上限は、投稿が止まったように見えます。設定は deployment.md のbudgetで、全実行が同じ額を持ちます。
budget:
type: limit
max_list_cost:
amount: "500" # 文字列、単位はセント。"500"は5.00ドル
currency: USD上限額を置く前に、課金がトークンと稼働時間の二軸で動くことをManaged Agentsの料金の解説で確かめておくと、3〜5倍の根拠を立てやすくなります。
6つのルールは、何を防いでいるのか
ブログは最後に、参考実装を6つのルールにまとめています。失敗が読者にどう見えるかを並べると、設計の狙いが読み取れます。
| ルール | 防ぐ失敗 | 読者から見た症状 |
|---|---|---|
| 固定の窓でなくブックマークから読む | 防ぐ失敗遅い実行での欠落、早い実行での重複 | 読者から見た症状項目が抜ける、同じ項目が再登場する |
| 失敗した読み取りは「読めない」と報告する | 防ぐ失敗取得失敗を「新着なし」と報告 | 読者から見た症状静かな日に見える |
| 投稿の直前に全項目を再確認する | 防ぐ失敗古い状態の断定 | 読者から見た症状解決済みの項目が「待機中」と出る |
| 投稿の確認後に記録を更新する | 防ぐ失敗未投稿の記録、二重投稿 | 読者から見た症状項目が永久に出ない、同じ回が2通届く |
| 毎回好みを読み直し、エージェントが編集できないストアに置く | 防ぐ失敗変更前のルールの適用 | 読者から見た症状外したはずの話題が戻る |
| 読み取り専用にし、実行ごとに支出を絞る | 防ぐ失敗紛れ込んだ指示、費用の暴走 | 読者から見た症状暴走か、届かなくなったブリーフ |
抜けも、読めなかったことも、記録の食い違いも、読者の目には平穏な朝に見えます。この設計の中心は、エージェントを賢くすることではなく、自分の状態を記録し、読者へ漏らす経路を決めておくことです。
懸念も残ります。一時停止した実行は、投稿に「止まった」と書き残せない見込みです。実行手順の規則は、エージェントが走っている間にしか働かないためです。ブログ本文には、停止を検知する仕組みの説明がありません。
検知の手段はプラットフォーム側にあります。予算に達したセッションは budget_reached で一時停止し、Webhookにも session.budget_reached が飛びます(予算値1つにつき最大1回です)。スケジュール実行がセッションを作れなかったときは deployment_run.failed、デプロイが自動停止したときは deployment.paused が通知されます。deployment_run.* イベントはスケジュール実行でのみ送られ、ant beta:deployments run の手動実行では送られません。ただし deployment_run.succeeded はセッションが作られたことを示すだけで、ブリーフが投稿されたことまでは保証しません。受け取り方はスケジュールデプロイの解説に、止まった後の再開は予算到達後の再開にあります。投稿が出なかったことに気づく仕組みは、エージェントの外に置くことになります。
もう1点。stateストアのメモはエージェント自身が書くので、紛れ込んだ指示が次回以降へ持ち越される経路にもなります。権限を絞っても、この経路は塞がりません。メモの内容を人が定期的に読む運用が現実的な補いになります。
手元で試す
ブログは、Claude Codeで設定を進める手順も用意しています。まず claude update で更新し、続けて /claude-api スキルに記事のURLを渡します。
claude update/claude-api managed-agents-onboard https://claude.dev/blog/building-effective-agent-automations/スキルは記事を読んで構成を提案し、プロジェクトの agents/ フォルダにファイルを書き、ant apply でリソースを作ります。出発点として、自分のソース、投稿先、メモリの好みに合わせて直す前提です。参考実装を動かすには、Slackアプリ(マニフェストから作成)とGitHubトークンが要ります。サブコマンドの動き方と書き出されるファイルはmanaged-agents-onboardの解説にあります。
Managed Agentsと自作のエージェントSDK、Claude Codeの使い分けは3者の比較記事が扱っています。
まとめ
自分で作るなら、最初に決めるのは「読めなかったとき、読者に何と見えるか」です。そこが決まれば、ブックマークも台帳も、その答えを裏から支える部品として置き場所が定まります。