23. Gate 1 — Pre-mortem (PM) サブノード
TL;DR(このノードは何をする・専門語ゼロ): 起案された技術判断が 6 ヶ月後に大失敗した前提に立ち、「何が原因で失敗したか」を逆算して書き出す Gate 1 のもう片肺。失敗シナリオは技術・運用・ビジネス/規制・認知の 4 領域から最低 1 件ずつ出すルールで、起こりやすさの目安
probabilityも付ける。判定はせず、後段の Judge が DA の指摘と一緒にまとめて最終整理する。
実装:
drp/src/nodes/socratic.ts内pmPromiseブロック プロンプト SSoT:prompts/production/gate1-pm/prompt.md— KV デプロイ・ADR-0042 Type 1 出力スキーマ:prompts/production/gate1-pm/output_schema.jsonテスト:drp/test-tc-socratic.mjs基盤 ADR: ADR-0071 / ADR-0033 / ADR-0042 / ADR-0056
1. 役割と位置づけ
Gate 1 の 逆算役。半年後の失敗を仮定して原因を逆算するため、DA とは別ベクトルで盲点を露出させる。
- 解決する課題: 採用判断の時点では見えない「採用後に効いてくる」失敗モードを事前に列挙する。技術的失敗だけでなく、運用・規制・認知バイアスも 4 カテゴリ強制で網羅する。
- 設計思想: 情報提供型 + カテゴリ強制。1 種類のリスクに偏らないよう、
category列挙を必須化(4 領域それぞれから最低 1 件)。 - DA との分担: DA は「現在の判断に対する反論」、PM は「採用後 6 ヶ月後の破滅シナリオ」。視点・時間軸が違うため Judge での重複は少ない。
- 兄弟ノード: 並列に走る Devil's Advocate (DA) と、後段の Judge。本ノードを含む 3 ノードは LangGraph 上は socratic 1 ノードに統合されている。
2. フロー図
flowchart LR
T[triage] -->|needsAdr=true| S[socratic 統合ノード]
S --> DA[gate1-da
Devil's Advocate]
S --> PM[gate1-pm
Pre-mortem
6ヶ月後の失敗シナリオ]
DA --> J[gate1-judge
統合・重複排除]
PM --> J
J -->|socraticPass=true
blindSpotFindings| B[body_generation]
style PM fill:#fdd,stroke:#c33
PM と DA は Promise.allSettled で並列実行。PM 単独で失敗しても DA が成功すれば Judge は DA の findings のみで進行する。
3. トリガー条件
| 条件 | 挙動 |
|---|---|
triage が needsAdr=true を返した | 実行 |
triage が needsAdr=false を返した | スキップ — triage が END に飛ばす |
blindSpotFindings.length > 0 | スキップ — 冪等性のため再実行しない |
buildPreGraph() には含まれない。buildGraph() / buildGraphWithWebhook() の main graph 内でのみ動く。
4. 入力
State から
| State フィールド | 用途 |
|---|---|
title | 任意 |
context | 必須。背景・決定内容 |
options | 任意。検討した代替案 |
LLM への user message
Title: {title} ← title 空なら省略
Context:
{context}
Alternatives considered: ← options 空なら省略
{options}
DA とは異なり、constitution.yaml 経由の観点ローテーションは行わない。プロンプト本文の「4 カテゴリ強制」が観点の代わりに機能する。
5. 処理ロジック
1. プロンプト取得: loadPrompt(env, 'gate1-pm', PM_FALLBACK_PROMPT)
2. LLM 呼出: claude-sonnet (MODELS.socratic), temperature=0.8, 90s timeout
3. JSON 抽出: parseJson() でコードフェンス除去 + JSON.parse
4. 整形: findings に source='premortem' を付与し、Judge への raw findings として返却
- PM 独自フィールドの category / probability は raw findings に保持されるが、Judge の出力スキーマには
直接は載らない(severity と evidence に吸収される設計)。
PM のプロンプトは 4 カテゴリ強制を ルール化しているが、LLM が 4 カテゴリを満たさないこともある。後段の Judge が dedupe / severity 分類で吸収する設計。
4 カテゴリ
category | 観点 |
|---|---|
technical | コード・インフラ・性能・セキュリティ起因の失敗 |
operational | デプロイ・監視・保守・オンコール負荷起因の失敗 |
business_regulatory | コスト超過・コンプラ違反・ステークホルダー拒否 |
cognitive | 埋没費用・確証バイアス・スコープ膨張・早すぎる最適化 |
probability ラベル
probability | 意味 |
|---|---|
likely | 半年以内に高確率で発生する |
possible | 発生条件が揃えば起きる |
unlikely | 起きにくいが影響が大きいので記録 |
6. LLM 設定
| 項目 | 値 | 根拠 |
|---|---|---|
| モデル | claude-sonnet (MODELS.socratic) | ADR-0033 — 多様な失敗シナリオ生成のため |
| temperature | 0.8 | ADR-0056 — シナリオ多様性確保。DA より低いのは「4 カテゴリ強制」の制約があるため |
| maxRetries | 3 | createLlm の exponential backoff |
| timeout | 90 秒 | SAMPLE_TIMEOUT_MS |
| コスト目安 | 約 $0.03 / ADR | 入出力合計 約 2〜4 KB |
| レイテンシ | 10〜30 秒 | DA と並列実行のため Gate 1 全体は max(DA, PM) + Judge |
7. 副作用
なし。LiteLLM Gateway 経由の LLM 呼出のみ。
8. 出力
Judge への raw findings(内部受渡し型)
{
source: 'premortem',
title: string, // 60 字目安・「(状況) すると、(具体的に何が起きるか)」形式
severity: 'critical' | 'high' | 'medium' | 'low',
evidence: string, // 最大 3 文
suggestedAction: string, // 動詞始まり 1-2 文
// 以下は LLM 生 JSON にあるが現状の内部型では Judge 入力時に落ちる:
// category, probability
}[] // 4 カテゴリ × 最低 1 件
注記: 現実装の
pmPromiseは category / probability を State 経由では保持しない(socratic.ts内で source/title/severity/evidence/suggestedAction の 5 項目に絞って rawFindings に積む)。 category / probability は Judge へ JSON 文字列化されて user message 経由で渡されるため、 Judge プロンプト側で必要に応じて参照する。
LLM 生 JSON(PM プロンプト直結の生出力)
{
"findings": [
{
"category": "technical | operational | business_regulatory | cognitive",
"title": "...",
"severity": "critical | high | medium | low",
"probability": "likely | possible | unlikely",
"evidence": "...",
"suggested_action": "..."
}
]
}
JSON スキーマは prompts/production/gate1-pm/output_schema.json を SSoT とする。
9. 分岐(次サブノード)
PM は socratic ノード内の Promise.allSettled([daPromise, pmPromise]) で集約される内部処理。LangGraph の addConditionalEdges での分岐はない。
| PM 状態 | 次の扱い |
|---|---|
| 成功 | findings を rawFindings に追加 → Judge へ |
| 失敗 | DA の findings のみで Judge を実行 |
| DA + PM 両方失敗 | skip-through — socraticPass=true, blindSpotFindings=[] で body_generation へ |
10. エラー時の挙動
| 障害パターン | 挙動 | 影響 |
|---|---|---|
| 90s timeout | Promise.race で reject → PM を失敗扱い | DA の findings のみで Judge が動く |
| Gateway 不達 | maxRetries=3 の exponential backoff 後に reject | 同上 |
| JSON 以外を返す | parseJson が throw → PM を失敗扱い | 同上 |
findings 配列欠落 | `data.findings | |
| 4 カテゴリ未充足 | 検出せず通す | Judge の dedupe / severity 分類で吸収。週次で category 分布を観測 |
エラーは event: 'blindspot_node_failed' で構造化ログ出力。
11. 既知の弱点・運用注意
- 4 カテゴリ強制の遵守率: LLM がプロンプト指示を守らず 2-3 カテゴリしか出さないことがある。週次で category 分布を集計し、特定カテゴリ欠落が続けばプロンプトを補強する。
category/probabilityが Judge 出力に直接出ない: 現実装の rawFindings 型では落ちる。観測の都合上、PM 生 JSON のログ出力で残す案は ADR-0102 後の検討事項。- 過剰な悲観: temperature 0.8 で「半年後の失敗」を強制するため、severity=critical が増えがち。Judge の dedupe + actionability 分類で抑制。
- DA との重複: cognitive カテゴリ(確証バイアス・スコープ膨張)は DA の指摘と被ることがある。Judge の
source='both'統合で吸収。 - 時間軸の固定: 6 ヶ月後を仮定するため、長期構造変更(数年スパンの会計制度改正など)は捉えにくい。長期 ADR では別途検討するか、context で明示する。
12. テストケース
| ID | 内容 | 期待 |
|---|---|---|
| TC-BS01-PM | 情報十分な ADR ドラフト | findings.length >= 4 かつ 4 カテゴリそれぞれから最低 1 件 |
| TC-BS02-PM | PM API エラー注入 | event: 'blindspot_node_failed' ログ・DA 単独で Judge が動く |
| TC-BS-PM-CAT | category 欠落シナリオ | 観測ログから category 分布の偏りを検出可能 |
実行: node drp/test-tc-socratic.mjs
13. 過去の設計判断ログ
| 日時 | 変更 | 経緯 |
|---|---|---|
| 2026-05-27 | ADR-0071 採択 + PR #1042 | 旧 Socratic を盲点検出 DAG に再定義・PM を新設 |
| 2026-05-28 | プロンプト KV 化(PR #1090) | loadPrompt(env, 'gate1-pm', ...) に切替・ADR-0085 |
| 2026-05-28 | temperature を 0.95 (DA 同等) → 0.8 へ | 4 カテゴリ強制の安定化のため少し下げた |
| 2026-06 | 4 カテゴリ強制ルール | プロンプト本文で「EXACTLY 4 categories (one per category minimum)」を明文化 |
14. 関連リンク
- 上位ノード: 02_socratic.md — Gate 1 統合ノード
- 兄弟サブノード: 21_gate1_da.md / 22_gate1_judge.md
- 前ノード: 01_triage.md
- 次ノード: 03_body_generation.md
- プロンプト SSoT:
prompts/production/gate1-pm/ - State 定義:
drp/src/state.ts - グラフ定義:
drp/src/graph.ts - 関連 ADR: