AWS DevOps Agent のスキルで references/ に置いた情報がどう使われるのかを調べてみた

AWS DevOps Agent のスキルで references/ に置いた情報がどう使われるのかを調べてみた

必要になった時点で個別に読み込まれました。SKILL.md に参照指示は不要。
2026.08.20

こんにちは、製造ビジネステクノロジー部の若槻です。

AWS DevOps Agentスキルは、SKILL.md のほかに references/ ディレクトリを持てます。

以前スキルを GitHub リポジトリからインポートした記事で、references/ 配下のファイルがディレクトリごと取り込まれ、エージェントがそれを読み込んで正しく回答することは確認しました。

ただあのときは references/ があるパターンしか試しておらず、置いた情報がどう扱われるのかまでは追えていませんでした。ファイルがなかったらどう答えるのか。SKILL.md に書いた参照指示は必要なのか。中身は常にコンテキストに載っているのか。

そこで今回は、references/ の有無と参照指示の有無だけを変えた同一内容のスキルを 3 つ用意して、同じ質問への回答を比較してみました。

検証はすべて Operator Web App のチャットで行っています。インシデントレスポンスの調査で同じ挙動になるかは試していません。

またチャットの中でも、AWS 環境やリポジトリを見に行かせていません。答えの出どころをスキルの中身だけに限定して、参照ファイルの有無以外の変数が入らないようにするためです。

結論

  • 参照ファイルにしか書いていない情報を、エージェントは読み込んで正確に答えました。表の該当行まで出どころとして示します
  • references/ がない場合、同じ質問に対して「記載なし」と答えました。値を推測で埋めることはありませんでした
  • SKILL.md 本文に「参照すること」と書いていなくても参照ファイルは読み込まれます。スキルを読み込むと戻り値にファイル一覧(resource_manifest)が含まれ、エージェントはそれを見て必要なファイルを取りに行きます
  • つまり references/ の中身は常時コンテキストに展開されるのではなく、必要になった時点で個別に読み込まれます
  • aws-cli 2.36.24 では aws devops-agent create-asset などのアセット系サブコマンドが使えるようになっていました。スキルのインポートも有効・無効の切り替えも CLI で完結します
  • スキルを有効化した直後の最初のチャットで、スキルの読み込みが Skill not found で失敗することがありました。作り直さず、もう一度質問すれば通ります

検証の設計

references/ の効果を見るには、参照ファイルにしか存在しない情報を用意する必要があります。リポジトリのコードから読み取れる情報だと、参照ファイルがなくてもエージェントがリポジトリを直接読んで答えてしまい、差が出ません。

そこで、リポジトリのどこにも書かれていない「検出結果の一次対応ポリシー」(重大度ごとの対応期限とエスカレーション先の表)を参照ファイルとして用意しました。

そのうえで、次の 3 パターンのスキルを作りました。

# スキル名 SKILL.md の参照指示 references/ の実ファイル
A severity-policy-with-references あり あり
B severity-policy-without-references なし なし
C severity-policy-no-pointer なし あり
  • A と C の差は SKILL.md の指示文 1 行だけ
  • B と C の差は参照ファイルの有無だけ

SKILL.mdname を除いて 3 つとも同一です。差分は次のとおりで、余計な変数が入っていないことを確認しています。

$ diff skills/severity-policy-with-references/SKILL.md skills/severity-policy-no-pointer/SKILL.md
2c2
< name: severity-policy-with-references
---
> name: severity-policy-no-pointer
26,27d25
< 重大度別の対応期限とエスカレーション先、サービス別の読み替えルール、例外条件は `references/severity-policy.md` の表を参照すること。
<

$ diff skills/severity-policy-without-references/SKILL.md skills/severity-policy-no-pointer/SKILL.md
2c2
< name: severity-policy-without-references
---
> name: severity-policy-no-pointer

description も 3 つとも同一にしてあります。スキルが選ばれる条件を揃えるためです。

検証に使ったスキル

ディレクトリ構成は次のとおりです。

skills/
├── severity-policy-with-references/     # A
│   ├── SKILL.md
│   └── references/
│       └── severity-policy.md
├── severity-policy-without-references/  # B
│   └── SKILL.md
└── severity-policy-no-pointer/          # C
    ├── SKILL.md
    └── references/
        └── severity-policy.md

