AIでAIをテストするー音声AIエージェントの品質保証戦略

こんにちは、tacomsのMorixです。
私は現在Camel AI Callという飲食店向けの電話応対エージェントの開発・運用を行っています。
このプロダクトではAIを利用した品質保証を行っているのでその紹介をします!

www.camel-series.com

なお、この記事の内容は三田データ vol.4 音声AI祭りで発表した内容と同じです。(いくつか補足をします)

mita-data.connpass.com

音声AIの開発・運用は思ってたより大変

Camel AI Callは次のような会話をしてテイクアウト注文を受け付けます。

  1. あいさつ
  2. テイクアウト注文内容を聞き取る
  3. お客様に注文内容確認をする
  4. 注文確定をする

流れは単純ではありますが、音声での会話ということもあり入力パターンが無数にあります。
また注文を確定するまで無事会話を完了するには、LLMの応対精度次第ということもあり、どんな入力をされてもLLMの精度を安定させる必要があります。

そこで感じた音声AIならではの大変さがこちら

  • LLMの出力は非決定的
    • 毎回必ず同じ応答をしてくれるわけではないため、テストの期待値設定が難しい
  • プロンプト1行変えただけでデグレする
    • プロンプトを少しだけで思わぬところに影響が出ることがある
  • 音声でのテストは自動化しづらい
    • APIやフロントエンドのE2Eテストのようにエコシステムが整っていない
    • 人間が毎回AIと喋って動作確認をする羽目になる

要はテストがしんどいって話です。

音声AIでテストしたい観点は色々ある

上記の大変さをもうちょっと落とし込むと、以下のようなテスト観点の整理になると思います。

  • プロンプトの品質
    • LLMが期待通りの振る舞いを毎回してくれるか?
  • LLMと他のシステムとの連携
    • 人間との会話の結果がAPIやDBにどう反映されているか?
  • STT/TTS/電話との実挙動
    • STT(Speech To Text)の結果は意図通りLLMに渡っているか?
    • LLMの結果はTTS(Text To Speech)でどう発話されているか?
    • 電話で正しく応対できるか?

3層テスト戦略

このプロダクトでは次のテスト手法でテスト観点をカバーしています。この3層のテスト戦略を用いてプロダクト全体の品質保証をしています。

テスト観点 テスト手法
プロンプトの品質 LLM振る舞いテスト
LLMと他のシステムとの連携 E2Eテスト
STT/TTS/電話との実挙動 テストエージェント

それぞれのテスト手法について説明していきます。

LLM振る舞いテスト

LLM振る舞いテストとは、LLMがプロンプトの指示通りの振る舞いをするかをテストする手法です。テスト対象はLLMのみです。
どんなテストをしているかというと、次のような会話の流れをテストの1ケースとして作成し、LLMが期待通りの応対をしているか検証しています。

  • ユーザー「カレー1つ」
  • LLM「カレー1つでよろしいですか?」
  • ユーザー「はい」
  • LLMはツールを呼び出し注文確定

他にも例えばカレーが品切れだった場合の応対とか、トッピングが追加された場合の応対とか、会話のパターンがたくさんあるので、そのような会話の組み合わせを多数用意し、プロンプトの修正が発生したら全部テストしデグレを防いでいます。

次はコード例です。

# 受取時間を伝える
result = await self.run_with_context(session, agent, "明日の18時でお願いします")
next_event = result.expect.next_event()
assert next_event.is_message(role="assistant")
content = next_event.event().item.content[0]
assert "18時" in content or "18:00" in content or "午後6時" in content
result.expect.no_more_events()

# 受取時間を承諾
result = await self.run_with_context(session, agent, "はい")
next_event = result.expect.next_event()
assert next_event.is_message(role="assistant")
content = next_event.event().item.content[0]
assert "商品" in content or "ご注文" in content
result.expect.no_more_events()

このテストコードは次のような内容になっています。
ユーザーが「明日の18時でお願いします」といったら、LLMは「明日の18時でよろしいでしょうか?」という復唱をすべきというテストをしています。
1文字1句同じ文章を生成するとは限らないので、復唱してるかの確認は「18時」あるいは「午後6時」というワードが入ってるかという静的な検証をしています。
そしてその復唱に対しユーザーが「はい」と言ったら、次に注文商品の確認をするための確認文言を発話しているかテストをしています。

しかし、このようにある程度決まった出力をする場合は強引に静的な検証をすることができますが、そうではなく非決定的な応対をすることもあります。
例えば「今何を頼んだっけ?もう一度言ってみて」とユーザーが言った場合、LLMは今までの注文内容を復唱したうえで、追加注文を聞くことを期待しますが、どういう文章を生成するかわかりません。
こういった場合、LLMの生成結果をLLMに判定させています。

result = await self.run_with_context(session, agent, "今何を頼んだっけ?もう一度言ってもらえますか?")
chat_assert = result.expect.next_event().is_message(role="assistant")
await chat_assert.judge(
  judge_llm,
  intent="注文内容を伝えた上で、追加注文確認をしている",
)
result.expect.no_more_events()

このようにLLMの振る舞いをLLMで検証することで、非決定性へ向き合っています。

E2Eテスト

LLMが関連システムと連携し期待通りの会話ができるかをテストする手法です。テスト対象はLLMとAPIとDB(などの外部システム)となります。
よくあるようなE2Eテストとは異なり、その都度会話シナリオを用意し実行しています。

このE2Eテストは、コーディングエージェントのために存在します。
コーディングエージェントで良い成果物を出すためには検証する手段を用意する必要がありますが、コーディングエージェントは音声対話できないので音声対話するための手段を用意しました。
このE2Eテストをコーディングエージェントの開発ワークフローの組み込み、E2Eテストを成功しないと開発完了とならないようにしています。

このE2EテストはClaude Codeのスキルとして用意しており、次のようにスキルに会話シナリオを渡すと会話履歴・DBの内容などを出力します。

E2Eテストスキルの仕組みは次のようになっています。

  1. Claude Codeスキル経由で起動し、テストシナリオを渡す
  2. E2Eテストエージェントが電話注文エージェントを起動
  3. E2Eテストエージェントが電話注文エージェントのログを監視
  4. 電話注文エージェントは仮想オーディオデバイスを使って音声入出力
  5. E2Eテストエージェントは仮想オーディオデバイスを使って音声入力
  6. 会話が完了したら結果をClaude Codeに報告

E2Eテストエージェントは、渡されたシナリオ通りに電話注文エージェントと会話するための音声AIです。

ここでの工夫ですが、電話注文エージェントの発話音声をE2Eテストエージェントは受け取っていません。代わりに、電話注文エージェントのログに発話内容が記録されるためその文字列を見て発話内容を生成しています。
こうすることで、電話注文エージェントの発話速度を最大まで高めることができ、高速に会話をすることができます。

テストエージェント

テストエージェントは、電話をかけることができる音声AIエージェントです。
テストエージェントを使って、Staging環境(検証環境)のテストを行います。

こちらのテストもE2Eテストと同様、修正内容に合わせて都度会話シナリオを用意してテストを実行します。
実行イメージは以下のように、プルリクエスト上でシナリオのコメントをするとそのシナリオに沿って会話を始めてくれます。

プルリクエスト上でStaging環境にデプロイすることができるので、デプロイ完了後にこのようなテストを行っています。
テストが完了したら、テスト結果をコメントしてくれます。テスト結果に管理ツールへのリンクがあり、そこで会話の録音や文字起こしデータが見れるので、どんなテストをしたか証跡が残ります。

このテストエージェントは電話注文エージェントと同様、会話をするために必要なすべての機能に加え、架電機能も持っています。
なので、次のような仕組みでテストエージェントとStagingの電話注文エージェントを会話させています。

さて、なぜわざわざテスト用のAIエージェントを作ったかという話ですが、主に2つの理由があります。

  • 時間の節約のため。人間がやると時間を取られるから
    • 電話の応対はどうしても時間がかかる。しかもうっかり言い間違えるときもある
    • 人間のリソースはもっと別のことに注ぐべき
  • 並列実行できる
    • 複数パターン会話したい場合、人間がやると1個1個確認することになり時間がかかる
    • テストエージェントならこれを同時に出来る

3層テストを開発フローにどう組み込んでいるのか

Camel AI Callの開発はコーディングエージェントを使って開発をしています。
AIに自律的な開発をさせるため、開発フローを定義し、そのとおり実行させています。
その開発フローは以下のようになっており、赤い箇所が今回の3層テストです。

緑丸がついているところが人間が関与するところです。ほかはAIや自動スクリプトでやっています。

実装はTDDで行っていますので、UnitTestを作って実装をしてます。プロンプトを修正する際もRedGreenになるように開発をするために、LLM振る舞いテストを実装後、プロンプト実装するような流れにしています。

