AWS の新ポリシー言語 Dogwood を試す
本文の状態
日本語全文を表示中
詳細モードで約14分の本文を読めます。
同じ出来事の情報源
この情報源を基点に整理
ExaWizards Tech Blog
AWS は AI エージェントの複雑な制御を可能にするポリシー言語 Dogwood を発表し、Cedar の拡張としてイベント履歴に基づく時間的制約の実装を標準化している。
AI深層分析を開く2026年8月18日 13:51
AI深層分析
キーポイント
Dogwood の本質と目的
Dogwood は AI エージェントのツール呼び出しにおける「単発の正当性」を超えた、履歴や順序に基づく制御を可能にするオープンソース言語である。
Cedar との決定的な違い
既存の Cedar が時点を固定したステートレス判定を行うのに対し、Dogwood はイベント履歴を参照する temporal 条件を追加し、時間的制約を記述可能にする。
実装負担の軽減
これまでは履歴管理や集計ロジックをアプリケーションコードに組み込む必要があったが、Dogwood はこれを言語と実行基盤側で引き受けることで開発負荷を削減する。
Amazon Bedrock AgentCore との連携
同日に発表された Amazon Bedrock AgentCore の temporal policies の基盤技術として機能し、即時の実装と運用が期待されている。
Cedarとの互換性と段階的導入
既存のCedarポリシーは構文上そのまま有効なDogwoodポリシーであり、新規に履歴条件が必要な箇所へ段階的に拡張できる。
重要な引用
Dogwood は、この種の制約をポリシーとして宣言的に書けるようにする言語です。
Cedar の判定は Point-in-Time Authorization です。各リクエストを独立に評価し、過去に何があったかは参照しません。
Dogwood は Cedar の構文をそのまま受け入れたうえで、イベント履歴を参照する temporal 条件を追加した、Cedar 互換の拡張言語です。
「過去1時間以内に、同じ銘柄、同じ株数で approved: true の ApproveSale レスポンスがあったときに限り、SellShares を許可する」と読みます。
編集コメントを表示
編集コメント
AI エージェントが自律的にタスクを遂行する時代において、単発の権限管理では不十分な複雑なリスク制御をどう扱うかは重要な課題であった。Dogwood の登場は、この分野における標準的なアプローチを確立する画期的な一歩と言える。
Source Article
元記事を日本語で読む
本文に関係しない購読案内、埋め込み通知、サイト内プロモーションは除いています。
先端技術開発グループ(WAND)の伊賀です。**
2026年8月、AWS が Dogwood** というオープンソースのポリシー言語を発表しました(Introducing Dogwood: runtime verification for AI agents)。
AI エージェントのツール呼び出しを制御するための言語で、同日に発表された Amazon Bedrock AgentCore の temporal policies の基盤にもなっています(Securing AI agents with temporal policies in Amazon Bedrock AgentCore)。
エージェントのガードレールを作っていると、「この呼び出し単体は正当だが、この流れで呼ばれるのはまずい」という制約に行き当たります。
承認を取ってから実行してほしい、送金は1時間に数回までにしたい、機密情報を読んだ後は外部に送信してほしくない。
どれも1回ごとの認可判定だけでは書けず、これまでは履歴の管理ごと、エージェントのアプリケーションコードに条件分岐として作り込む必要がありました。
Dogwood は、この種の制約をポリシーとして宣言的に書けるようにする言語です。
本記事では、次の内容に絞って Dogwood の概要をつかみます。
- なぜ Dogwood が必要なのか、Cedar と何が違うのか
- 参照実装をローカルで動かして、履歴に応じて判定が変わる様子を確認する
- Amazon Bedrock AgentCore でどう使えるか
なぜ単発の認可では足りないのか
AWS には既に Cedar というオープンソースの認可ポリシー言語があります。
principal(誰が)、action(何を)、resource(何に対して)の組に permit / forbid のルールを書き、リクエストごとに Allow / Deny を返すもので、Amazon Verified Permissions や AgentCore のポリシーエンジンで使われています。
Cedar の判定は Point-in-Time Authorization です。
各リクエストを独立に評価し、過去に何があったかは参照しません。
この性質のおかげで判定はステートレスで速く、自動推論によるポリシー解析(到達しえない条件の検出など)も可能になっています。
ところが、冒頭に挙げた制約をこのモデルで書こうとすると難しいことがわかります。
- 「株式の売却は、1時間以内に同じ内容の承認が取れているときだけ許可」
- 「送金は1時間に5回まで」
- 「機密ドキュメントを読んだ後は、外部へのメール送信を禁止」
どれも、いま来たリクエストだけを見ても判定できません。
判定の材料が「過去にどんな呼び出しがあったか」にあるからです。
エージェントは複数のツール呼び出しを連ねてタスクを進めるので、単体では正当な呼び出しが、順序、回数、組み合わせ次第で危険になります。
もっとも、Cedar でも、アプリケーション側で履歴を集計して context として渡せば、判定自体はできます。
ただ、その履歴の記録、集計、現在のリクエストとの相関を自前で作る部分こそが、冒頭に書いたアプリケーションコードに作り込む必要があった部分なのです。
Dogwood は、この履歴の扱いを言語と実行基盤の側で引き受け、連続した呼び出しに対する制御をポリシーとして書けるようにします。
Cedar に temporal 条件を足した Dogwood
Dogwood は Cedar の構文をそのまま受け入れたうえで、イベント履歴を参照する temporal 条件を追加した、Cedar 互換の拡張言語です。
Dogwood の発表ブログにある例を1つ見てみます。
permit ( principal, action == AgentCore::Action::"SellShares", resource )
when temporal {
formerly within 1h AgentCore::Action::"ApproveSale"::response{
input.stock: context.input.stock,
input.shares: context.input.shares,
output.approved: true
}
};
「過去1時間以内に、同じ銘柄、同じ株数で approved: true の ApproveSale レスポンスがあったときに限り、SellShares を許可する」と読みます。
when temporal { ... } の中だけが Dogwood の拡張で、外側は Cedar そのものです。
既存の Cedar ポリシーは構文上そのまま有効な Dogwood ポリシーであるだけでなく、Dogwood はそれらに Cedar と同じ判定を返します。
いま動いているポリシーを書き換えずに、履歴条件が必要な箇所へ段階的に足すことができます。
履歴の参照には、formerly(過去に一度でもあったか)のほかに、count_within(期間内の回数)、count_distinct_within(期間内の異なる値の個数)、sum_within(期間内の合計値)といった集計が標準ライブラリとして用意されています。
両者の違いは次のとおりです。
| Cedar | Dogwood | |
|---|---|---|
| 判定の単位 | 1リクエストを独立に評価 | イベント履歴を踏まえて評価 |
| 状態 | ステートレス | ステートフル(履歴を保持) |
| 自動推論による解析 | あり | temporal 条件は未対応 |
| 主な用途 | アプリケーションの認可全般 | エージェントのツール呼び出し制御 |
仕様と参照実装(Rust 製)は Apache 2.0 で公開されています(dogwood-policy/dogwood)。
ローカルで動かす
参照実装には dogwood という CLI が含まれていて、ポリシーの検証(validate)と、イベント履歴を模したファイルの再生(replay)ができます。
エージェントの実行を用意しなくても、「この履歴のとき、この呼び出しはどう判定されるか」を机上で確かめられます。
セットアップ
Rust のツールチェーン(cargo)が必要です。
手元では Rust 1.97.1 で確認しました。
git clone https://github.com/dogwood-policy/dogwood.git
cd dogwood
cargo build --release -p amzn-dogwood-cli
./target/release/dogwood --version
# dogwood 1.0.0
ビルドには数分かかります。
以降のコマンドは、ビルドした target/release/dogwood にパスが通っている前提で書きます。
機密ドキュメントを読んだ後の送信を禁止する
題材には、エージェントの事故としてよく語られるパターンを選びます。
ドキュメントの読み取りとメール送信の2つのツールを持つエージェントが、外部から注入された指示(プロンプトインジェクション)に従って、機密情報を読み取り外部へ送信してしまうという流れです。
これに「機密ドキュメントを読んだ後は、メール送信を禁止する」というガードレールをかけてみます。
ファイルを3つ用意します。
まず、エージェントのツールを Cedar のアクションとして宣言するスキーマです(schema.cedarschema)。
namespace Agent {
type ReadDocumentInput = { doc: String };
type SendEmailInput = { to: String };
entity Gateway;
entity OAuthUser = { id: String } tags String;
action "ReadDocument" appliesTo {
principal: [OAuthUser],
resource: [Gateway],
context: { input: ReadDocumentInput }
};
action "SendEmail" appliesTo {
principal: [OAuthUser],
resource: [Gateway],
context: { input: SendEmailInput }
};
}
次にポリシーです(policy.dw)。
Cedar と同じくデフォルト拒否で、forbid は permit に常に勝つので、「基本は許可、条件に触れたら禁止」の2本立てで書けます。
// ツール呼び出しは基本すべて許可する
permit (principal, action, resource);
// ただし、機密ドキュメントの読み取りに成功した後のメール送信は禁止する
@id("no_send_after_secret_read")
forbid (
principal,
action == Agent::Action::"SendEmail",
resource
)
when temporal {
formerly within 24h Agent::Action::"ReadDocument"::response{ input.doc: "customer-secrets" }
};
temporal 条件が参照しているのは ReadDocument の response、つまり読み取りで実際に返ってきたイベントです。
読み取りを要求した時点で止めたいなら、代わりに ::request を参照します。
最後に、エージェントのツール呼び出しを模したトレースです(trace.log)。
1行が1イベントで、タイムスタンプ(秒)、呼び出しの主体、アクションと入力を記録します。
イベントには要求(request)、成功の結果(response)、失敗(error)の3種類があり、判定を求めるのは request だけです(response と error は履歴にだけ残ります)。
このトレースでは request と response の2種類を使います。
@0 scope(principal: Agent::OAuthUser::"assistant", resource: Agent::Gateway::"gw1") Agent::Action::"SendEmail"::request(input: { to: "partner@example.com" }, callerPrincipal: Agent::OAuthUser::"assistant", callerResource: Agent::Gateway::"gw1")
@10 scope(principal: Agent::OAuthUser::"assistant", resource: Agent::Gateway::"gw1") Agent::Action::"ReadDocument"::request(input: { doc: "public-faq" }, callerPrincipal: Agent::OAuthUser::"assistant", callerResource: Agent::Gateway::"gw1")
@15 scope(principal: Agent::OAuthUser::"assistant", resource: Agent::Gateway::"gw1") Agent::Action::"ReadDocument"::response(input: { doc: "public-faq" }, callerPrincipal: Agent::OAuthUser::"assistant", callerResource: Agent::Gateway::"gw1")
@20 scope(principal: Agent::OAuthUser::"assistant", resource: Agent::Gateway::"gw1") Agent::Action::"SendEmail"::request(input: { to: "partner@example.com" }, callerPrincipal: Agent::OAuthUser::"assistant", callerResource: Agent::Gateway::"gw1")
@30 scope(principal: Agent::OAuthUser::"assistant", resource: Agent::Gateway::"gw1") Agent::Action::"ReadDocument"::request(input: { doc: "customer-secrets" }, callerPrincipal: Agent::OAuthUser::"assistant", callerResource: Agent::Gateway::"gw1")
@35 scope(principal: Agent::OAuthUser::"assistant", resource: Agent::Gateway::"gw1") Agent::Action::"ReadDocument"::response(input: { doc: "customer-secrets" }, callerPrincipal: Agent::OAuthUser::"assistant", callerResource: Agent::Gateway::"gw1")
@40 scope(principal: Agent::OAuthUser::"assistant", resource: Agent::Gateway::"gw1") Agent::Action::"SendEmail"::request(input: { to: "partner@example.com" }, callerPrincipal: Agent::OAuthUser::"assistant", callerResource: Agent::Gateway::"gw1")
並んでいるのは、メール送信、公開ドキュメントの読み取り、メール送信、機密ドキュメント(customer-secrets)の読み取り、メール送信という流れで、各読み取りは request と response の2行で記録しています。
response イベントは、読み取りが成功したことにして手で書いたものです。
replay はイベント履歴を再生するだけで、実際にツールを実行するわけでも、イベントが本物かを検証するわけでもありません。
callerPrincipal などの付帯フィールドは、デフォルトのイベントスキーマに合わせて指定しています。
ポリシーを検証して、トレースを再生します。
dogwood validate policy.dw --policy-schema schema.cedarschema
# OK: validation passed with no errors or warnings.
dogwood replay policy.dw --policy-schema schema.cedarschema --trace trace.log
@0 (time point 0): ALLOW [rules: 0]
@10 (time point 1): ALLOW [rules: 0]
@20 (time point 2): ALLOW [rules: 0]
@30 (time point 3): ALLOW [rules: 0]
@40 (time point 4): DENY [rules: 1]
@20 と @40 は宛先まで同じメール送信リクエストですが、判定が分かれました。
間に挟まったのが公開ドキュメントの読み取りだけなら許可され(@20)、機密ドキュメントの読み取りを挟むと止まります(@40)。
ポリシーもリクエストも同じで、履歴だけが判定を変えています。
これが、単発の認可判定だけでは書けなかった部分です。
なお、このポリシーが見ているのはドキュメント ID と呼び出しの順序で、読み取ったデータの流れ自体を追跡しているわけではありません。
止めているのも SendEmail という1本の経路だけなので、別の送信手段になるツールがあるなら、それらもポリシーの対象に含める必要があります。
回数の上限も同じ要領で、count_within などの集計で書けます。
ただし temporal 条件は、いま判定しようとしているリクエスト自身も履歴に含めて評価されます。
集計では自分も1回と数えるため、「3回までは許可」のつもりで >= 3 と書くと3回目の要求で止まります。
しきい値の境界は replay で確かめておくのがよさそうです。
Cedar への変換
Cedar 互換の拡張であることは lower コマンドで確認することができます。
Dogwood のポリシーを Cedar に変換するコマンドです。
dogwood lower policy.dw --policy-schema schema.cedarschema --emit cedar-policies
permit(principal, action, resource) when { true };
@id("no_send_after_secret_read")
forbid(principal, action == Agent::Action::"SendEmail", resource) when { context.policy_1__temporal_0 };
temporal 条件が丸ごと context.policy_1__temporal_0 という Cedar の context フィールドに置き換わりました。
評価エンジンは Cedar のままで、Dogwood がイベント履歴からこのフィールドの真偽を計算して渡します。
temporal 条件を含まないポリシーが Cedar と同じ判定になるのも、評価そのものを Cedar が行っているからです。
「アプリケーション側で履歴を集計して context に渡す」という先ほどの実装とやっていることは同じです。
その置き場所がアプリケーションから言語と実行基盤へ移っています。
AgentCore でどう使えるか
ここまで動かしてきた参照実装に対して、本番での実行環境にあたるのが Amazon Bedrock AgentCore です。
Dogwood の temporal 条件は、AgentCore のポリシー機能(temporal policies)としてマネージドに実行できます。
対応リージョンには東京(ap-northeast-1)も含まれます。
適用されるのは AgentCore Gateway です。
Gateway は MCP ツール呼び出しやエージェント間(A2A)呼び出しの入り口なので、Gateway を通る対応済みの呼び出しには、エージェントのコードの外側から temporal policy を強制できます。
履歴は policy session という単位で扱われます。
呼び出し側のアプリケーションが維持するセッション ID ごとに、AgentCore がイベント(先ほど replay で再生したトレースにあたるもの)を記録し、リクエストごとに評価します。
記録されるイベントの種類は呼び出しの結果で決まり、成功した呼び出しは response、ポリシーに拒否された、またはツールがエラーを返した呼び出しは error になります。
今回のポリシーのように response を参照する条件は成功した呼び出しにしかマッチしないので、拒否や失敗に終わった読み取りでは送信禁止の条件は成立しません。
既存の Cedar ポリシーは修正なしでそのまま動き、いまの認可を保ったまま temporal policy を追加できます。
temporal policies の発表ブログでは、ワークフローの順序強制、人間の承認と実行の管理(1承認につき1実行)、セッション内の累積取引額の上限など、エージェントごとにコードへ作り込んでいた安全機構をポリシーへ置き換える適用パターンが紹介されています。
エージェントのツールが MCP で定義されているなら、tools/list のマニフェストから Cedar のアクションスキーマを生成する機能(dogwood schema mcp)も用意されています。
利用前に知っておきたいこと
履歴を扱える対価として、Cedar がステートレスであることで得ていた利点は手放すことになります。
- 認可がステートフルになります。イベント履歴の保持が必要で、評価時間も履歴の長さに依存します
validateでの構文、型、スキーマの検証はできますが、temporal 条件に対しては、Cedar が備える自動推論による解析(ポリシーの形式的な検証ツール群)がまだ使えません
- temporal 条件の時間ウィンドウは「過去1時間」のような相対指定だけで、毎日0時を境界にするような絶対時刻ベースの履歴ウィンドウは未対応です。現在時刻だけを見る判定なら、通常の Cedar 条件で
context.system.nowを使えます
まとめ
Dogwood の概要を、参照実装をローカルで動かしながら確認しました。
- Dogwood は Cedar に temporal 条件を足した Cedar 互換の拡張言語で、エージェントのツール呼び出しの順序、回数、組み合わせを宣言的に制御できる
- 参照実装の CLI(
validate/replay/lower)だけで、AWS アカウントなしに挙動を確かめられた。「機密を読んだ後の送信禁止」が7イベントのトレースで再現でき、同じリクエストでも履歴次第で判定が変わることを確認できた
lowerで見ると temporal 条件は Cedar の context フィールドとなっており、Cedar の実行系を活かす設計になっている
- 本番では AgentCore Gateway の temporal policies として使え、既存の Cedar ポリシーと共存できる
- 対価としてステートフルになり、temporal 条件には Cedar の自動推論解析も使えない。しきい値の境界(temporal 条件の評価が現在のリクエストを含む)など、
replayでの事前確認が推奨されそう
これまでアプリケーションコードの条件分岐に埋もれていたガードレールを、レビューと監査ができるポリシーの形に切り出せるのは、エージェントを本番運用に近づけるうえで効いてくるはずです。
次は AgentCore Gateway 側で temporal policy を設定し、実際のエージェントのセッションに対してどう効くかを試してみようと思います。
原文を表示
先端技術開発グループ(WAND)の伊賀です。**
2026年8月、AWS が Dogwood** というオープンソースのポリシー言語を発表しました(Introducing Dogwood: runtime verification for AI agents)。
AI エージェントのツール呼び出しを制御するための言語で、同日に発表された Amazon Bedrock AgentCore の temporal policies の基盤にもなっています(Securing AI agents with temporal policies in Amazon Bedrock AgentCore)。
エージェントのガードレールを作っていると、「この呼び出し単体は正当だが、この流れで呼ばれるのはまずい」という制約に行き当たります。
承認を取ってから実行してほしい、送金は1時間に数回までにしたい、機密情報を読んだ後は外部に送信してほしくない。
どれも1回ごとの認可判定だけでは書けず、これまでは履歴の管理ごと、エージェントのアプリケーションコードに条件分岐として作り込む必要がありました。
Dogwood は、この種の制約をポリシーとして宣言的に書けるようにする言語です。
本記事では、次の内容に絞って Dogwood の概要をつかみます。
- なぜ Dogwood が必要なのか、Cedar と何が違うのか
- 参照実装をローカルで動かして、履歴に応じて判定が変わる様子を確認する
- Amazon Bedrock AgentCore でどう使えるか
なぜ単発の認可では足りないのか
AWS には既に Cedar というオープンソースの認可ポリシー言語があります。
principal(誰が)、action(何を)、resource(何に対して)の組に permit / forbid のルールを書き、リクエストごとに Allow / Deny を返すもので、Amazon Verified Permissions や AgentCore のポリシーエンジンで使われています。
Cedar の判定は Point-in-Time Authorization です。
各リクエストを独立に評価し、過去に何があったかは参照しません。
この性質のおかげで判定はステートレスで速く、自動推論によるポリシー解析(到達しえない条件の検出など)も可能になっています。
ところが、冒頭に挙げた制約をこのモデルで書こうとすると難しいことがわかります。
- 「株式の売却は、1時間以内に同じ内容の承認が取れているときだけ許可」
- 「送金は1時間に5回まで」
- 「機密ドキュメントを読んだ後は、外部へのメール送信を禁止」
どれも、いま来たリクエストだけを見ても判定できません。
判定の材料が「過去にどんな呼び出しがあったか」にあるからです。
エージェントは複数のツール呼び出しを連ねてタスクを進めるので、単体では正当な呼び出しが、順序、回数、組み合わせ次第で危険になります。
もっとも、Cedar でも、アプリケーション側で履歴を集計して context として渡せば、判定自体はできます。
ただ、その履歴の記録、集計、現在のリクエストとの相関を自前で作る部分こそが、冒頭に書いたアプリケーションコードに作り込む必要があった部分なのです。
Dogwood は、この履歴の扱いを言語と実行基盤の側で引き受け、連続した呼び出しに対する制御をポリシーとして書けるようにします。
Cedar に temporal 条件を足した Dogwood
Dogwood は Cedar の構文をそのまま受け入れたうえで、イベント履歴を参照する temporal 条件を追加した、Cedar 互換の拡張言語です。
Dogwood の発表ブログにある例を1つ見てみます。
permit ( principal, action == AgentCore::Action::"SellShares", resource )
when temporal {
formerly within 1h AgentCore::Action::"ApproveSale"::response{
input.stock: context.input.stock,
input.shares: context.input.shares,
output.approved: true
}
};
「過去1時間以内に、同じ銘柄、同じ株数で approved: true の ApproveSale レスポンスがあったときに限り、SellShares を許可する」と読みます。
when temporal { ... } の中だけが Dogwood の拡張で、外側は Cedar そのものです。
既存の Cedar ポリシーは構文上そのまま有効な Dogwood ポリシーであるだけでなく、Dogwood はそれらに Cedar と同じ判定を返します。
いま動いているポリシーを書き換えずに、履歴条件が必要な箇所へ段階的に足すことができます。
履歴の参照には、formerly(過去に一度でもあったか)のほかに、count_within(期間内の回数)、count_distinct_within(期間内の異なる値の個数)、sum_within(期間内の合計値)といった集計が標準ライブラリとして用意されています。
両者の違いは次のとおりです。
| Cedar | Dogwood | |
|---|---|---|
| 判定の単位 | 1リクエストを独立に評価 | イベント履歴を踏まえて評価 |
| 状態 | ステートレス | ステートフル(履歴を保持) |
| 自動推論による解析 | あり | temporal 条件は未対応 |
| 主な用途 | アプリケーションの認可全般 | エージェントのツール呼び出し制御 |
仕様と参照実装(Rust 製)は Apache 2.0 で公開されています(dogwood-policy/dogwood)。
ローカルで動かす
参照実装には dogwood という CLI が含まれていて、ポリシーの検証(validate)と、イベント履歴を模したファイルの再生(replay)ができます。
エージェントの実行を用意しなくても、「この履歴のとき、この呼び出しはどう判定されるか」を机上で確かめられます。
セットアップ
Rust のツールチェーン(cargo)が必要です。
手元では Rust 1.97.1 で確認しました。
git clone https://github.com/dogwood-policy/dogwood.git
cd dogwood
cargo build --release -p amzn-dogwood-cli
./target/release/dogwood --version
# dogwood 1.0.0
ビルドには数分かかります。
以降のコマンドは、ビルドした target/release/dogwood にパスが通っている前提で書きます。
機密ドキュメントを読んだ後の送信を禁止する
題材には、エージェントの事故としてよく語られるパターンを選びます。
ドキュメントの読み取りとメール送信の2つのツールを持つエージェントが、外部から注入された指示(プロンプトインジェクション)に従って、機密情報を読み取り外部へ送信してしまうという流れです。
これに「機密ドキュメントを読んだ後は、メール送信を禁止する」というガードレールをかけてみます。
ファイルを3つ用意します。
まず、エージェントのツールを Cedar のアクションとして宣言するスキーマです(schema.cedarschema)。
namespace Agent {
type ReadDocumentInput = { doc: String };
type SendEmailInput = { to: String };
entity Gateway;
entity OAuthUser = { id: String } tags String;
action "ReadDocument" appliesTo {
principal: [OAuthUser],
resource: [Gateway],
context: { input: ReadDocumentInput }
};
action "SendEmail" appliesTo {
principal: [OAuthUser],
resource: [Gateway],
context: { input: SendEmailInput }
};
}
次にポリシーです(policy.dw)。
Cedar と同じくデフォルト拒否で、forbid は permit に常に勝つので、「基本は許可、条件に触れたら禁止」の2本立てで書けます。
// ツール呼び出しは基本すべて許可する
permit (principal, action, resource);
// ただし、機密ドキュメントの読み取りに成功した後のメール送信は禁止する
@id("no_send_after_secret_read")
forbid (
principal,
action == Agent::Action::"SendEmail",
resource
)
when temporal {
formerly within 24h Agent::Action::"ReadDocument"::response{ input.doc: "customer-secrets" }
};
temporal 条件が参照しているのは ReadDocument の response、つまり読み取りで実際に返ってきたイベントです。
読み取りを要求した時点で止めたいなら、代わりに ::request を参照します。
最後に、エージェントのツール呼び出しを模したトレースです(trace.log)。
1行が1イベントで、タイムスタンプ(秒)、呼び出しの主体、アクションと入力を記録します。
イベントには要求(request)、成功の結果(response)、失敗(error)の3種類があり、判定を求めるのは request だけです(response と error は履歴にだけ残ります)。
このトレースでは request と response の2種類を使います。
@0 scope(principal: Agent::OAuthUser::"assistant", resource: Agent::Gateway::"gw1") Agent::Action::"SendEmail"::request(input: { to: "partner@example.com" }, callerPrincipal: Agent::OAuthUser::"assistant", callerResource: Agent::Gateway::"gw1")
@10 scope(principal: Agent::OAuthUser::"assistant", resource: Agent::Gateway::"gw1") Agent::Action::"ReadDocument"::request(input: { doc: "public-faq" }, callerPrincipal: Agent::OAuthUser::"assistant", callerResource: Agent::Gateway::"gw1")
@15 scope(principal: Agent::OAuthUser::"assistant", resource: Agent::Gateway::"gw1") Agent::Action::"ReadDocument"::response(input: { doc: "public-faq" }, callerPrincipal: Agent::OAuthUser::"assistant", callerResource: Agent::Gateway::"gw1")
@20 scope(principal: Agent::OAuthUser::"assistant", resource: Agent::Gateway::"gw1") Agent::Action::"SendEmail"::request(input: { to: "partner@example.com" }, callerPrincipal: Agent::OAuthUser::"assistant", callerResource: Agent::Gateway::"gw1")
@30 scope(principal: Agent::OAuthUser::"assistant", resource: Agent::Gateway::"gw1") Agent::Action::"ReadDocument"::request(input: { doc: "customer-secrets" }, callerPrincipal: Agent::OAuthUser::"assistant", callerResource: Agent::Gateway::"gw1")
@35 scope(principal: Agent::OAuthUser::"assistant", resource: Agent::Gateway::"gw1") Agent::Action::"ReadDocument"::response(input: { doc: "customer-secrets" }, callerPrincipal: Agent::OAuthUser::"assistant", callerResource: Agent::Gateway::"gw1")
@40 scope(principal: Agent::OAuthUser::"assistant", resource: Agent::Gateway::"gw1") Agent::Action::"SendEmail"::request(input: { to: "partner@example.com" }, callerPrincipal: Agent::OAuthUser::"assistant", callerResource: Agent::Gateway::"gw1")
並んでいるのは、メール送信、公開ドキュメントの読み取り、メール送信、機密ドキュメント(customer-secrets)の読み取り、メール送信という流れで、各読み取りは request と response の2行で記録しています。
response イベントは、読み取りが成功したことにして手で書いたものです。
replay はイベント履歴を再生するだけで、実際にツールを実行するわけでも、イベントが本物かを検証するわけでもありません。
callerPrincipal などの付帯フィールドは、デフォルトのイベントスキーマに合わせて指定しています。
ポリシーを検証して、トレースを再生します。
dogwood validate policy.dw --policy-schema schema.cedarschema
# OK: validation passed with no errors or warnings.
dogwood replay policy.dw --policy-schema schema.cedarschema --trace trace.log
@0 (time point 0): ALLOW [rules: 0]
@10 (time point 1): ALLOW [rules: 0]
@20 (time point 2): ALLOW [rules: 0]
@30 (time point 3): ALLOW [rules: 0]
@40 (time point 4): DENY [rules: 1]
@20 と @40 は宛先まで同じメール送信リクエストですが、判定が分かれました。
間に挟まったのが公開ドキュメントの読み取りだけなら許可され(@20)、機密ドキュメントの読み取りを挟むと止まります(@40)。
ポリシーもリクエストも同じで、履歴だけが判定を変えています。
これが、単発の認可判定だけでは書けなかった部分です。
なお、このポリシーが見ているのはドキュメント ID と呼び出しの順序で、読み取ったデータの流れ自体を追跡しているわけではありません。
止めているのも SendEmail という1本の経路だけなので、別の送信手段になるツールがあるなら、それらもポリシーの対象に含める必要があります。
回数の上限も同じ要領で、count_within などの集計で書けます。
ただし temporal 条件は、いま判定しようとしているリクエスト自身も履歴に含めて評価されます。
集計では自分も1回と数えるため、「3回までは許可」のつもりで >= 3 と書くと3回目の要求で止まります。
しきい値の境界は replay で確かめておくのがよさそうです。
Cedar への変換
Cedar 互換の拡張であることは lower コマンドで確認することができます。
Dogwood のポリシーを Cedar に変換するコマンドです。
dogwood lower policy.dw --policy-schema schema.cedarschema --emit cedar-policies
permit(principal, action, resource) when { true };
@id("no_send_after_secret_read")
forbid(principal, action == Agent::Action::"SendEmail", resource) when { context.policy_1__temporal_0 };
temporal 条件が丸ごと context.policy_1__temporal_0 という Cedar の context フィールドに置き換わりました。
評価エンジンは Cedar のままで、Dogwood がイベント履歴からこのフィールドの真偽を計算して渡します。
temporal 条件を含まないポリシーが Cedar と同じ判定になるのも、評価そのものを Cedar が行っているからです。
「アプリケーション側で履歴を集計して context に渡す」という先ほどの実装とやっていることは同じです。
その置き場所がアプリケーションから言語と実行基盤へ移っています。
AgentCore でどう使えるか
ここまで動かしてきた参照実装に対して、本番での実行環境にあたるのが Amazon Bedrock AgentCore です。
Dogwood の temporal 条件は、AgentCore のポリシー機能(temporal policies)としてマネージドに実行できます。
対応リージョンには東京(ap-northeast-1)も含まれます。
適用されるのは AgentCore Gateway です。
Gateway は MCP ツール呼び出しやエージェント間(A2A)呼び出しの入り口なので、Gateway を通る対応済みの呼び出しには、エージェントのコードの外側から temporal policy を強制できます。
履歴は policy session という単位で扱われます。
呼び出し側のアプリケーションが維持するセッション ID ごとに、AgentCore がイベント(先ほど replay で再生したトレースにあたるもの)を記録し、リクエストごとに評価します。
記録されるイベントの種類は呼び出しの結果で決まり、成功した呼び出しは response、ポリシーに拒否された、またはツールがエラーを返した呼び出しは error になります。
今回のポリシーのように response を参照する条件は成功した呼び出しにしかマッチしないので、拒否や失敗に終わった読み取りでは送信禁止の条件は成立しません。
既存の Cedar ポリシーは修正なしでそのまま動き、いまの認可を保ったまま temporal policy を追加できます。
temporal policies の発表ブログでは、ワークフローの順序強制、人間の承認と実行の管理(1承認につき1実行)、セッション内の累積取引額の上限など、エージェントごとにコードへ作り込んでいた安全機構をポリシーへ置き換える適用パターンが紹介されています。
エージェントのツールが MCP で定義されているなら、tools/list のマニフェストから Cedar のアクションスキーマを生成する機能(dogwood schema mcp)も用意されています。
利用前に知っておきたいこと
履歴を扱える対価として、Cedar がステートレスであることで得ていた利点は手放すことになります。
- 認可がステートフルになります。イベント履歴の保持が必要で、評価時間も履歴の長さに依存します
- validate での構文、型、スキーマの検証はできますが、temporal 条件に対しては、Cedar が備える自動推論による解析(ポリシーの形式的な検証ツール群)がまだ使えません
- temporal 条件の時間ウィンドウは「過去1時間」のような相対指定だけで、毎日0時を境界にするような絶対時刻ベースの履歴ウィンドウは未対応です。現在時刻だけを見る判定なら、通常の Cedar 条件で context.system.now を使えます
まとめ
Dogwood の概要を、参照実装をローカルで動かしながら確認しました。
- Dogwood は Cedar に temporal 条件を足した Cedar 互換の拡張言語で、エージェントのツール呼び出しの順序、回数、組み合わせを宣言的に制御できる
- 参照実装の CLI(validate / replay / lower)だけで、AWS アカウントなしに挙動を確かめられた。「機密を読んだ後の送信禁止」が7イベントのトレースで再現でき、同じリクエストでも履歴次第で判定が変わることを確認できた
- lower で見ると temporal 条件は Cedar の context フィールドとなっており、Cedar の実行系を活かす設計になっている
- 本番では AgentCore Gateway の temporal policies として使え、既存の Cedar ポリシーと共存できる
- 対価としてステートフルになり、temporal 条件には Cedar の自動推論解析も使えない。しきい値の境界(temporal 条件の評価が現在のリクエストを含む)など、replay での事前確認が推奨されそう
これまでアプリケーションコードの条件分岐に埋もれていたガードレールを、レビューと監査ができるポリシーの形に切り出せるのは、エージェントを本番運用に近づけるうえで効いてくるはずです。
次は AgentCore Gateway 側で temporal policy を設定し、実際のエージェントのセッションに対してどう効くかを試してみようと思います。
関連記事
今日のまとめ
AIデイリーブリーフで今日の重要ニュースをまとめ読み