A の SKILL.md です。手順の骨格だけを書いて、具体的な期限やエスカレーション先の値は一切書いていません

skills/severity-policy-with-references/SKILL.md
---
name: severity-policy-with-references
description: security-infra リポジトリで有効化しているセキュリティ検出サービス(GuardDuty / Security Hub / Inspector / Macie / AWS Config)の検出結果に対する一次対応ポリシー。検出結果を見つけたときに、いつまでに対応すべきか、誰にエスカレーションすべきか、対応不要と判断してよいかを回答するときに使用する。
---

# 検出結果の一次対応

security-infra リポジトリで有効化しているセキュリティ検出サービスの検出結果について問われた場合は、この手順に従って回答すること。

## ステップ 1: 検出結果の重大度と発生元を確認する

検出結果の重大度(CRITICAL / HIGH / MEDIUM / LOW / INFORMATIONAL)、検出したサービス、発生元リージョンを確認する。

## ステップ 2: 一次対応の期限を判断する

重大度に応じて定められた期限内に一次対応を行う。期限は重大度ごとに異なるため、必ず定められた値に従うこと。

## ステップ 3: エスカレーション先を判断する

重大度に応じた一次エスカレーション先と、必要な場合の二次エスカレーション先を判断する。

## ステップ 4: 例外に当てはまらないか確認する

対応不要と判断してよい例外条件に当てはまらないかを確認する。当てはまる場合は、その理由を記録する。

重大度別の対応期限とエスカレーション先、サービス別の読み替えルール、例外条件は `references/severity-policy.md` の表を参照すること。

## ステップ 5: 報告する

以下を含めて報告する。

1. 検出結果の重大度と検出したサービス
2. 一次対応の期限
3. 一次エスカレーション先と二次エスカレーション先
4. 例外に当てはまる場合はその条件と理由

参照ファイルには、具体的な値をすべてここだけに書いています。A と C で完全に同一のファイルです。

skills/severity-policy-with-references/references/severity-policy.md
# 検出結果の一次対応ポリシー

security-infra リポジトリで有効化しているセキュリティ検出サービスの検出結果に対する、一次対応の期限とエスカレーション先を定めた表。

## 重大度別の一次対応期限とエスカレーション先

| 重大度 | 一次対応の期限 | 一次エスカレーション先 | 二次エスカレーション先 |
|---|---|---|---|
| CRITICAL | 検知から 1 時間以内 | セキュリティ当番 | 部門責任者 |
| HIGH | 検知から 4 時間以内 | セキュリティ当番 | セキュリティ推進チーム |
| MEDIUM | 翌営業日の 12 時まで | セキュリティ推進チーム | なし |
| LOW | 週次レビューで扱う | 週次レビュー | なし |
| INFORMATIONAL | 対応不要 | なし | なし |

## サービス別の読み替えルール

| サービス | 読み替えルール |
|---|---|
| Amazon GuardDuty | `Recon:EC2/Portscan` は、送信元が検証環境の VPC の場合のみ重大度を 1 段下げて扱う |
| AWS Security Hub | `us-east-1` 由来の検出結果が `ap-northeast-1` に集約されて二重に現れた場合は、発生元リージョン側の 1 件のみを対応対象とする |
| Amazon Inspector | 稼働していないコンテナイメージに対する検出結果は、次回ビルドまで LOW として扱う |
| Amazon Macie | 検証用のダミーデータを格納しているバケットの検出結果は対応不要とする |

## 例外として扱ってよい条件

- 検出結果の発生リソースが、24 時間以内に削除予定であることが確認できている場合
- 同一リソースに対する同一タイプの検出結果を、直近 7 日以内に対応済みである場合
- 検出結果が AWS Config のルール評価の遅延に起因すると確認できた場合

上記のいずれかに当てはまる場合は、対応不要と判断した理由を記録して週次レビューで共有する。

AWS CLI からインポートできるようになっていた

前回の記事では、アセット管理の API に対応するサブコマンドが手元の aws-cli 2.34.35 に含まれておらず、Operator Web App のフォームからインポートしていました。

今回 2.36.24 で確認したところ、create-asset などが使えるようになっていました

$ aws --version
aws-cli/2.36.24 Python/3.14.6 Darwin/25.5.0 exe/arm64

3 つのスキルを CLI からインポートします。他のスキルと混ざらないよう、まず statusINACTIVE にして作成します。

BASE=https://github.com/<owner>/security-infra/tree/main/skills

for s in severity-policy-with-references \
         severity-policy-without-references \
         severity-policy-no-pointer; do
  aws devops-agent create-asset \
    --agent-space-id <agent-space-id> \
    --asset-type skill \
    --metadata '{"agent_types":["GENERIC"],"status":"INACTIVE"}' \
    --content "{\"sourceUrl\":{\"url\":\"$BASE/$s\"}}"
done

--metadatanamedescription を渡していない点に注目です。Web App のフォームと同じで、これらは SKILL.md の frontmatter からサービス側が読み取ります。

agent_types に指定した GENERIC は、Web App の詳細画面では「すべてのエージェント」と表示される値です。ドキュメントによると、スキルはエージェントタイプ(Generic / On-demand / Incident Triage / Incident RCA / Incident Mitigation / Evaluation)を指定して絞り込めます。今回はチャットからしか質問していないので、インシデントトリアージなど調査系のエージェントで同じように読み込まれるかは確認していません。

取り込まれたファイルは list-asset-files で確認できます。設計どおり、B だけ references/ が入っていません。

$ aws devops-agent list-asset-files \
    --agent-space-id <agent-space-id> \
    --asset-id <asset-id-A> \
    --query 'items[].path'
[
    "SKILL.md",
    "references/severity-policy.md"
]

$ aws devops-agent list-asset-files \
    --agent-space-id <agent-space-id> \
    --asset-id <asset-id-B> \
    --query 'items[].path'
[
    "SKILL.md"
]

検証中は 1 つだけを ACTIVE にして、残りを INACTIVE にします。切り替えも CLI から行えます。

aws devops-agent update-asset \
  --agent-space-id <agent-space-id> \
  --asset-id <asset-id> \
  --metadata '{"agent_types":["GENERIC"],"status":"ACTIVE"}'

なお --metadata は差分更新ではなく置き換えなので、agent_types も一緒に渡す必要があります。

3 パターンに同じ質問を投げる

有効なスキルを 1 つずつ入れ替えながら、Operator Web App のチャットで同じ質問を投げました。

security-infra の運用ポリシーでは、GuardDuty の重大度 CRITICAL の検出結果について、一次対応の期限、一次エスカレーション先、二次エスカレーション先はそれぞれ何と定められていますか。AWS 環境の調査は不要です。根拠にした情報の出どころも示してください。

期待する答えは、参照ファイルの CRITICAL 行にある「検知から 1 時間以内 / セキュリティ当番 / 部門責任者」です。

「AWS 環境の調査は不要です」と添えているのは、GuardDuty の検出結果を実際に取りに行かせないためです。結果として 3 パターンとも、呼ばれたツールはスキルの読み込みと参照ファイルの読み込みだけでした。AWS の API もリポジトリも一度も参照していないので、回答の出どころはスキルの中身に限られます

A: 参照指示あり・参照ファイルあり → 正確に答えた

エージェントはスキルを読み込んだあと、references/severity-policy.md を読み込みました。


「ポリシーの詳細が references/severity-policy.md に記載されているので読み込みます」と述べてから参照ファイルを読んでいる

ツール呼び出しの引数は次のとおりです(list-journal-records から抜粋。以降も同様)。

{ "skill_id": "severity-policy-with-references" }
{ "skill_id": "severity-policy-with-references", "path": "references/severity-policy.md" }

回答は参照ファイルどおりでした。

severity-policy-with-references スキルの references/severity-policy.md に基づいてお答えします。

項目 定め
一次対応の期限 検知から 1 時間以内
一次エスカレーション先 セキュリティ当番
二次エスカレーション先 部門責任者

出どころ: severity-policy-with-references スキルに同梱されたリファレンスファイル references/severity-policy.md「重大度別の一次対応期限とエスカレーション先」表の CRITICAL 行。

3 項目すべて一致しているうえに、出どころもファイル名と表の行まで示しています。さらに、質問していないサービス別の読み替えルールにも触れていました。

