はじめに

はじめまして。大阪電気通信大学 情報通信工学部 情報工学科3年の多田早澄です。

2026年8月の1か月間、CA Tech JOB に参加し、株式会社AbemaTV BD本部 開発局 AdTechチームに配属されました。インターン期間中は、インシデント初動調査を支援するエージェントを開発しました。

 

背景

ABEMA の広告配信基盤では、アラートとインシデントを PagerDuty で管理しています。 アラートが発生すると、オンコール担当者が状況を切り分け、関係者へ一次回答します。

一次回答までの調査では、Datadog のメトリクス、Cloud Logging のログ、Slack の関連スレッド、直近のデプロイを確認します。調査先と操作には共通する部分が多い一方、得られた証拠を関連付け、次に何を調べるかを決める判断は残ります。初動調査が長引けば、関係者への一次回答と、その後の対応判断も遅れます。

開発開始時点では、この調査を個人の開発環境で半自動化する取り組みがありました。しかし、実行環境と認証が個人に依存していたため、ほかのメンバーが同じ仕組みを継続して使える状態ではありませんでした。

そこで本プロジェクトでは、個人向けに行われていた半自動化を、チームで共有して運用できる調査エージェントへ発展させることにしました。個人向けの仕組みをチームの仕組みに変えるには、調査処理だけでなく、実行環境、権限、調査結果を人間が検証できる形まで設計する必要があります。

 

インシデント発生から調査結果が届くまで

PagerDuty でインシデントが発生すると、最小限のインシデント情報を含む通知が Webhook エンドポイントに届き、調査処理が起動します。

調査エージェントは処理を始めると、既存の PagerDuty 通知へ返信するのではなく、Slack にインシデント専用の親メッセージを投稿します。

調査中は、進捗を同じスレッドへ返信します。最終的な投稿には、観測した事実、考えられる原因、影響、確認できていない事項、そして担当者が次に取る行動を含めます。

四つの役割と二つの wave で進める調査

一度の調査で証拠がそろわなかったとき、そのまま報告を作ると未確認事項が増え、制限なく調査を続けると初動調査が終わりません。そこで調査処理を PlanSourceCorrelateDraft という四つの役割に分けたうえで、Source による調査を二つの wave に区切りました。

  • Plan:インシデント情報と PagerDuty の最初のアラートを読み、読み取り専用の検索から対象サービスの候補を探します。 その結果を基に、Datadog、Cloud Logging、GitHub の三つの Source へ、調査する質問、時間窓、利用できる Tool を指定したタスクを作ります。
  • Source:指定された情報源と Tool だけを使い、質問ごとに回答済み、未解決、調査を続けられない状態のいずれかを記録します。 あわせて、観測した事実、実行した Tool、取得元を Evidence に残します。
  • Correlate:三つの Source の最終 Evidence を照合し、情報源間の一致、矛盾、不足を区別して整理します。
  • Draft:固定された Evidence と相関結果だけから、Slack へ投稿する一次回答とポストモーテムのたたき台を作ります。

wave 1 では、三つの Source を並列に実行します。 その結果に未解決の質問や再調査できる失敗が残った場合、制御コードが Follow-up Plan を作り、該当する質問だけを wave 2 へ割り当てます。 wave 2 では直近の時間窓を更新し、wave 1 と同じ問い合わせの再実行を拒否します。 追加調査が不要な Source はモデルや外部 API を呼ばず、wave 1 の Evidence をそのまま後段へ渡します。

 

 

役割間では、自由形式の自然言語ではなく、型を定めた JSON の artifact を受け渡します。 Evidence には、質問の状態、観測事実、実行した Tool、取得元に加えて、前の wave や調査タスクと対応付けるダイジェストを保持します。 Correlate と Draft はこの artifact を受け取り、制御コードが実行との対応やダイジェストを検査してから Slack へ投稿します。

この検査は原因候補の正しさを保証するものではなく、担当者が記述の根拠へ戻れることと、異なる実行の Evidence が混ざらないことを守ります。

 

Kubernetes のカスタムリソースから実行を始める

役割を分け、調査を二つの wave に区切っても、それだけでは処理の順序や並列実行は決まりません。 前段の完了を待ち、必要な Pod を起動する仕組みが必要でした。

そこで、調査の実行に Argo Workflows を使いました。Argo Workflows では、処理の実行順序や依存関係を Kubernetes のカスタムリソースとして定義できます。

ただし、Argo Workflows だけでは、PagerDuty の通知を Workflow の作成につなぐ入口は決まりません。 そこで PagerDuty 用の Webhook エンドポイントを用意し、通知を受け取ると、あらかじめ定義した WorkflowTemplate を参照する Workflow カスタムリソースを Kubernetes API へ作成するようにしました。 Webhook 受付処理に与える権限は、調査用 Workflow の作成と参照に限定しています。

Argo Workflows の Controller は、作成された Workflow を検知すると、最初に PagerDuty からアラートを取得します。 取得後は Slack の親メッセージ作成と Plan を進め、二つの wave を実行したあとに、結果の相関、下書きの生成と検証、Slack への投稿を進めます。 同じ wave の Source は並列に実行し、三つの Source が完了したあとに次の処理を開始します。

 

Kubernetes 上で権限を分ける

調査と制御の各処理は、Argo Workflows が起動する短命な Pod として実行します。各 Pod は同じ Go のイメージとバイナリを使い、異なるコマンドとして Plan や Source、Follow-up Plan などを動かします。

エージェント内部のモデルと Tool の実装には Eino を使い、役割間の実行順序は Argo Workflows に任せました。 この分離により、モデルを呼ぶ処理と Kubernetes 上のオーケストレーションを同じ仕組みに閉じ込めずに済みます。

情報源からの読み取りだけを許可し、外部への書き込みは Slack への投稿に限定しました。さらに、Pod ごとに必要な認証情報だけを渡し、役割に不要な Secret は共有しません。

ここで RBAC だけでは埋まらない穴がありました。RBAC は Workflow リソースを作成できるかを制御できますが、その Workflow がどのコンテナ、ServiceAccount、Volume を指定するかまでは制約しません。

この制約を補うため、ValidatingAdmissionPolicy を導入しました。ValidatingAdmissionPolicy は Common Expression Language(CEL)で API リクエストを検査し、条件に合わないリソースを拒否できる Kubernetes の仕組みです

当時、Kubernetes マニフェストを管理するリポジトリには同種の設定例がなく、生成された検証条件が十分かを一人で評価することも難しくなりました。

 

Datadog の認証を調べ直す

Datadog への認証について、当初はエージェントが API Key と Application Key の組み合わせを提案しました。それで実装を始めることもできましたが、サービスから継続的に利用する認証として適切かは別の問題です。

私が一次資料を調べ直したところ、サービスアカウント向けの Service Access Tokens(SATs)が候補になりました。Datadog の公式ドキュメントでも、サービスアカウントから API を呼ぶ場合は、単独で利用でき、スコープを設定できる SATs が推奨されています

私は SATs の採用条件を整理し、自分には権限のない検討と作成を社員へ依頼しました。作成された SATs は必要な Pod だけに渡し、Bearer 認証で API を呼び出す構成にしました。

 

三つの技術比較

今回の構成は、使いたい技術を先に決めて組み合わせたものではありません。 既存基盤、保守対象、権限境界を比較した結果です。

マルチエージェントを動かす基盤

Temporal を比較対象に入れたきっかけは、インターン以前から、Cursor のクラウドエージェントが耐久性のある実行のために Temporal へ移行した事例を知っていたことです。 長時間動くエージェント基盤の実例として、今回の調査処理にも適用できるかを検討しました。

ただし、Cursor の採用事例があることと、今回の要件に適していることは別です。 長期間にわたって人間からの Signal を待ち続ける処理や、実行状態を独立したサービスへ永続化する要件はありませんでした。

