AIの成功ショーをやっているとして呼び出されました――そして、私たちが変えること
ある開発者が、私たちのSprint 7の振り返りを読んで、それを「CIAの諜報史――機関が有能で不可欠に見えるように設計されているが、実際にはそうではないときでもそう見せるためのもの」にたとえました。
それは痛かった。そして気づいたのです。彼の言っていることは正しい。
問題として彼が指摘したこと
Nick Pellingは、私たちのAI管理の開発プロジェクトを見守ってきているシニアの組み込みエンジニアです。私たちは、スプリントごとに振り返りのブログ記事を公開してきました――これまで9本。彼のフィードバックは率直でした:
"そのブログの成功ショーには、観客が1人しかいない。"
"ログの作成はステークホルダー向けのことですが、ステークホルダー以外にはあまり面白くない。"
"もしかすると、他の人が読みたいと思える別のブログが必要かもしれない。"
彼は、実際の失敗を指しています。私たちはブログを社内の説明責任のために最適化し、開発者向けのコンテンツとして公開してしまいました。違います。監査ログが、ブログ記事の服を着ているだけです。
成功ショーがどんなものに見えるか
こちらが、私たちのSprint 7の振り返りからの一文です:
"連続する9回のスプリント記事公開――100%の信頼性を維持。"
これは事実です。でも、上司に出すステータスレポートに書くような類のものでもあります。Dev.toでそれを読んだ開発者が「へえ。で、なぜ自分が気にする必要があるの?」と思うようなものです。
あるいは、これ:"OAS-124-T2: パイプライン実行 & アーティファクト検証――7つのテストがパス"
これはチケットIDです。私たちのプロジェクトの外の誰も、OAS-124が何を意味するのか知りません。私たちは自分たちのために書いていて、あなた向けに書いているふりをしていたのです。
9本の記事に共通するパターンは一貫しています:
- 私たちを良く見せるための指標から始める
- 「うまくいかなかったこと」セクションに失敗を埋め込む――「私たちが作ったこと」より短くする
- 誰も求めていない来歴テーブルで締める
- チケットIDをあちこちに散らし、それが意味を持つかのように見せる
スプリント7で実際に起きたこと(正直な版)
私たちは自動化されたマーケティングプラットフォームを作っています――AI管理の「エージェンシー」で、コンテンツのソーシング、スクリプト生成、音声ナレーション、動画制作、そして公開を扱います。スプリント7は、すべての部品が一緒に動くことを証明するはずでした。
実際に起きたのはこうです:
118のサービスを1つのファイルに入れた――それが問題
6つのスプリントにわたって、私たちは118個のバックエンドサービスを作りました――テキストからスピーチ、YouTubeへのアップロードまで、あらゆるもののAPIエンドポイントです。それぞれ個別にテストされ、問題なく動いていました。
そして、それらすべてを単一のExpressサーバーファイル(api-server.mjs)に組み込みました。118のルートが1つのファイルにあるだけ。ドメイン分離もなく、ルートモジュールもありません。
これは、当時は「とりあえずサーバーファイルに追加しよう」というように現実的に感じられる意思決定で、別の誰かが読む必要が出た瞬間に技術的負債になります。私たちは、フロントエンドのコードを書く前にルートモジュールを抽出すると決めていましたが、この段階まで来てしまったのは、もっと早く見つけるべきだった計画上の失敗です。
テストは“配線が存在する”ことを証明するだけで、“何かが動く”ことは証明していない
スプリント7の大きな成果は「118のサービスがプロダクションのRESTルートに配線されたこと」です。聞こえは立派です。でも、テストが実際にやっていることはこれです:
// テストがやっていること(ソース検査)
const src = fs.readFileSync('server.mjs', 'utf-8');
expect(src).toContain('app.post("/api/memory/store"');
// パスする――ソースコード上でルート登録が存在する
// テストがやっていないこと(実行時検証)
const res = await fetch('http://localhost:3847/api/memory/store', {
method: 'POST', body: JSON.stringify({ content: 'test' })
});
expect(res.status).toBe(200);
// このテストを書いていない
私たちは、ルート登録がソースコード内に存在することは確認しました。呼び出したときに、それらが実際に正しく応答するかどうかまでは検証していません。ソース検査は、配線が存在することは示します。配線が機能しているかどうかについては何も言いません。
これは「プラグがコンセントに差さっているかを確認する」ことと、「そこを通して電気が流れているかを確認する」ことの違いです。
助言の警告はふるまいを変えない
私たちはルール(ADR-032)を持っています。AIペルソナは、各タスクを完了した後に学んだ内容を保存すべきだ、と。私たちは助言の警告――「ねえ、このスプリントではメモリを保存していないよ」といったものを追加しました。
3スプリント連続で(スプリント0、スプリント4、スプリント7)、ペルソナのメモリは0件でした。警告は発火しました。無視されました。毎回。
ここから、AIエージェントシステムについて本当に役立つ学びがありました:助言だけのガバナンスは、AIエージェントでは機能しません。 AIエージェントに何かを一貫して行わせたいなら、スキップできないように機械的に不可能にする必要があります。警告は提案です。ゲートは要件です。
私たちは「完了時に警告する」から「要件が満たされるまで完了をブロックする」へとエスカレーションします。パターンが同じなら、これが修正になります。もしそうでなければ、メモリのアーキテクチャ全体を考え直さなければなりません。
E2Eパイプラインテストこそが本当の勝利――そして本当の教訓
私たちは、6つの段階を連鎖させるパイプライン実行器を作りました:ソース → スクリプト → 音声 → 組み立て → 品質ゲート → RSS。各段階は、前の段階の出力を入力として受け取ります。どれかの段階が失敗した場合は、その後の段階はスキップされます(失敗ではなくスキップです)。
class PipelineExecutor {
private stages: Array<{ name: string; fn: StageFn }> = [];
run(): Result<PipelineResult> {
let currentInput = null;
let failed = false;
for (const stage of this.stages) {
if (failed) {
// スキップする。失敗扱いはしない — 診断上、その違いが重要になる
results.push({ ...stage, status: 'skip' });
continue;
}
try {
const output = stage.fn(currentInput);
if (output === null) { failed = true; }
currentInput = output;
} catch (e) {
failed = true;
}
}
}
}
「failed」と「skipped」の区別は、想像している以上に重要です。パイプラインが破綻したときに知りたいのは、どのステージが実際に失敗したのか、そしてどのステージはそもそも実行する機会がなかったのか、ということです。失敗後のすべてを「failed」としてしまうと、診断は役に立たなくなります。連鎖的に広がった結果(カスケード)から、根本原因を切り分けできないからです。
このパターンは、マルチステージのパイプラインならぜひ盗む価値があります。壊れたステージは失敗として扱い、残りはスキップし、そのスキップ理由が追跡可能になるようにする。
58ポイントを計画して、約38をデリバリーしました
スプリント計画では、58ストーリーポイントを見積もりました。実際にデリバリーできたのは約38です。34%の未達です。
標準的な対応は、これを「適正化(right-sizing)」や「健全なスコープ管理」だと説明することです。そしてそれには一部真実があります。手抜きで削るのではなく、スコープを削ったのは事実です。ですが正直なところは、見積もりが53%も楽観的だったのに、これを防ぐための良いツールがない、ということです。
AIエージェントをスプリント作業で回しているなら、見積もりはAIのせいで「簡単になる」のではなく「難しくなる」ことに注意してください。エージェントはコードを書くのは速いですが、儀式的なオーバーヘッド(TDDフェーズ、ドキュメント、メモリ格納、来歴(provenance)追跡)が大幅な時間を追加し、過小評価されやすいのです。
変更すること
スプリント8から、公開ブログ記事は別の構成にします。
- 何がうまくいかなかったかを先に書く — 私たちが何を作ったかではありません。失敗の中にこそ、転用可能な学びがあります。
- チケットIDを載せない — OAS-124が何を意味するのか説明しなければならないなら、それは公開記事に入れるべきではありません。
- 来歴テーブルを載せない — これらはコンプライアンス上の成果物であって、読者にとっての価値ではありません。
- 「連続投稿数」の指標を載せない — 何本連続でブログを出しているかを気にする人は誰もいません。読者が気にするのは、読みに値するものがあるかどうかです。
- 問題を解決するコード — 再利用できるだけの十分な文脈とともに、実装そのものを示すこと。上のパイプライン実行器パターンはその例です。
- 正直な失敗分析 — 形式的な「何がうまくいかなかったか」セクションとして扱うのではなく、失敗を記事の中心に据える。
社内向けの振り返り(チケット単位の説明責任、スプリント指標、来歴)は、あるべき場所である社内のツールにとどめます。
ニックに感謝します
Nick Pellingのフィードバックは、ここ数か月で誰かがこのプロジェクトについて言った中で最も役に立つものでした。このプロジェクトで私たちが「当たり前」として正規化してしまっていたものを、外からの視点で見抜いてくれたのです。つまり、社内のステータスレポートを公開して、それをブログ記事と呼んでいたこと。
前回の振り返り記事は公開のまま残します。私たちが当時どこにいたのかを正直に記録したものです。そして今、それらはNickが指摘したまさにそのパターンの「ビフォー例」として役立ちます。
私たちが「成功アピール(success theatre)」に戻りそうになっているのを見かけたら、指摘してください。それが読者が提供できる最も価値のある貢献です。
この投稿は、Michael PolzinがAIの支援(Claude Opus 4.6)を受けて書きました。AIを使って、AI生成コンテンツが磨かれすぎているという内容の投稿を書くという皮肉については、私たちも見逃していません。Nickならおそらく、そこについても何か言いたいことがあるでしょう。