補足として、GuardDuty 固有の読み替えルールが 1 件あります。Recon:EC2/Portscan の検出結果は、送信元が検証環境の VPC である場合に限り、重大度を 1 段下げて扱うと定められています。CRITICAL の検出結果がこのルールに該当する場合は HIGH として扱われるため、期限・エスカレーション先も HIGH の定め(4 時間以内 / セキュリティ当番 → セキュリティ推進チーム)が適用されます。

参照ファイルを部分的に拾ったのではなく、ファイル全体を読んで関連する記述を組み合わせていることが分かります。

B: 参照ファイルなし → 「記載なし」と答えた

同じ質問に対して、エージェントはまずスキルを読み込み、値が見つからないことに気付きます。そのうえで別のスキル(security-infra-guardrails)も確認し、最終的に答えられないと報告しました。


スキルの読み込みが 2 回だけで、参照ファイルの読み込みは一度も現れない

パネルの見出しが A・C の「1 個のツール · 1 個のスキル 使用済み」ではなく「2 個のスキル 使用済み」になっている点も分かりやすいところです。ツール呼び出しにも path が現れておらず、参照ファイルを読む動きが一度も起きていません。

{ "skill_id": "severity-policy-without-references" }
{ "skill_id": "security-infra-guardrails" }

両スキルの SKILL.md には手順の骨格のみ記載されており、GuardDuty CRITICAL の具体的な期限・エスカレーション先の数値はスキル本文に含まれていません。

severity-policy-without-references スキルは「期限や escalation 先は重大度ごとに定められている」と述べていますが、その実体は別途ポリシードキュメントに存在するはずです。スキルのリソースマニフェストには SKILL.md しか記載がなく、参照先ドキュメントへのリンクも含まれていないため、現在読み込めるスキル情報の範囲内では、GuardDuty CRITICAL に対する一次対応期限・一次エスカレーション先・二次エスカレーション先の具体的な値を確認することができません。

項目 状況
一次対応期限 記載なし — スキルに「重大度ごとに期限が異なる」とある旨のみ
一次エスカレーション先 記載なし
二次エスカレーション先 記載なし


3 項目すべて「記載なし」。情報の出どころとして参照した SKILL.md を 2 つ挙げている

ここが一番確かめたかったところです。SKILL.md には「重大度に応じて定められた期限内に一次対応を行う」と書いてあるので、それらしい値をでっち上げる余地は十分にありました。実際にはそうならず、「記載なし」と明示したうえで次のステップを提案してきました。

具体的なポリシー値を確認するには、以下のいずれかが必要です。

  • security-infra リポジトリのポリシードキュメントを直接参照する(例:docs/runbooks/ 配下に SLA/エスカレーション定義がある場合)
  • スキルの管理者にポリシードキュメントをスキルリソースとして追加してもらう

A と B は SKILL.md の差が「参照ファイルを見よ」の 1 行だけで、あとは参照ファイルがあるかないかだけです。回答の中身がここまで変わるのは、参照ファイルの内容が実際に使われている証拠と言えます。

C: 参照指示なし・参照ファイルあり → 指示がなくても読んだ

これが個人的に一番意外でした。SKILL.md から references/severity-policy.md を参照するよう指示していないにもかかわらず、エージェントは自発的に読み込みました。


read_skill_resource に skill_id と path が渡っている。SKILL.md には参照指示を書いていない

トレースの各ステップは展開するとツール名と入力が確認できます。スキルの読み込みが skill_read、参照ファイルの読み込みが read_skill_resource です。

{ "skill_id": "severity-policy-no-pointer" }
{ "skill_id": "severity-policy-no-pointer", "path": "references/severity-policy.md" }

読み込みの前には「ポリシーの詳細が別ファイルに記載されているので、参照ポリシー文書を読み込みます。」と、自分で判断した旨を述べていました。

回答は A と同じく正確でした。

項目 定め
一次対応の期限 検知から 1 時間以内
一次エスカレーション先 セキュリティ当番
二次エスカレーション先 部門責任者

根拠となった情報の出どころ