選択肢 今回の利点 今回の懸念
Argo Workflows 既存の Kubernetes と Argo の基盤を利用できる Kubernetes リソースとしての権限制御が必要
Temporal 長期間の待機、Signal、状態保持を扱える 今回はその機能を必要とせず、新たな運用対象が増える
独自のキュー、ワーカー、データベース 要件に合わせて構成できる 実行状態、再開、可視化を新たに実装して保守する必要がある
エージェントフレームワーク内の機能 一つのプロセス内で役割を組みやすい Workflow 全体がフレームワークの都合に影響されやすい

Temporal は実行状態を保持し、障害後も処理を再開できる Durable Execution を提供します。 それは長時間の処理や人間の入力待ちには適していますが、今回の要件だけで採用すると、活用しない機能のためにも運用対象を増やすことになります。

そこで、役割間の制御には既存基盤である Argo Workflows を使い、エージェントフレームワークは各エージェントの内部に限定しました。 実際にフレームワークを交換して検証したわけではありませんが、外側の Workflow と「artifact」の契約を保ったまま内部を変更できる構成を目指しています。

言語とフレームワークの組み合わせ

選定時点で調べた範囲では、TypeScript は、エージェントフレームワークとモデルや Tool との連携ライブラリのどちらも Go より選択肢が多い状況でした。 TypeScript のエージェント開発を取り巻くコミュニティも Go より活発で、実装例や知見が多く共有されていました。 一方、既存サービスでは主に Go が使われていました。

フレームワークの候補は、TypeScript 側に Flue、Go 側に Google ADK と Eino がありました。 想定した実装言語が異なるため、フレームワークだけを言語から切り離して選ぶことはできませんでした。

Flue を採用する場合は、TypeScript のエコシステムを活用できる代わりに、インターン終了後も新しい実行環境を保守し続ける必要があります。

Google ADK には Go 版もありましたが、選定時点では Python を中心に展開されていると受け止めました。 Go 版と Python 版の実際の機能差までは確認できていません。 ただ、Go で実装し保守する前提では、この不確実性から候補としての優先度を下げました。 Eino は Go を前提に設計されており、既存コードへモデルと Tool を組み込みやすい点を評価しました。

その結果、調査エージェントには Go と Eino の組み合わせを採用しました。

Slack スレッドをどう確定するか

調査エージェントが進捗と調査結果を同じ場所へ投稿し続けるには、Slack のチャンネル ID に加えてスレッド ID が必要です。 ところが、PagerDuty Incident Workflows の Slack 連携で得られる出力には、後続の返信に使えるスレッド ID が含まれていません

既存の PagerDuty 連携メッセージを Slack 上で検索する案も検討しました。 しかし、Slack の search.messages が要求するのは User Token の search:read スコープであり、調査エージェントが使う Bot Token からは呼び出せません

Slack のメッセージイベントを監視し、インシデントとメッセージを突合することもできます。 その場合は対応関係を保持するデータベースが必要になり、今回の機能のために新たな状態管理が増えます。

方式 採用しなかった理由またはトレードオフ
PagerDuty Incident Workflows から Slack 連携を開始する 後続処理に必要なスレッド ID を得られない
既存メッセージを検索する Bot Token では search.messages を利用できない
Slack イベントを監視して突合する インシデントとの対応関係を保持するデータベースが必要
専用の親メッセージを作る 既存の PagerDuty 通知とは別スレッドになる

結果として、調査エージェント自身が専用の親メッセージを作り、投稿時のレスポンスからチャンネル ID とスレッド ID を取得する方式を選びました。既存の PagerDuty 通知と調査結果が別スレッドになる点は残りますが、処理内部だけで返信先を確定できます。

 

AI の提案に覚えた違和感

最初に AI が提案したのは、サービスと監視対象を固定のカタログへ登録し、その対応表から利用する情報源を選ぶ仕組みでした。ところが、そのまま実装を進めるうちに、固定された対応関係を運用し続ける設計に違和感を覚えました。サービスや監視対象を追加するたびにカタログを更新し、監視構成が変わるたびに実態と対応表を同期しなければなりません。調査エージェントが増えるほど、固定された対応関係も増えていきます。