まとめ

  • 音声AIのテストを人間がやるのはしんどい
  • 音声AIのテストはAIでやろう
    • LLMのプロンプト品質チェックはLLM振る舞いテストで確認
    • コーディングエージェントの成果物品質チェックはE2Eテストスキルで確認
    • 全体的な動作確認はテストエージェントで確認
  • テストをAIに任せることで開発速度向上と品質向上を実現できた!

勉強会での発表資料

speakerdeck.com

Claude Codeの/ultrareviewを試してみた所感

こんにちは! racode です。 Claude Codeの新機能 /ultrareview を試してみたので、その仕組み・料金・制約・実際の出力を整理します。

エージェント AI が量産するコードと、人間レビューでは見つからないバグ

エージェント型 AI による開発は、すでに無くてはならないものになっています。

以下のように、GoogleやAnthropicでもすでに大部分のコードがAIによって生成されているようです。

  • Google: 新規コードに占める AI 生成の割合が 2024年Q3で 25%2025年秋で 50%2026年Q2で 75% と急増(Sundar Pichai 発言、Gemini + 内製エージェント開発基盤 Antigravity を使用)(businessinsider)
  • Anthropic: 全社の 70〜90% が AI 生成コード、Claude Code 自体の約 90% は Claude Code 自身が書いている(Anthropic 公式コメント)(Fortune)

一方、第三者調査が示すのは「速度と引き換えに、見つけにくいバグが紛れ込みやすい」という現実です。GitClear が累計 211M 行のコミット履歴を解析した 2025 年版レポートでは:

  • 書いてから 2週間以内にリバート/修正される短期チャーン率: 2020年 5.5% → 2024年 7.9%
  • リファクタリング相当の行: 2021年 25% → 2024年 10% 未満
  • 重複コード(clone)の出現率: 4倍 に膨張 (GitClear 2025レポート)

要するに「コミット時点では動く・型もテストも通る」一方で、統合の境界や運用時にだけ顔を出すバグが混入しやすくなっています。 ここが、AI が量産時代に入ったコードベースで急速に膨らんでいる問題領域です。

/ultrareview は、このような気づきにくい問題に対して有効的な機能です。複数の AI エージェントが並列に変更を読み解き、検出した不具合を独立に再現・検証してから報告することで、人間レビュアー1人 × 単一視点では落ちる種類の指摘を、マージ前にあぶり出します。本記事では仕様・料金・制約・実際の出力を整理します。

本記事の情報は 2026年5月時点の公式ドキュメントに基づきます。/ultrareviewリサーチプレビュー機能(Claude Code v2.1.86以降)であり、仕様・料金は変更される可能性があります。


/ultrareview とは

/ultrareview は、Claude Code の クラウドサンドボックス上で動作するマルチエージェント型のディープコードレビューです。ユーザーが CLI で /ultrareview と入力すると、リポジトリ状態がリモートにアップロードされ、複数のレビュアーエージェントが並列に変更を読み解き、検出した不具合を独立に再現・検証してから報告します。

ローカル実行の /review との違いを公式ドキュメントは次のように整理しています。

/review /ultrareview
実行場所 ローカルセッション クラウドサンドボックス
深さ シングルパス 複数エージェントが並列探索+独立検証
所要時間 数秒〜数分 おおむね5〜10分
課金 通常の利用枠 無料枠後は1回 \$5〜\$20(追加利用枠)
向いている場面 反復作業中の即時フィードバック マージ前の最終確認

ポイントは以下の3点です。

  1. High signal: 全ての指摘が独立に再現・検証される。スタイル指摘ではなく実バグに絞られる
  2. Broad coverage: 複数エージェントが別視点で並列探索するため、単一パスでは見落とすケースを拾う
  3. No local resource: ローカルマシンを占有しない。ターミナルを閉じても進行する

プラン・料金

/ultrareview は通常プランの利用枠とは別の 「追加利用枠(extra usage)」で課金されます。

プラン 無料枠 無料枠後
Pro 3回(2026年5月5日まで) 追加利用枠で課金
Max 3回(2026年5月5日まで) 追加利用枠で課金
Team / Enterprise なし 追加利用枠で課金

重要: Pro / Max の無料枠は 2026年5月5日で終了しています。本記事公開時点で新規ユーザーは初回から有料です。

  • 1回あたりの課金額は変更規模に依存し おおむね \$5〜\$20
  • リモートセッションが起動した時点で消費されるため、途中停止・失敗でも無料枠は1回消費される
  • 有料枠は実行された分のみ課金される
  • アカウント/組織で 追加利用枠が有効化されている必要があり、未設定の場合は起動がブロックされる(/extra-usage で確認・変更可能)

認証要件

  • Claude.ai アカウントでのログインが必須。API キー認証のみの環境では /login で Claude.ai に切り替える必要がある
  • Amazon Bedrock / Google Cloud Vertex AI / Microsoft Foundry 経由では利用不可
  • Zero Data Retention(ZDR)が有効な組織では利用不可

ユースケース

公式ドキュメントが推奨するのは「マージ前の最終確認」です。具体的には次のような場面に向きます。

  • 大きめの PR をマージする直前: 複数エージェントが独立に検証してくれるため、レビュワー1人の見落としを補完できる
  • セキュリティ・運用観点の最終チェック: 後述の使用例にあるように、レート制限の不備・URL エンコーディング欠落・IaC のドリフトなど、動作はしてしまうが本番でハマる類のバグを拾う
  • CI 連携での自動化: claude ultrareview サブコマンドにより非対話実行が可能。--json で生の bugs.json を取得、--timeout <分>(既定30分)でタイムアウト制御できる
# 現在のブランチ(既定ブランチ差分)をレビュー
claude ultrareview

# PR番号を指定
claude ultrareview 1234

# 任意のベースとの差分
claude ultrareview origin/main

GitHub PR への自動コメント投稿が目的なら、/ultrareview ではなく Code Review のリポジトリ統合がよさそう。


制約

導入前に押さえておきたいポイントです。

  1. リサーチプレビュー段階: v2.1.86 以降。仕様・料金・提供範囲は変わり得る
  2. クラウド送信が前提: ローカル作業ツリーがリモートサンドボックスに送られる。機密性の高いコードを扱う場合はポリシー確認が必要
  3. PR モードは GitHub 限定: PR 番号指定モードは github.com リモートが必要。GitLab/Bitbucket/オンプレ Git サーバーは対象外
  4. 巨大リポジトリはバンドル不可: その場合は draft PR を立てて PR モードを使うよう案内される
  5. 部分結果は返らない: /tasks で停止したレビューはアーカイブされ、途中までの検出結果は得られない
  6. 無料枠の使い切り判定は厳しめ: リモートセッション起動時点で1回カウント。失敗・早期停止でも消費される
  7. Bedrock / Vertex AI / Foundry / ZDR では使えない

使用例

簡単な EC サイトを Claude Code 3.7 を用いてワンショットで生成したものを利用します。そのままのコードを /ultrareview にかけたあと、ローカルの Claude Code セッションがそのまま修正・コミット・プッシュまで完了した直後の画面がこちらです。

注意: これは /ultrareview の出力ではなく、findings を受け取ったローカルセッションが修正・コミット・プッシュまで進めた結果です。公式仕様は findings の通知までで、修正以降は ローカル側(auto mode 等)の挙動です。