スキル severity-policy-no-pointer に付属するリソースファイル references/severity-policy.md(security-infra リポジトリの運用ポリシーを定義したドキュメント)の「重大度別の一次対応期限とエスカレーション先」表の CRITICAL 行に基づいています。


A と同じ値を返し、出どころも参照ファイルの表の行まで示している

SKILL.md に参照の導線を書いておくのは丁寧ですが、必須ではないということです。

結果まとめ

# SKILL.md の参照指示 references/ の実ファイル 参照ファイルを読んだか 回答
A あり あり 読んだ 正確(1 時間以内 / セキュリティ当番 / 部門責任者)
B なし なし 読むファイルがない 記載なしと報告(値の捏造なし)
C なし あり 読んだ 正確(A と同じ)

なぜ指示なしでも読まれるのか

C の挙動が気になったので、裏側で何が渡っているのかを確認しました。

Web App のトレースは各ステップを展開するとツール名と 入力 は見られますが、出力 は表示されません。戻り値まで見るには list-journal-records を使います。ツール呼び出しとその戻り値まで含まれています。

aws devops-agent list-journal-records \
  --agent-space-id <agent-space-id> \
  --execution-id <execution-id>

records[].content は JSON 文字列なので、パースして整形します。スキルを読み込んだツールの戻り値が次のようになっていました。

{
  "content": "---\nname: severity-policy-no-pointer\ndescription: ...\n---\n\n# 検出結果の一次対応\n\n...",
  "resource_manifest": [
    "SKILL.md",
    "references/severity-policy.md"
  ]
}

contentSKILL.md の全文、resource_manifestスキルが持っているファイルの一覧が入っています。エージェントは SKILL.md の本文を読むと同時に、「このスキルには references/severity-policy.md というファイルがある」ことを知るわけです。

だから SKILL.md に参照指示がなくても、答えに足りない情報があればファイル名から見当をつけて取りに行けます。B の回答に「スキルのリソースマニフェストには SKILL.md しか記載がなく」という表現が出てきたのも、この一覧を見ていたからです。

実際のファイル読み込みは、read_skill_resource というスキル ID とパスを引数に取る別のツール呼び出しになっていました。

{
  "skill_id": "severity-policy-no-pointer",
  "path": "references/severity-policy.md"
}

つまり references/ の中身は最初からコンテキストに展開されているのではなく、必要と判断された時点で個別に読み込まれる構造です。

この構造は運用面でもありがたいところです。参照ファイルを増やしても、使われない限りトークンを消費しません。手順は SKILL.md に、分量のある一覧表や設定値は references/という分け方をしておけば、スキルを太らせても実行時のコストは膨らみにくいことになります。

その他に気付いたこと

有効化直後の最初のチャットが Skill not found になる

スキルの statusACTIVE に変えたあと、最初に質問したチャットでスキルの読み込みが失敗する現象が B と C の両方で起きました。UI では「1 個の失敗」バッジが付き、「スキルの読み込みでエラーが発生しました」と表示されます。


スキルの読み込みに失敗したあと、リポジトリの探索に切り替えている

journal records で戻り値を見ると、素直にそう書かれています。

Skill not found: severity-policy-no-pointer

スキル名自体はエージェントに渡っていて(回答の中でスキル名を正しく呼んでいる)、実体の取得だけが失敗している状態です。B では有効化の 40 秒後、C では 2 分後に発生し、いずれも新しいチャットで同じ質問をやり直すと成功しました

有効化から一定時間待てば解消するという類のものではなさそうなので、CLI や UI でスキルの状態を変えた直後は、1 回目が失敗しても作り直さずにもう一度試すのがよさそうです。

スキルとして読めなくてもリポジトリから読まれることがある

上記の C の失敗ケースは、実は最終的に正しい答えを返しました。スキルの読み込みに失敗したあと、エージェントがGitHub リポジトリのファイルを直接読みに行ったからです。

journal records に残っていたツール呼び出しがこちらです。

{
  "hostname": "github.com",
  "repository": "<owner>/security-infra",
  "path": "skills/severity-policy-no-pointer/references/severity-policy.md"
}

スキルはリポジトリからインポートしたものなので、同じ内容のファイルがリポジトリにも当然あります。ネイティブの GitHub 連携でそのリポジトリが読める状態だと、こちらの経路が使えてしまうわけです。