固定カタログ方式は完成させず、実装段階で採用を取りやめました。代わりに、インシデント情報と取得済みの証拠から次の調査対象を動的に見つける方式へ変更しました。ただし、モデルの自由な探索に任せきるわけではありません。利用できる情報源と権限は、コードと Workflow で制限します。

比較軸 固定カタログ方式 動的発見方式
初期実装 対応表と検索処理が必要 調査計画と発見処理が必要
保守対象 サービスと監視対象の対応表 Tool の契約と探索条件
構成変更への追従 カタログの同期が必要 取得した証拠から追従できる余地がある
主なリスク カタログと実態のずれ 無関係な対象を探索する可能性
制御方法 固定の対応付け 情報源と権限をコードと Workflow で制限

この変更では、固定カタログ方式のためにすでに生成されていたコードを相当量削除しました。エージェントが生成したテストや検証処理にも、同じ条件を重ねて確認する冗長なものがあり、不要と判断したものは残しませんでした。

コードが増えたという事実だけでは、継続して使える機能になったかを判断できません。今回の成果を左右したのは、当初の設計に固執せず、実装段階で保守しにくい構造を見直せたことでした。

 

残った課題

インターン期間中に残った課題は、過去のインシデントを使った調査エージェントの評価と、調査結果から Draft PR を作成するところまで支援範囲を広げることです。どちらも現在は設計段階であり、インターン期間内には実装まで進められませんでした。

過去のインシデントでどう評価するか

調査エージェントが調査結果らしい文章を返しても、それだけでは初動判断に使えるとは言えません。 文章の流暢さと、証拠に基づく正しさは別だからです。

そこで、Slack から過去のインシデントスレッドを取得し、当時の情報を入力として調査エージェントを動かし、その出力を実際の調査結果と比較して評価するまでを自動化したいと考えています。 同じ過去事例を使って評価することで、モデルや Prompt、Tool の変更によって出力がどう変わったかも継続して確認できます。

PR 作成への拡張

調査結果から修正案を生成し、Draft PR を作るところまで支援範囲を広げることも検討しています。 実現する場合も、自動化の上限は修正用ブランチ、コミット、Draft PR の作成までとします。 レビュー、マージ、リリースは人間が判断します。

 

インターンでの学び

開発を始める前は、自分で手を動かして実装を増やすことが、成果につながると考えていました。 しかし実際には、何を作るかと同じくらい、何を作らないか、どこで周囲の判断を仰ぐかが問われました。

たとえば、複雑な処理を一つの仕組みにまとめるのではなく、状況に応じて考える部分と、確実に制御する部分を分けました。 この切り分けによって、それぞれの役割を整理しながら開発を進められました。

自分だけでは妥当性を判断できない変更に直面したこともあります。 そのときは、一次資料を調べ、確認したい条件を整理したうえで、社員の方にレビューを依頼しました。 分からないまま進めず、自分が確認できたことと、判断を仰ぎたいことを分けて伝える必要があると学びました。

また、実装の途中で方針を取りやめた経験もありました。 一か月という短い期間では、作ったコードを成果として残したくなります。 しかし、その後もチームが保守することを考えれば、負担を増やす実装を捨てる判断も必要でした。

 

おわりに

今回の開発では、個人の開発環境に依存していた半自動化を、権限と検証可能性を考慮したチーム向けの仕組みとして設計しました。 一方で、評価や運用に向けて取り組むべき課題も残っています。

一か月のインターンを通じて、目の前の実装だけでなく、その後の運用と保守まで考えて判断する必要があると学びました。 実装を増やすだけでなく、不要なものを捨て、自分だけでは判断できないことを周囲へ相談する過程も、今回得られた成果です。

インターン中は、定例会議などを通じてチーム内外の社員と交流し、今回のタスクについて相談してフィードバックを受けました。 そこで交わされた議論からは、ABEMA で広告を配信する仕組みや、広告ドメインについても知ることができました。 こうした交流は、自分のキャリアについて考えるきっかけにもなりました。

今後の開発でも、運用後まで見据えて判断する姿勢を持ち続けたいと思います。

開発にあたり支えてくださったトレーナー、メンター、配属先のみなさまに感謝します。