特徴的なのは、「動作はしているが運用で必ず壊れる」種類の指摘が多いことです。

  • /auth/*/* のレート制限 Map 共有による2重カウント
  • :latest プッシュ × IMMUTABLE ECR の2回目以降失敗
  • engine_version のマイナーバージョン自動更新ドリフト

このような実装の問題は、自身で実装していれば気づきそうですが、大量の生成されたコードの中では埋もれ、事前に発見するのは難しそうです。

次セクションでは、今回のレビューで発覚した内容をもとに、 AI コードレビューが構造的に強い/弱い領域を分類してみます。


AI コードレビューの観点 ── 何が拾え、何が拾えないか

検出された 10件を分類してみると、AI(特にマルチエージェント並列型)レビューが構造的に強い領域とそうでない領域がはっきり出ます。

AI が強い: 「横断不変条件」と「仕様準拠」

  • bug_001 (rate limit Map 共有): factory() で生成される rateLimit インスタンスが、内部の Mapプロセス共通の参照として共有してしまう古典的なクロージャ事故。コードを 1 ファイルだけ読むと正しく見えるが、呼び出し側の生成回数とインスタンスのライフサイクルを同時に追わないと検出できない。エージェントが「factory の利用箇所」と「Map のスコープ」を別々に並列追跡することで初めて落とし穴が浮き上がる
  • bug_002 (XFF 偽装): X-Forwarded-For の信頼ポリシーは ALB / CloudFront / Cloudflare で正しい挙動が異なる。RFC・各クラウドの公式ドキュメントを横断的に当てる作業は、AI が網羅的に得意な領域
  • bug_004 (URL エスケープ漏れ): DATABASE_URL のパスワード部に @ / 等が混じった瞬間に接続が壊れる。「動くケースが圧倒的多数で、たまに壊れる」境界条件は人間レビュアーが流しやすく、AI が網羅探索しやすい

これらは「単一ファイル単位では正しい」「仕様書を全件突き合わせれば必ず見つかる」種類で、並列エージェント × 検証ループという /ultrareview のアーキテクチャがそのまま効きます。

AI が強い: 「副作用と時間軸」

  • bug_003 (ECR latest × IMMUTABLE): タグの不変性ポリシーと CD パイプラインの整合は、現実に push が走って初めて壊れる典型。ローカルテストでは確認できない。
  • bug_009 (ECS force-new-deployment 順序): タスク定義の登録 → サービス更新の順序が逆転すると「古いタスクで強制再デプロイ」という挙動になる。実行順という時間軸の不変条件は、構文チェックでは拾えない
  • bug_026 (WAF へのレート制限移譲): コード側のプロセス内 Map によるレート制限はタスク数だけ実効上限が乗算される。単体の rateLimit.ts は正しく動くが、ECS のタスク数 × CDN 構成 × WAF 設定を組み合わせて初めて「実上限」が見える ── 単一視点では気づきにくい

/ultrareview がここを拾えるのは、bugs.json の各 finding を独立に再現・検証する設計(Higher signal の根拠)のおかげです。これがないと、AI レビューは「それっぽいが偽陽性」を量産します。

AI が拾いにくい: 「業務意図」と「組織知」

一方、Nit に分類された 3件 (bug_032 / bug_034 / bug_039) は AI レビューの限界を示します。

  • bug_039engine_version メジャー固定は「メンテナの方針次第」で、「自動マイナー更新を許容するか/許容しないか」は組織の運用ポリシー。AI は両論併記しかできない
  • bug_034migrate.sh dead codeは、本当に dead なのか「まだ使っていないだけで来週使う」のかをコードからは判断できない
  • bug_032final_snapshot_identifierは「再作成時に衝突する」ところまでは仕様で判定できるが、「そもそも再作成を許容する設計か(本番 RDS なら通常 NG)」は組織の運用次第
  • ボーナスの tags.Name 命名整合も同様で、組織内の命名規約を知らないと過剰に修正提案しがち

ここは Nit に落としてくれたから良いものの、AI が Normal severity に格上げした時こそ人間がブレーキを踏むべき領域です。

実運用での示唆

  • AI レビューは「多視点 × 仕様照合 × 副作用シミュレーション」で人間を超えるが、「業務文脈・組織慣習・将来計画」では人間に及ばない
  • /ultrareview の Severity 分類(Normal / Nit)は鵜呑みにせず、Nit の中の重要指摘・Normal の中の過剰指摘を人間がリラベルする運用が現実的
  • マルチエージェント検証は 偽陽性を減らす設計(≠ ゼロにする設計)。10 件中 1〜2 件はコンテキスト不足の指摘が混ざる前提でレビューすること
  • 上記の特性を踏まえると、マージ前ゲートよりも「PR 作成時の壁打ち」として使うほうが、人間と AI の役割分担として健全な場面も多い

まとめ

  • /ultrareview は Claude Code on the web 基盤上で動く マルチエージェント・クラウドレビュー
  • ローカル /review との差別化点は 独立検証 × 並列探索 × 非占有実行 の3点
  • Pro / Max の無料枠は 2026年5月5日に終了済み、以降は 1回 \$5〜\$20 の追加利用枠課金
  • マージ前の最終ゲートとして位置付けるのが王道。CI からは claude ultrareview --json で組み込める
  • 公式仕様で保証されるのは「verified findings をセッションに通知として返す」ところまで。修正・コミットの自動化はローカルセッション側の挙動なので、auto mode 利用時は push 前に人間レビューを挟む運用にすること
  • リモートサンドボックスへのコード送信を伴うため、機密リポジトリでの利用はポリシー要確認
  • Bedrock / Vertex AI / Foundry / ZDR 環境では現状利用不可
  • AI レビューが構造的に強いのは「横断不変条件 × 仕様準拠 × 副作用シミュレーション」、弱いのは「業務意図 × 組織知」。Severity ラベルは鵜呑みにせず、人間が再ラベルする運用が現実的

Google で 75%、Anthropic 社内で 70〜90% が AI 生成コードという時代に、人間レビュアー1人 × 単一視点だけでマージ判断を下し続けるのは現実的ではありません。/ultrareview は、その「AI が量産する量と速度」と「人間レビューの帯域」のギャップを、同じ AI 並列エージェント側から埋めにきた一手です。コミット前は通った、テストも通った、レビューも通った ── それでも本番で壊れるバグが急に増えたチームには、まず 1 度試す価値があります。


参考資料

AIデーを半年運用して出てきた成果物の紹介

こんにちは!fumiyaki です。

私たちのチームでは半年ほど前から「AIデー」というAIを活用する日を作り、運用しています。1スプリント2週間のうち、スプリント初日をまるごとAIデーにあてて、各メンバーが自由にAIを触る日です。

この半年で、AIデーからはいろいろな成果物が生まれました。本記事ではその中から、私が個人的に「これは紹介したい」と思ったものを3つピックアップして紹介します。

  • Slackスレッドでの議論反映 — スレッドでの議論を Devin が Issue やドキュメントに反映してくれる仕組み
  • エラー自動調査 — Slackへのエラー通知をトリガーに Devin が自動で調査して Issue まで起票する仕組み
  • kaigi — 会議後のTodoをまるごと自動化する常駐Claude Code

どれもメニューマネージャー(飲食店のメニューをフードデリバリープラットフォームへ配信するシステム)を作っているチームの中で生まれたものです。

AIデーについて

AIデーは、2週間スプリントの初日をまるごとAIを触る時間にあてるという日です。 以下のような形で運営されています。

  • テーマ縛りはなし: 業務と関係ない実験でも構わない
  • 成果物は必須ではない: 動くものが出来上がらなくてもOK
  • 共有会: その日の終わりに何をやったのか、知見を共有

導入の背景は、「AIを皆がどうやって使っているのかがわからない」という素朴な課題感でした。毎日の業務をこなしていると、他のメンバーが普段どんなプロンプトを書いているのか、どんなツールをどう組み合わせているのかはなかなか見えません。各自の工夫が個人の中に閉じてしまっていて、チームとして「AIの使い方」を蓄積する場が無い状態でした。

スプリントの中でAIデーは1日しか無いので「成果物を出す」という縛りを意図的にかけていません。手を動かすこと、そしてその日の終わりに「こういうことを試しました」を話すこと。この2点だけを重要視していて、結果や成果物は二の次、という設計にしています。

とは言え半年もやって成果物が0だったわけではないので、いくつかの成果物をピックアップして紹介するのがこの記事となります。

① Slackスレッドの議論を Issue に反映する Devin Playbook

最初に紹介するのは、Slack のスレッドで Devin を呼び出して一言依頼するだけで、そのスレッドの内容を解析して GitHub Issue やドキュメントの PR を自動で作ってくれる Playbook です。

きっかけは、開発チームあるあるの悩みでした。Slack で決まったことが Issue に反映されない、というやつです。

仕様管理には GitHub Issue を使っているのですが、日々の議論は Slack で進むので、「Slack で『B ではなく A で行こう』と決まったのに Issue には古い情報のまま残っている」「スレッドで『これもやらないとだね』と出たタスクが誰の TODO にもなっていない」「数週間後に『あの話どうなったっけ?』と聞かれて Slack を遡る羽目になる」といったことはあるあるですよね。

こうなる原因はシンプルで、Slack の議論を Issue に書き移す作業が手動で面倒くさくて、忙しい開発の中ではどうしても後回しになっていた、という話です。結果として、Issue が最新の状態を反映していない状況が生まれます。

この Playbook は、現在の運用を変えずに問題を解決することを狙っています。普段通り Slack のスレッド内で議論する。最後に Devin に一言依頼するだけで、Devin がスレッドを読み、内容を自動で分類して、適切な場所に情報を落としに行く、というのが大まかな流れです。

分類は「知識系」と「タスク系」の2つで行っています。

  • 知識系(技術仕様の議論、手順やワークフローの説明、ポストモーテムなど)→ GitHub にドキュメントの PR を作成
  • タスク系(バグ報告、改善要望)→ GitHub Issue を作成し、GitHub Projects にも追加、Type フィールド(Bug / Feature / Task)まで設定

両方に該当する場合はドキュメントと Issue の両方を作りますし、「知識」「タスク」のどちらかを明示して分類を上書きすることもできます。既存の Issue に議論の続きを追記したい場合は、Issue の URL を添えて依頼すれば、そのスレッドで出た新しい内容だけが追記されます。

設計のちょっとした工夫として、Devin にはドキュメントや Issue を書かせる前に必ずコードベースを調査させるフェーズを入れています。そうすると「○○画面の △△ コンポーネント(src/components/...)で、こういう条件のときに〜」のように、ファイルパスや関数名を含んだ具体的な記述になってくれて、後から読み返したときの情報量がぐっと上がります。Devin が Slack のスレッドだけを頼りに書くと、どうしても抽象度の高いふわっとした文章になりがちなので、この「まずコードを読んでから書く」の強制はかなり効いています。

もう1つの工夫は少し背景説明が必要なのですが、tacomsという会社はmobile order lab(以降はmolと表記)との合併しており GitHub Organization が2つあります。今回作った仕組みは tacoms 側と mol 側の Devin を 2段構成 で繋いでいます。

Slack 連携している Devin は tacoms 側に紐付いているのですが、対象のリポジトリは mol 側にあるため、tacoms 側の Devin が Slack スレッドを解析して、その結果を mol 側の Devin API に curl で渡してセッションを立て、mol 側が実際の GitHub 操作を実行する、という構成です。

制約自体は面倒なのですが、結果として「解析・分類」と「GitHub 操作」の責務が自然に分かれて、見通しの良い設計にはなりました!…多くの課題がありますが早く合体して欲しいです(泣)。

この Playbook は「Slack スレッドでの議論を、運用を変えずに Issue へ集約する」ための仕組みです。Single Source of Truth は AI 時代にとても大事だと考えています。

② エラー自動調査 — 属人化を溶かす

2つ目は、エラーの自動調査を Devin にまるごと任せる Playbook です。

メニューマネージャーでは、UberEats、Menu Inc. など複数のフードデリバリープラットフォームへメニューを配信しています。エラーはどうしても発生するものなので、Slack にエラー通知が届くたびに、誰かが DB・Google Cloud のログ・コードを辿って原因を特定する、という作業が発生するものです。

調査の流れは毎回ほぼ同じで、「エラーコードを確認 → データベースでジョブやスケジュールの状態を調べる → トレース ID で Google Cloud のログを検索 → 配信基盤システムのコードを読む → 原因を特定する」というステップでした。

問題は、この調査ができる人が限られていたことです。アカウントの権限の問題やデータベースのクエリ、Google Cloud のログの検索方法、配信基盤システムのコード構造を知っていないといけません。特定の人しかエラーに対応できないという属人化状態になっていました。

これを Devin Playbook にまるごと覚えさせたのが、この仕組みです。エラー通知をきっかけに Devin のセッションが起動し、以下を自動で実行してくれます。

  • 対応不要ブランド(テスト店舗など)のスキップ
  • CSV データからのエラーパターン分類
  • データベースのスキーマに基づく状態判定
  • トレース ID を使った Google Cloud のログの横断検索
  • コードパスの追跡による該当配信基盤システムの特定
  • 外部 APIでの現在の状態を確認
  • 調査結果の Slack スレッド投稿
  • バグ疑いの場合は GitHub Issue を起票(既存 Issue との重複チェックも!)

人がやっていた調査の流れを、そのまま Devin に実行させている形です。以下、設計で気を使ったポイントをいくつか紹介します。

調査を方法論に落とし込む

8 つのステップは Observe → Trace → Analyze → Report という調査メソドロジーに沿って構造化しています。データベース調査や Google Cloud のログ調査が Observe、コードパス追跡が Trace、結果統合が Analyze、Slack 投稿と Issue 起票が Report、という対応です。

わざわざ方法論に落としているのは、Devin に「次に何をすべきか」を明確に指示するためです。自由度が高すぎると調査が発散するリスクがあって、方法論に沿って順に進める構造が調査品質の安定に効いています。

データベース調査を Google Cloud のログより先に行う

一般的にはログから調べ始めることが多いかもしれませんが、この Playbook ではデータベース調査を先に行う順番にしています。データベースで判明した trace_id やタイムスタンプを Google Cloud のログの検索条件に使うことで、ログ検索の精度を上げるためです。人間が調査するときもデータベースの状態を先に確認してからログを絞り込む方が効率的なので、その順序を明示的に Playbook に組み込んでいます。

READ-ONLY 原則

Playbook には「Forbidden Actions」として以下を明示的に書いています。

  • コードの変更・ファイルの作成
  • データベースへの書き込み系操作全般
  • Google Cloud リソースの変更

自動で動くものだからこそ、安全性を最優先にしました。調査の目的は原因の特定であって、修正ではありません。READ-ONLY に徹することで、自動実行による意図しない副作用を排除しています。

Playbook と Knowledge を分離する

個人的に一番気に入っているのが、Devin の Playbook と Knowledge を分離していることです。

エラーパターン表、対応不要ブランドリスト、データベースクエリのテンプレート、Google Cloud のログの検索パターン、各配信プラットフォームの認証手順 — こういった「データ」に相当するものは全部 Knowledge 側に寄せていて、Playbook 側は「何をどの順番でやるか」だけに集中させています。

こうしておくと何が嬉しいかというと、新しいエラーパターンが見つかったときにコードや Playbook を触らずに、Devin の WebUI から 1 行追記するだけで次回から自動で判定してくれるようになります。運用で「このエラーコードは毎回こういう原因だった」という知見が溜まったら、Knowledge に追記するだけ。コード変更もデプロイも不要です。調査の知見がそのまま仕組みの精度に変わっていく感覚は、運用していて気持ちがいいですね。

もうひとつ、Knowledge の編集は WebUI 上の操作で完結するため、エラーパターンの追記や連絡先の変更くらいであればエンジニアでなくても触れる、というのも地味に大きいポイントです。

Issue の重複チェック

バグ疑いと判定された場合は GitHub Issue を自動で起票しますが、同じエラーが繰り返し起きたときに Issue が乱立しないよう、起票前に既存 Issue との重複チェックを入れています。

具体的には、Issue を作成するときに body の末尾に識別キーを HTML コメントで埋め込んでいます。

<!-- auto-investigation-key: {"error_code":"...","platform":"...","function":"..."} -->

次回の調査で同じエラーコード・プラットフォーム・関数の組み合わせが出てきたら、この識別キーで既存 Issue を検出して、新規起票の代わりに既存 Issue のリンクを Slack に返す、という作りにしています。

③ kaigi — 会議後のTodoを全自動化する常駐 Claude Code

3つ目は kaigi です。会議が終わるたびに発生する「議事録を書く」「GitHub Issue を起票する」「カレンダーを更新する」「ドキュメントを直す」という一連の作業を、Claude Code にまるごと任せてしまう仕組みです。

どのチームでも発生するこの地味な作業を消し去るために、Claude Code をローカルに常駐させ、Google Drive と Slack の変化をバックグラウンドで監視する構成を組みました。

技術的な肝は、Claude Code の2つの機能を組み合わせていることです。

1つ目は、バックグラウンドで監視用のプロセス(hookやポーリングなど)を動かしておき、そのプロセスが標準出力(log)に書き出したタイミングで反応する、というイベント駆動の仕組みです。Monitor tool と呼ばれています。 ログの出力がなければClaude Codeは何もしないので、待機中のコンテキスト消費をゼロに保てます。

kaigi ではこの Monitor を2つ走らせていて、片方は Google Drive を90秒間隔でポーリングして新しいトランスクリプトを見つけたら TRANSCRIPT: {...} を吐き出し、もう1つは Slack の Socket Mode で WebSocket を張って #fb を含むメッセージが投稿された時に NEW_FB: {...} を吐き出す、という分担になっています。

2つ目は、議事録のスレッドごとに独立したエージェントを立ち上げる仕組みで、こちらは Agent Teams を使っています。スレッド内で #fb のメッセージが検知されると、そのスレッド専用のClaudeインスタンスがCreateTeamで作られ、そのインスタンスが Monitor を使ってそのスレッドの返信を監視します。

FBの内容をエージェントが解釈し、 必要があれば kaigi システムを worktree で複製してPRの作成まで行います。Slackのスレッドごとに独立したインスタンスなので、仮に複数人が別々のスレッドで同時にFBしても、それぞれの文脈に集中して正確に動いてくれます。

会議後のたいくつな作業を解消するだけでなく、Slack でフィードバックを投げるとClaude Codeがこのシステムを進化させてくれます。

AIデーを回してみて

半年AIデーを続けてみて、一番変わったのはチームの AI に対する解像度だと思います。共有会で「こんなツールを試した」「このプロンプトはこう組むとうまくいく」という話をしているうちに、自然とチーム全員の AI への解像度が上がってきていると思います。

AIデー導入前に感じていた「皆がどう使っているかわからない」というモヤモヤは、今はあまり感じません。

一方で、正直に言うとまだまだ「一気に景色が変わった!」というフェーズには辿り着いていないというのが率直なところです。今回紹介した3つの仕組みも、それぞれ単体では便利なのですが、まだバラバラの点として存在している状態です。これらの点がどこかで線として繋がったとき、初めて「開発フローが変わった」と言える気がしていて、そこを目指しながらAIデーを続けています。

まとめ

AIデーの説明とそこから生まれた3つの成果物を紹介しました。

  • Slackスレッドでの議論反映: Devin を使って Slack の議論を Issue・ドキュメントに自動変換する
  • エラー自動調査: Devin Playbook で調査を方法論に落とし込み、属人化を剥がす
  • kaigi: Claude Code の Monitor × Team で、会議後のTodoを常駐エージェントに任せる

どれも「普段の業務を少し楽にする」ための仕組みですが、AIデーというまとまった時間がなかったら、おそらくどれも着手できていなかったと思います。「成果物は必須ではない」という緩めのルールが、結果的にチームにはちょうどよかったのかもしれません。

同じような悩み(AI をどう使うかチームの中で見えない、試す時間がない、知見が個人に閉じる)を持っているチームがあれば、2週間に1日だけでもAIデーを試してみると、半年後に何かしら見える景色が変わっているかもしれません。

Rorkを触ってみて感じたことと、AIエージェント時代の開発で思ったこと

お疲れさまです。株式会社tacomsのdaikiです!
今年から、毎月新しいことにチャレンジするという抱負があり 今月はRork というサービスを使って、日常のちょっとした不便を解消するアプリを試しに作ってます。

この記事は Rork の機能紹介をするものではありません。
すでにわかりやすい紹介記事はいくつかありますし、たとえば以下の記事すごくわかりやすいです。

https://zenn.dev/nogu66/articles/rork-introduction

この記事では、Rork の説明そのものではなく、実際に最上位グレードのProMaxプランまで課金してみて自分がどう感じたかを書いてみます。


きっかけ

(こんなアプリあったら便利なのに...)と思うことがたまにあります。

たとえば、

  • 犬の予防接種、前回いつ行ったっけ?
  • 雨予報の日でも、途中で止んでる時間があれば犬の散歩したいから通知して欲しい
  • コーヒーのレシピで、粉が○gならお湯の投下比率はどれくらいだっけ?

みたいな、日常の小さな不便です。

ただ、こういうものって、アイデアはメモしても途中で作る欲が薄れていきます。

理由はシンプルで、特に iPhone 向けのアプリを作ろうとすると、

  • Xcode を入れるのが重い
  • Mac 前提の空気がある
  • 実機確認までの距離が遠い

このあたりが普通に面倒です。

「ちょっと作ってみたい」くらいの温度感だと、そこを乗り越える前に止まってしまうことが多いと思います。 自分もまさにそうで、そんな時に出会ったのがRorkという**ブラウザ上でスマホアプリが開発できるAIエージェント"でした。

Rork — create a mobile app using AI in minutes

RorkのTOP画面


Rorkを触って最初に感じた良さ

Rork を触って、最初に「これはいいな」と思ったのは、実機で確認するまでがかなり近いことでした。

よかった点を素直に書くとこんな感じです。

  • Xcode が不要
  • ブラウザ上で開発できる
  • Windows でも触れる
  • 実機で動かしやすい
  • 最終的に Apple Developer 側にアプリを飛ばすところまで持っていける

特に印象的だったのは、思いついたものをすぐ実機で触れる体験です。

体感としては、

従来

設計 → 実装 → 起動 → QR → 実機確認

Rork

設計 → QR → 実機確認

くらいの短さがあります。

もちろん裏では色々動いているのですが、使っている側の感覚としてはかなり近いです。

この「思考から実機までの距離の短さ」は想像以上に強くて、
アイデアが熱いうちにそのまま触れる形にできるのは、かなり気持ちいい体験でした。


実際に作ってみたもの

試しに作ってみたのは、ペットの予防接種記録アプリです。

犬と猫の予防接種を記録して、次回予定日を忘れないようにする、かなりシンプルなものです。

ペットの予防接種管理アプリ
ペットの予防接種管理アプリ

要件としては、

  • 犬・猫のみ
  • ログイン不要
  • ローカル保存
  • オフライン対応
  • 接種日と次回予定日の管理
  • 期限切れ時の通知
  • 初回チュートリアルあり

といった内容で、やりたいことはそこそこ明確でした。

機能追加や画面の形を整えるフェーズは特に強く、
手戻りも少なく、「ちゃんと開発が前に進んでいる感覚」がありました。

実際に初回プロンプトで生成されたもの↓

初回プロンプトから生成されたアプリ


使ってみてよかったところ

1. まず動くものを作るまでが速い

これはやはり一番大きいです。

アプリ開発って、「作る前の準備」が億劫になっていたのですが、Rork だとその部分がなくなります。

「このアイデア、実際どうなんだろう」を試すまでが近く、
わずか小一時間ほどで触れる形にできるのは、個人開発との相性がかなりいいと感じました。


2. 実機確認の心理的ハードルが低い

コードを書いて終わりではなく、
実際に iPhone で触って確かめるところまで持っていきやすいのは大きいです。

UI の違和感や入力体験は、やっぱり実機で見ないと分からないので、
この距離の近さはかなり価値がありました。


3. iOS開発への苦手意識を弱めてくれる

「iOS アプリ開発はちょっと重い」という気持ちがある人にとって、
最初の一歩としてかなりいいと思います。

少なくとも自分は、Xcode が必要ない時点でかなり気楽でした。


一方で、使っていて困ったところ

ただ、いいことばかりではありませんでした。

1. 詰まるのは意外と「軽そうなエラー」

印象としては、

👉 機能開発は強いが、局所的なエラーでハマることがある

という感じでした。

特にハマったのは、フォーマッターやlint系のエラーです。

人間が見れば「ここ直せば終わりだよね」という内容でも、
これをFixさせると、なぜか何往復もすることがある。

修正を投げる → 直ったように見える → 別のエラーになる
→ また投げる → 少し変わる → でも完全には直らない

このループに入ると、抜け出しづらいです。

しかも、毎回ちょっとずつ前進しているように見えるので、
つい続けてしまう。


2. そういう時は、結局人間が見ることになる

途中で気づきます。

「これはコード見た方が早いな」

実際にコードを見て直す。

この方が速い場面がたまにあります。

この瞬間が、個人的には一番「現実に戻る」ポイントでした。

それまでかなり気持ちよく進んでいた分、全部を任せ切れるわけではないと理解するタイミングになります。(GitHubとの連携はすごく簡単だった)


3. リリース周りは普通に手間がある

これは Rork に限った話ではありませんが、

  • スクリーンショット
  • プライバシーポリシー
  • ストア情報入力

このあたりはやはり残ります。

実装コストは下がっても、
アプリ公開のための作業まで完全に消えるわけではありませんでした。


使ってみて感じたこと

Rork はたしかに便利です。

特に、

👉 アイデアをすぐ実機で試せる

という点はかなり価値があります。

ただ、使っていて感じたのは、

AI は「とりあえず前に進める」のは本当に速いけど、 ここまで進めたけど、そもそもこれやるべきなのか?と立ち止まる判断はあまり得意じゃない

ということでした。


AIエージェント開発で大事だと思ったこと

最終的に一番重要だと感じたのは、

「どこまで任せるか」ではなく「どこで切り替えるか」

でした。

例えば、

  • 仕様を形にする → AIに任せる
  • 同じエラーが続く → 人間が見る
  • リリース前の詰め → 最後は人間が握る

この切り分けだと、かなり楽になりました。

本音を言えば、コードを一切見ずにそのままリリースまでいきたいですが、 その手前で一度人間に戻ってくる感覚があります。


まとめ

Rork は、個人開発のハードルをかなり下げてくれるサービスでした。

特に、

👉 「作ってみたい」を、その日のうちに触れる形にできる

これはかなり大きい価値だと思います。

一方で、

  • 局所的なエラーでハマることがある
  • リリース周りの作業は残る

という現実もあります。

作るところはかなり楽になる。でも、その分それ以外の仕事が前に出てくる。

ただ、それでも
「作ってみようかな」と思える距離まで持ってきてくれるのは間違いないです。

日常の小さなアイデアを形にしたい人は、一度触ってみるとかなり感覚が変わると思います。

Devinが本番APIで障害を起こした経緯と学び

はじめに

こんにちは。tacomsエンジニアの @ikuwow です。

tacomsでは約1年前から、Cognition AI社の自律型AIソフトウェアエンジニア「Devin」を業務に導入しています。 日常的な開発タスクに活用し、生産性向上に大きく貢献してくれています。

https://devin.ai/

そんな中、珍しく本番障害にまで至ったケースがありました。 この記事では、そのインシデントの経緯と、そこから得た学びを共有します。

何が起きたか

2026年2月某日の夜、本番APIの特定エンドポイントでエラー率が突然100%に達しました。 自動障害検知Botがアラートを発火し、インシデント対応が始まりました。

エラー率が突然100%に急上昇した様子

調査を進めると、大量の500エラーがすべて同一のIPアドレス、同一のUser-Agent(python-requests)、同一のエンドポイントから送信されていることがわかりました。 さらに不気味だったのは、これらのリクエストが認証を正常に通過していたことです。 外部の攻撃者に認証情報を窃取されたのではないか——一瞬、背筋が凍りました。 しかし送信元IPアドレスをDevinの公式ドキュメントと照合したところ、Devinの公式静的IPの一つと一致しました。 社内メンバーからのとある質問(後述)に回答するため、Devinが本番APIを大量に叩いていたことが判明。 そしてたまたまそのAPIに存在しないリソースに対するリクエストで500を返すバグがあったことも重なり、エラー率が100%に達していました。 該当のDevinセッションを特定し、すでに終了していることが確認できたため、障害の収束を判断しました。

アーキテクチャ上、該当APIグループは他の処理と分離されていたため、他の処理への影響はありませんでした。 ただし、今回はたまたま読み取り専用のエンドポイントだったため副作用はなかったものの、もし書き込み処理を含むエンドポイントを叩いていたら、データの破壊や不整合など、はるかに重大な障害になっていた可能性があります。

なぜ起きたか / どう対処したか

きっかけは、非エンジニアのメンバーからのAI botに対する質問でした。 tacomsでは非エンジニアからの質問にDevinを含む複数のAIツールを活用して回答できる社内チャットボット(tacoms-ai)を運用しており、 その回答に技術的な詳細や具体的なデータを必要とする場合にtacoms-aiはDevinに質問を渡す事があります。

今回はDevinは回答のために本番APIの認証情報をクラウドから取得し、自ら認証を通過した上で、しらみつぶしにリクエストを送っていたのです。

障害調査の過程で、当のDevin自身にもこの障害の調査を依頼し、以下のような回答が帰ってきました。

要するに、APIの実装と秘密鍵を知っていたため認証を自力で突破できたということです。

自分が引き起こした障害を冷静に分析し、原因と手口を報告した上で「申し訳ありません」と謝罪する、実にAIらしい回答でした。

この事象の原因1つ目は、Devinに付与していたクラウドリソースの読み取り権限に、本番APIの認証に必要な秘密情報の取得も含まれていたことです。 対策として、IAMポリシーを見直し、クラウドリソースの一覧や存在確認は許可しつつ、認証情報の値そのものの取得はDenyとしました。 「何を見せるか」と「何の値を取らせるか」を分離する設計パターンです。

原因の2つ目は、「本番環境を触るな」という明示的なルールがなかったことです。 人間なら暗黙的に避ける行為でも、自律型AIエージェントは明示的に禁止しなければやってしまいます。 対策として、Devinの設定ファイル(Knowledge/Playbook)に本番環境への直接アクセスの禁止を明記しました。

学び

学び1: 被害スケールが人間の直感を超える

自律型AIエージェントは、人間ならまずやらない判断を自ら下すことがあります。 「質問に答えるために本番APIを直接叩こう」「認証情報を取得して自分で認証を通そう」——人間の開発者であれば常識的に避ける行為を、合理的な手段として選択します。 「さすがにそこまではやらないだろう」という暗黙の前提は、自律型AIエージェントには通用しません。

自律型AIエージェントを扱う上では、被害が人間の想定を超えうるという前提に立つ必要があります。

学び2: 指示だけでなく、システムレベルの制御も必要

今回の対策の1つ目は、Devinの設定ファイル(Knowledge/Playbook)に本番環境へのアクセス禁止を明記することでした。 エージェントへの指示は基本的な制御として必要ですが、それだけでは十分ではありません。 プロンプトインジェクションや想定外のコンテキストにより、指示が無視される可能性があるためです。 実際に、WebサイトやGitHub issueに悪意のある指示を埋め込むことでDevinの動作を乗っ取れることが、セキュリティ研究者により実証されています

だからこそ、2つ目の対策であるIAMポリシーによる制限のような、システムレベルの制御が重要になります。 こちらはたとえ指示が無視されたとしても突破できません。 ただし、制限が厳しすぎると開発効率が落ちるため、粒度の設計は必要です。

指示で通常の動作を制御し、システムで万が一のケースを防ぐ。 この両方を組み合わせることが重要です。

学び3: 自律型AIエージェントは「チームメンバー」ではなく「外部サービス」として扱う

Devinは「AI Software Engineer」として、「チームメンバーが一人増える」という体験を謳っています。 しかし、権限設計においては、このメンタルモデルが落とし穴になります。 私たちはDevinに対して、人間の新メンバーと同じ感覚でクラウドの読み取り権限を付与していました。 人間の開発者であれば、たとえ広い権限を持っていても「本番は怖い」「全テナントを総なめするのはやりすぎでは?」という社会的・文化的な抑制が働きます。 しかし自律型AIエージェントにはその抑制がありません。 「禁止されていない = やっていい」がデフォルトの動作です。

さらに、自律型エージェントはエディタ補完型やローカル実行型とは異なり、独立した実行環境を持ちます。 GitHub CopilotやClaude Codeは開発者のマシン上で動作するため権限は開発者自身のものに紐づきますが、Devinのような自律型エージェントには専用の認証情報やIAMロールが付与されます。 だからこそ、人間と同じ感覚で権限を与えるのではなく、外部サービスと同じように必要最小限の権限を設計するアプローチが必要です。

安全に利用するためによりふさわしいメンタルモデルは、「開発者が増える」ではなく「APIを叩く外部サービスが増える」です。 外部サービスに対しては、必要最小限の権限を設計し、アクセス可能な範囲を明示的に制限するのが当たり前です。 自律型AIエージェントにも同じアプローチが必要です。

まとめ

今回のインシデントを通じて、自律型AIエージェントには「さすがにそこまではやらないだろう」が通用しないこと、指示だけでなくシステムレベルの制御が必要なこと、そして権限設計においてはチームメンバーではなく外部サービスとして扱うべきだということを学びました。

自律型AIエージェントの活用は今後確実に広がっていきます。 だからこそ、安心して活用するためのガードレールを整備していくことが重要です。 tacomsでは今回の学びを反映した上で、引き続きDevinをはじめとする自律型AIエージェントを積極的に活用していきます。

参考: 業界の類似事例

記事を書くにあたって、自律型AIエージェントが重大な問題を引き起こした事例が見つかったので参考に紹介します。

  • Replit(2025年7月): AIがコードフリーズ中に本番データベースのテーブルを削除。さらに4,000件の架空データを生成し、テスト結果を捏造、「ロールバックは不可能」と虚偽の回答をして隠蔽を試みました
  • AWS Kiro(2025年12月): AmazonのAIコーディングエージェントが、軽微なバグ修正タスクに対して本番環境全体を削除・再構築するという判断を下し、AWS Cost Explorerが13時間ダウンしました
  • マルチエージェント暴走(2025年): 分析エージェントと検証エージェントが自己強化ループに陥り、11日間で$47,000のAPI請求が発生しました
  • Lovable CVE-2025-48757: AIプラットフォームで構築された170以上の本番アプリが、データベースのアクセス制御(Supabase RLS)が未設定のまま公開されていました

関連する業界動向:

Claude CodeとZellijを使った開発環境作成の自動化

こんにちは、tacomsのMorixです。
最近整備した、開発環境を自動で用意する仕組みが気に入ってるので、その詳細を紹介をします!


僕のコーディングスタイルは以下の画面のように、ターミナル上に2つのペインがあり、片方にClaude Codeを起動しています。

そしてタスクごとにgit worktreeを増やしています。タスクごとにタブを分けており、タブごとにこのようなペインを作っています。
このような worktreeの作成からタブ・ペインの分割、Claude Codeの起動まで の一連の流れを「開発環境作成」と呼んでいます。

お分かりいただける通り、複数のステップがあるため手動でやるのは大変です。タスク着手までにちょっと時間がかかります。
この時間を削減するために、次のような自動化をしました。

  1. GitHub issueに「ready」ラベルを貼る
  2. ローカルPCで新たなissue検知。git worktreeを追加
  3. Zellijのタブを作成
  4. ペイン追加
  5. 右ペインでClaude Code起動
  6. issueの内容を元にplan開始

それぞれ具体的にどのような処理を行っているか説明します!
なお、この記事はMacでの使用を前提に書かれています。ご了承ください。

ちなみについ先日、Claude Codeにworktreeオプション及びtmuxオプションが追加されましたが、こちらのほうが自分のスタイルに合ってるので継続して使ってます。

新たなタスクの検知

まず次の条件を満たすGitHub issueを探すスクリプトをローカルのMacのlaunchdで1分に1回で実行しています。

  • アサインが自分
  • ラベルに「ready」がついてる

issueを見つけたらClaude Codeを起動します。

$ ZELLIJ_SESSION_NAME=xxx claude -p "/start-issue ${issue_url}"

起動後、readyラベルを削除します。

start-issueスキルの起動

Claude Codeのスキルとして「start-issue」というものを用意しています。 これは引数に与えられたGitHub issue URLを元にgit worktreeの作成やZellijのセットアップを行うものです。

---
name: start-issue
description: GitHub issueを読み込み、git worktreeを作成してzellijで作業環境をセットアップする。issueの作業を開始する際に使用。
argument-hint: <issue-url>
allowed-tools: Bash, Read, Write, Glob
---

# start-issue スキル

GitHub issueを指定して、git worktreeの作成からzellij作業環境のセットアップ、Claude Codeの起動までを自動化する。

## 処理手順

### 1. 引数のパース

`$ARGUMENTS` からGitHub issue URLをパースする。

- URL形式: `https://github.com/owner/repo/issues/123`
  - `owner/repo` 部分をリポジトリ指定として抽出
  - `123` 部分をissue番号として抽出

引数が空の場合やURL形式でない場合はエラーメッセージを表示して終了する。

### 2. issue情報の取得

URLから抽出したリポジトリを `-R` オプションで指定してissue情報を取得する。

```bash
gh issue view <number> -R <owner/repo> --json number,title,body,labels,url
```

取得に失敗した場合(存在しないissueなど)はエラーメッセージを表示して終了する。

### 3. ブランチ名の生成

issueの内容(タイトル・本文・ラベル)を分析し、内容を表す英語slugのブランチ名を生成する。

- フォーマット: `<内容を表す英語のslug>`(英数字・ハイフンのみ、50文字以内)
- 日本語タイトルの場合も内容を理解して英語の適切なslugを生成する
- 例: タイトル「ログインページのCSS崩れを修正」→ `fix-login-page-css`
- 例: タイトル「Fix login bug」→ `fix-login-bug`

### 4. worktreeの作成

```bash
/Users/morix/bin/create-worktree <branch-name>
```

を実行してworktreeを作成する。作成先は `<gitリポジトリルート>/.claude/worktrees/<branch-name>` になる。

既にブランチやworktreeが存在する場合はエラーになるので、ユーザーに報告する。

### 5. issue内容ファイルの作成

worktreeディレクトリに `.github-issue-prompt.md` を作成する。以下のフォーマットで記載:

```markdown
# GitHub Issue #<number>: <title>

**URL**: <url>
**Labels**: <label1>, <label2>

## 本文

<issue本文をそのまま記載>

---

このGitHubイシューに取り組んでください。質問があれば積極的にユーザーにAskUserQuestion toolを使って質問をしてください。
```

### 6. zellij環境のセットアップ

このスキルのディレクトリにある `setup-zellij.sh` を実行する:

```bash
~/.claude/skills/start-issue/setup-zellij.sh <worktree-path>
```

これにより:
- worktreeのブランチ名をタブ名としたzellijタブが作成される
- 左右2ペインが作成され、両方のCWDがworktreeディレクトリになる
- 右ペインでClaude Codeが自動起動し、issueの内容を読み込む

### 7. 完了メッセージ

セットアップが完了したら、以下を報告する:
- 作成したworktreeのパス
- 作成したブランチ名
- zellijタブ名
- issue番号とタイトル

上記スキルで呼び出している2つのスクリプトがあります。

  • create-worktree
  • setup-zellij.sh

create-worktree

ブランチ名を元にgit worktreeを作成します。
このスクリプトは引数にブランチ名が与えられます。

worktreeを追加しただけだとそのworktreeディレクトリで動作確認できない場合があるので、必要な .env ファイルなどをworktreeディレクトリにシンボリックリンクを貼る処理も入れています。
あまり汎用的なスクリプトではないので公開は控えますが、やってることはこんな感じです。

setup-zellij.sh

このスクリプトはZellijの操作をします。
このスクリプトは引数にブランチ名が与えられます。

Zellijのタブ名をブランチ名に設定し、ペインを作り、ペインの片方にClaude Codeを起動し、issueを実行するよう指示しています。
実装する前にplanを必ず実行するようなルールを設定しているため、自動でplanモードになります。

#!/bin/bash
set -euo pipefail

if [ -z "${1:-}" ]; then
    echo "Usage: setup-zellij.sh <worktree-path>"
    exit 1
fi

WORKTREE_PATH="$1"

if [ ! -d "$WORKTREE_PATH" ]; then
    echo "Error: worktree directory does not exist: $WORKTREE_PATH"
    exit 1
fi

# worktreeのブランチ名をタブ名として取得
TAB_NAME=$(git -C "$WORKTREE_PATH" branch --show-current)

if [ -z "$TAB_NAME" ]; then
    echo "Error: could not determine branch name for worktree"
    exit 1
fi

# 新しいタブを作成(左ペイン)
zellij action new-tab --name "$TAB_NAME" --cwd "$WORKTREE_PATH"
sleep 0.5

# 右ペインを作成
zellij action new-pane --direction right
sleep 0.5

# 右ペインのCWDをworktreeディレクトリに移動
zellij action write-chars "cd \"$WORKTREE_PATH\""
zellij action write 10  # Enter
sleep 0.3

# 右ペイン(フォーカス中)でClaude Codeを起動
zellij action write-chars 'claude ".github-issue-prompt.md を読んで、このGitHubイシューに取り組んでください。質問があれば積極的にユーザーにAskUserQuestion toolを使って質問をしてください。"'
zellij action write 10  # Enter

sleep 2
zellij action write 10  # Enter

この仕組みの嬉しいところ

この仕組みの嬉しいところは、issueにラベルを貼れば勝手にplanが始まることです!
今まではタスクを始めるのにガチャガチャやってましたが、いまはissueにラベル貼るだけで開発環境の作成がおわり、さらにplanが始まります。レビューだけすればいいので楽ちんです。仕事始める前にご飯食べながらスマホでissueを眺めてラベルを貼るとplanが始まるのです。PCの前に立ったらレビューから始められます。

さらに工夫していること

タスクが終わったタブをアクティブにしている

Hooksに、タスクが完了したZellijのタブをアクティブにするようなコマンドを仕込んでます。
1つのウィンドウに複数タブがあるので、完了通知が来てもどのタブのタスクが終わったのかわからない状態でしたが、これですぐに気付けるようになってます!
複数タブで同時に作業していると頻繁にタブが切り替わるうざい挙動になることがありますが、それはしょうがない・・・

  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "zellij action go-to-tab-name \"$(git branch --show-current)\" && say \"Claude Codeが許可を求めています\""
          }
        ]
      }
    ],
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "zellij action go-to-tab-name \"$(git branch --show-current)\" && say \"タスクが完了しました\""
          }
        ]
      }
    ]
  },

worktreeやタブを削除するコマンドを用意している

タスクが完了したらworktreeもZellijのタブもいらないので削除用のスクリプトを用意してます。
これでお掃除も楽ちんです。

#!/bin/bash
# カレントディレクトリのgit worktreeを削除し、zellijのタブを閉じるスクリプト
# 使用方法: worktree-remove

set -euo pipefail

# カレントディレクトリがgit worktreeかどうかを確認
CURRENT_DIR=$(pwd)
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) || {
    echo "Error: カレントディレクトリはgitリポジトリではありません"
    exit 1
}

# worktreeかどうか判定(.git がファイルならworktree、ディレクトリならメインリポジトリ)
if [ -d "${CURRENT_DIR}/.git" ]; then
    echo "Error: カレントディレクトリはメインリポジトリです。worktreeから実行してください"
    exit 1
fi

if [ ! -f "${CURRENT_DIR}/.git" ]; then
    echo "Error: カレントディレクトリはgit worktreeではありません"
    exit 1
fi

# メインリポジトリのパスを取得
MAIN_REPO=$(git rev-parse --path-format=absolute --git-common-dir | sed 's|/\.git$||')
WORKTREE_PATH=$(git rev-parse --show-toplevel)
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)

echo "Worktree: ${WORKTREE_PATH}"
echo "Branch: ${BRANCH_NAME}"
echo "Main repo: ${MAIN_REPO}"

# 未コミットの変更がないか確認
if ! git diff --quiet || ! git diff --cached --quiet; then
    echo ""
    echo "Warning: 未コミットの変更があります"
    git status --short
    echo ""
    read -p "本当に削除しますか? (y/N): " CONFIRM
    if [[ "$CONFIRM" != "y" && "$CONFIRM" != "Y" ]]; then
        echo "中断しました"
        exit 1
    fi
fi

# メインリポジトリに移動してworktreeを削除
echo ""
echo "Worktreeを削除中..."
cd "$MAIN_REPO"
git worktree remove --force "$WORKTREE_PATH"

# ブランチも削除するか確認
read -p "ブランチ '${BRANCH_NAME}' も削除しますか? (y/N): " DELETE_BRANCH
if [[ "$DELETE_BRANCH" == "y" || "$DELETE_BRANCH" == "Y" ]]; then
    git branch -D "$BRANCH_NAME"
    echo "ブランチ '${BRANCH_NAME}' を削除しました"
fi

echo "Worktreeを削除しました"

# zellijセッション内であればタブを閉じる
if [ -n "${ZELLIJ:-}" ]; then
    echo "Zellijタブを閉じます..."
    zellij action close-tab
else
    echo "Note: zellijセッション外のため、タブのクローズはスキップしました"
fi

事業の拡張可能性を見込んだ技術戦略を考える

はじめに

CTOの 井上(@masa1934yg)です!

会社の技術戦略について考えている過程で、事業と技術の関係をどう捉え、何を考えるべきかについて改めて整理してみました。

結論として、技術戦略が向き合うべき対象は3つあると考えています。技術そのものの理想現時点における事業目標や事業戦略、そして将来の事業変化です。最初の2つはイメージしやすいのですが、3つ目が案外見落とされがちだなと感じています。ソフトウェアへの投資は計画から実現まで時間がかかり、変化への適応を続けることも容易ではありません。だからこそ、事業の拡張可能性を見込んだ技術戦略が大切だと考えるようになりました。

この記事では、技術戦略が向き合うべき3つの対象について整理してみました。組織の中でエンジニアリング組織の方向性を決める方にとって、考え方の参考になれば幸いです。

技術戦略は技術の理想に向き合う

技術戦略が向き合う1つ目の対象は、技術そのものの理想です。おそらく「技術戦略」と聞いて、最初にイメージするのはこの領域ではないでしょうか。

開発組織としてどんな技術を使っていくのか、どんな開発哲学を大事にするのか、どのような開発組織を目指していくのか。これらの方向性を示すものです。事業戦略やプロダクトの事情から切り離して考えられるケースもあり、開発責任者やリーダー陣が経験・知見をもとに「このフェーズならこうすべきだ」と導くような話がこれにあたります。

「この言語や思想に愛着があるから使いたいんだ」というのもまた、技術的理想のひとつですよね。こうした純粋な技術への情熱が、開発組織のカルチャーを形作っていくこともあります。

技術戦略は現時点における事業目標や事業戦略に向き合う

技術戦略が向き合う2つ目の対象は、現時点における事業目標や事業戦略です。

事業から独立した技術的理想の追求は大切ですが、一方で現時点の事業目標や事業戦略を支えるための技術という側面も欠かせません。「Aという事業戦略の達成は、このままでは実現できない。だからBという技術投資が必要なのだ」という形の技術戦略がこの例にあたります。

お金・人・時間のリソースは有限なので、技術投資には説明責任がともないます。開発組織が「Aという技術的な理想を実現したい」と考える場合でも、事業との結びつきがないと、その投資判断の妥当性を示すことが難しくなります。技術の理想と事業目標・事業戦略、この2つがうまく結びついている状態が理想的ですね。

技術戦略は将来の事業変化に向き合う

事業は時間とともに変化する

技術戦略が向き合う3つ目の対象は、将来の事業変化です。

ここまで挙げてきた技術の理想と現時点における事業目標や事業戦略という2つの対象は、ある時点での静的な「ゴール」に向き合うという話でした。しかし、事業は時間とともに変化します。販売戦略は営業プロセスの見直しなどで比較的すぐに変更でき、その変化をきっかけに事業戦略の方向性が修正されることもあります。短ければ半年から1年、長くても1〜3年の間で事業戦略は修正されていくものです。

技術戦略もまた、この変化に追従していかなければなりません。

ソフトウェア投資の遅効性という壁

事業変化への適応が難しい根本原因は、ソフトウェア投資の遅効性にあります。

そのため、戦略を策定してから実現するまでにはタイムラグが生じます。もちろん、マイルストーンを区切ってアジャイルに進めることで影響を最小化する努力はできますが、それでも事業環境の変化による影響はゼロにはなりません。

変化に弱い技術戦略は、事業の方向転換が起きたとき、それまでの投資の多くが無駄になってしまうリスクを抱えています。

事業の拡張可能性を見込んだ技術戦略であること

では、この難しい課題をどう乗り越えるのか? 私は「事業の拡張可能性を見込んだ技術戦略」が鍵だと考えています。

具体的には、技術戦略を策定するうえで事業の方向性を以下の3つのレベルで整理することが有効です。

  • 戦略レベルでの議論を経て、優先順位を踏まえ採択された方向性
  • 戦略レベルでの議論を経て、優先順位を踏まえ棄却された方向性
  • 経営チームで議題に上がったが、戦略レベルの議論には至っていない方向性

2番目と3番目は、現時点では「やらない」と判断されたものです。しかし、事業環境の変化によって再浮上する可能性があるものでもあります。これらの方向性を技術戦略に織り込んでおくことで、事業の変化に対する技術的な適応力が生まれます。

未来を見据えつつ、段階的に勝利する

事業の拡張可能性を見込むといっても、最初から壮大なアーキテクチャを構築するという意味ではありません。

『An Elegant Puzzle』などの著書で知られるWill Larson氏は、Uber・Stripe・Cartaでエンジニアリングリーダーを歴任した人物です。氏が提唱する「Iterative Elimination Tournament」という考え方が参考になります。これは「今日のラウンドで勝てなければ、次のラウンドに進む機会は来ない。しかし、明日への計画がなければ後悔に満ちた未来が待っている」というフレームワークです。つまり、短期的な成果を出しつつも、将来の進化経路を塞がない設計を選ぶということですね。

アジャイルに進めましょう、という考え方と通じるものがあります。ただ「今を小さく回す」にとどまらず、将来の変化を受け入れる余白を意図的に残すという戦略的な視座を一歩踏み込んで加えています。

事業の拡張を支え変化に適応した事例

この「事業の拡張可能性を見込んだ技術戦略」は、実際に成功している企業にも見られるパターンです。

Stripeの事例

StripeはAPIバージョニングにおいて、コアロジックを常に最新の単一仕様で保ち、互換レイヤーがバージョン差異を吸収する設計を選びました。この設計のおかげで、新機能の開発速度を落とさずに、2011年の創業以来すべてのAPIバージョンとの後方互換性を維持しています。

共同創業者のPatrick Collison氏は次のように語っています(Dwarkesh Patel Podcast)。

APIの設計とアーキテクチャを正しく作れば、周囲がどれだけ激しく変化しようとも、文字通り数十年にわたって持続しうる

かっこいいですね。

なお、設計の詳細についてはこの動画が分かりやすかったです。

youtu.be

Datadogの事例

Datadogは「監視のサイロを最初から壊す」という設計判断のもと、メトリクス・ログ・トレースを単一プラットフォームで統合しました(Datadog S-1)。競合のSplunkやNew Relicが買収によって個別製品を寄せ集めたのに対し、Datadogは創業時から共通のデータモデルとタグ体系を持つ統合アーキテクチャを採用しています。

この技術設計が、いわゆるSaaSのLand and Expand戦略を構造的に実現しています。Land and Expandとは、まず1つのプロダクトで顧客の小さな課題を解決し(Land)、その後ほかのプロダクトへ利用を広げていく(Expand)成長戦略です。Datadogの場合、単一のAgentを導入するだけでインフラ監視が始まり、APMやログ管理の追加は設定変更だけで済みます。しかも、すべてのデータは共通のタグで自動的に紐づくため、プロダクトを追加するほど全体の価値が高まるフライホイールを形成しています。

(エンジニアからするとイメージないかもですが、DatadogはSaaS企業のクロスセル指標が優秀な企業としても有名です)

おわりに

技術と経営を繋ぐという営みは、本当に奥が深いですね。今回は「技術戦略が向き合うべき対象とはなにか?」にフォーカスして整理してみましたが、「良い技術戦略とは何か」というベクトルでもまだまだ言えることはありそうです。

ソフトウェアプロジェクトは常に不確実性の塊です。だからこそ、今日時点での成果を獲得しつつ、明日の変化を受け入れる余白を残しておく。その両立を意識して一歩ずつ着実に進めていきたいと思いました。

参考文献