障害時のフォールバックとしては嬉しい挙動ですが、検証の設計としては注意が必要でした。今回 B のディレクトリには references/ をそもそも作っていないので、リポジトリを読まれても参照ファイルは存在せず、A/B の比較は成立しています。「参照ファイルなし」の条件を作るときは、リポジトリ側にもファイルを置かない必要があります。

ファイルの中身も CLI で確認できる

取り込まれたファイルの中身は get-asset-file で読めます。リポジトリを更新したあと、同期が反映されているかを確認するのに便利でした。

$ aws devops-agent get-asset-file \
    --agent-space-id <agent-space-id> \
    --asset-id <asset-id> \
    --path SKILL.md \
    --query 'file.content.text' --output text | head -5
---
name: severity-policy-with-references
description: security-infra リポジトリで有効化しているセキュリティ検出サービス(GuardDuty / Security Hub / Inspector / Macie / AWS Config)の検出結果に対する一次対応ポリシー。検出結果を見つけたときに、いつまでに対応すべきか、誰にエスカレーションすべきか、対応不要と判断してよいかを回答するときに使用する。
---

なお get-asset-content はスキル全体を zip の Base64 で返します。ファイル単位で見たいときは get-asset-file を使うほうが手軽です。

list-chats は呼び出し元のセッションごとに結果が変わる

チャットの一覧は list-chats で取得できますが、今回 Web App で作ったチャットは返ってきませんでした。返ってきたのは 2 週間前の 5 件だけで、--user-id を指定しても変わりません。このオプションは deprecated で無視されるとヘルプに明記されています。

This field is deprecated and will be ignored -- the service resolves user identity from the authenticated session.

--max-results を小さくすると nextToken が返り、並びは新しい順でした。ページネーションの取りこぼしではなく、本当にそこで一覧が止まっています。

CloudTrail で CreateChat の呼び出し元を確認すると理由が分かりました。list-chatsロールセッション名まで含めた呼び出し元の identity でスコープされています。

チャットの作成日 CreateChat の呼び出し元セッション
2 週間前の 5 件 assumed-role/<role>/<CLI と同じセッション名>
今回の 5 件 assumed-role/<role>/<Web App が発行したセッション名>

Web App は POST /authorizer/credentials で自前の認可エンドポイントからクレデンシャルを取得して API を呼びます。ログイン時に CreateOneTimeLoginSession が走り、<アクセスキーのプリンシパル ID>-<ユーザー名> 形式のセッション名が発行されるため、CLI から assume したセッションとは identity が別物になります。古い 5 件が見えたのは、当時のログインが既存のコンソールセッションをそのまま引き継いでいて、CLI と同じセッション名だったからです。

日付で切れているように見えたのは偶然で、実際は「どの identity が作ったチャットか」で切れていたわけです。

一方 list-journal-records は実行 ID を直接渡せば identity に関係なく中身を取得できます。実行 ID は Web App でチャットを開いたときの URL(/chat/{execution-id})から拾えるので、一覧は Web App、中身は CLI という組み合わせで足ります。

おわりに

AWS DevOps Agent のスキルで references/ に置いた情報がどう使われるのかを、あり・なしを比較して調べてみました。

冒頭に挙げた 3 つの問いは、それぞれこうなりました。

ファイルがなかったらどう答えるのか。 「記載なし」と報告しました。SKILL.md には「重大度に応じて定められた期限内に」と書いてあるので、それらしい値をでっち上げる余地はありましたが、そうはなりませんでした。

参照指示は必要なのか。 不要でした。SKILL.md に一言も書いていないパターンでも、エージェントは自分で判断して読み込みました。

中身は常にコンテキストに載っているのか。 載っていません。スキルを読み込んだ時点で渡るのは SKILL.md の全文とファイル一覧(resource_manifest)だけで、参照ファイルの中身は必要になった時点で個別に取りに行きます。

この 3 つ目が運用面では一番ありがたいところです。参照ファイルを増やしても、使われない限りトークンを消費しません。手順は SKILL.md、分量のある一覧表や設定値は references/ と分けておけば、スキルを育てても実行時のコストは膨らみにくいことになります。

以上

この記事をシェアする

関連記事