僕はいま Shingan(心眼) という、AIエージェントのワークフローを 実行する前に 静的解析するエンジンをGoで書いている。無限ループ、コスト爆発、PII漏洩経路みたいな構造バグを、モデルを一切呼ばずに、走らせる前に見つけるやつだ。
その状態で 6/11 の Code with Claude: Extended Tokyo(Fairmont Tokyo)に行った。で、半日たって気づいたんだけど、登壇者の所属も題材もバラバラなのに、ほぼ全部のセッションが同じことを別の言い方で言っていた。
モデルの賢さと人間の注意力に品質を頼るな。機構で潰せ。
機構の置き場所はセッションごとに違った。evalループだったり、差分テストだったり、入力品質ゲートだったり、ほとんどは実行時の機構だ。僕がやってるのはそれを一番左、つまり実行前まで前倒しする話なので、自分の立ち位置を確認しながら聞く一日になった。気持ちよかったかというと、半分はそうで、半分は「土台は全部揃ってきてる、急げ」みたいな焦りもあった。とりあえず、その話をする。
先に、Shingan が何なのか
コードの静的解析ツールは腐るほどある。ESLint、SonarQube、CodeRabbit、Snyk。全部「ソースコード」が対象だ。
一方で、AIエージェントの「ワークフロー定義」を実行前に構造検証するツールはほぼ無い。調べた限りだと、n8n専用の FlowLint がv0.3系で出始めたくらい。LangGraph 周辺の LangSmith や Systems Inspector は「動かしてから観測・テストする」ランタイム側の道具で、走らせる前にグラフを読むものじゃない。ADK / LangGraph / CrewAI 向けの「実行前」解析はほぼ空白で、Go実装は見当たらなかった。
Shingan はそこを狙っている。発想はコンパイラと同じだ。実行時に落ちる前に、コンパイル時に落とす。エージェントのワークフローを有向グラフとして読んで、走らせる前にこういうのを拾う:
- 終わらないループ(LoopAgent の終了条件が構造的に成立しない、等)
- コスト爆発(あるパスを通ると LLM ノードが何百回も叩かれる、等)
- PII / インジェクションの到達経路(外部入力が untrusted のまま危険ノードに届く経路)
技術スタックは Go + goa + Onion Architecture + Factory Pattern。ルールは Analyzer インターフェースに揃えて goroutine で並行に回す。最初の解析対象は構造体としてそのまま読める Google ADK-Go。ドメイン層に WorkflowGraph を置いて、パーサーはインフラ層のアダプタにしてあるので、対象フレームワークは後で差し替えられる。要するに「ワークフロー静的解析の SonarQube」を目指してる。FlowLint が「n8n専用の ESLint」だとすると、立ち位置はその一段上だ。
アーキテクチャ
データの流れはこう。パーサーがワークフロー定義をグラフに起こして、解析ルールを並行で走らせて、結果を吐く。
Factory が3つ(Parser / Analyzer / Reporter)あるのは、対象フレームワーク・解析ルール・出力形式を独立に足せるようにするため。Onion なので依存はぜんぶ内側(ドメインの WorkflowGraph と解析ルール)を向いていて、ADK-Go だろうが将来 n8n だろうが、外側のパーサーを差し替えるだけで核は触らない。API は goa の design-first で、OpenAPI が DSL から落ちるので、CI から叩くときのスキーマがドリフトしない。
何を入力して、何を返すのか
簡略化した例で。こういう「リトライ用のループ」があるとする(※実際の ADK-Go の API とは細部が違う、構造だけ示す擬似コード):
// 簡略化した擬似コード(構造のみ)
loop := agent.NewLoopAgent("retry",
fetchAgent, // 外部APIを叩く
validateAgent, // 結果を検証する
)
// ↑ 終了条件がどこにも無い。検証が通っても通らなくても fetch に戻る
Shingan はこれをコードとして実行せず、グラフとして読む:
validateAgent から外に出るエッジが無い。OK でも NG でも fetchAgent に戻るだけで、ループから抜ける経路が構造的に存在しない。これは入力を1個も用意しなくても、グラフを辿るだけで分かる。なので走らせる前にこういう指摘を返す(出力イメージ。スキーマは設計中):
{
"rule": "infinite-loop",
"severity": "error",
"node": "retry (LoopAgent)",
"message": "ループの終了条件に到達するパスが存在しません",
"path": ["start", "fetchAgent", "validateAgent", "fetchAgent", "…"],
"cost_note": "fetchAgent が上限なしで再実行されうる(コスト爆発の経路)"
}
サイクル検出は DFS、到達不能ノードは逆向き BFS。アルゴリズム自体は教科書レベルで、難しいのはそこじゃなく「エージェント固有のどの構造を バグと定義するか」のほう。そこがこのプロダクトの本体だと思ってる。
ここまでが前提。ここからがイベントの話。
横串:全部「機構で潰せ」だった
workshop "How we Claude Code"(Jason Schwartz / Anthropic)
ライブで Todo アプリを作りながら "the contract" という検証設計を見せていた。状態を DOM に機械可読で出して、検証仕様(fixtures + invariants)を Claude に書かせて、ページの状態・コード隣のルール・ハーネスの3つをプログラムで突き合わせる。わざと壊しに行く fixture を仕込んで、フレームワークが「嘘」を検出できるかまで見せていた。原則は「最初から検証前提で作れ」「検証可能な単位で割れ」「スタックを跨いで検証しろ」。
締めの一言がずっと残ってる。要は「もうコードを書くことが仕事じゃない。コードが書かれる 条件 を整えるのが仕事だ」。
検証を後付けにするな、最初から設計に入れろ。これは静的解析の思想そのものだ。Shingan は「最初から」を一番左、つまり実行前に置いてるだけ。
Myrealtrip:トークン空間で計算させた本番障害
韓国の旅行プラットフォームの事例。料金計算を「日数カウント+料金バンド照合+減算+自然言語説明」まるごと1回の Claude コールに丸投げしたら本番で死んだ。レイテンシ8秒(Python の timedelta なら1ミリ秒)、同じ入力で ₩90,000 と ₩100,000 のバンドを引き分ける、そして自然言語推論は unit test できないので回帰テストが組めない。直し方は「Claude には抽出だけさせて、計算は Python」。
これ、Shingan 的には 静的に拾える形のアンチパターン だ。「LLM ノードの責務に決定的計算(算術・日付・バンド照合)が混ざっている」は、グラフを読めば設計時に指摘できる。実行して8秒待って初めて気づく話じゃない。
StockPilot:compute-over-context とコスト爆発
Anthropic のライブ・ヒルクライミング。~400行の system prompt と inline tool を抱えたエージェントを、15行 + 5 skills に割り直す過程を見せていた。元の構成だと daily sweep が7分でfail(答えは合ってるがアラートが遅すぎる=「遅さは不正解」)、1 run ≈ $8.39、特定タスクで全体コストの約7割を食っていた。原因は「全部読んで1行ずつ処理する」癖。直すと該当タスクのトークンが 30万 → 7,588(97%減)、フル12タスクの eval も改善した(登壇スライドに 71%→92% と 9/12→12/12 の2系統の数字が出てたので、ここは幅で受け取ってる)。
注意したいのは、彼らはこれを evalループ(実行して計測)で見つけたってこと。実行時の機構として完全に正しい。そのうえで僕の賭けは、この種のコスト爆発の少なくとも一部は実行する前にグラフの形で出せる、ということだ。ループの中に重いノードが入っていないか、あるパスがフルコンテキストを何度も送る形になっていないか。形で決まる部分は、回す前に分かる。
Federation:「システムは失敗してない、入力が失敗した」
2人チームで HSコード10桁分類のコンペ1位を取った話。教訓として置いていたのが "self-aware system > confident system"。顧客の請求書記述は本人にしか分からない略記で書かれていて、自信満々に間違ったコードを返すより、曖昧なら聞き返すか人間に flag する独立した入力品質ゲートを置け、と。HSコード誤分類1件で Ford が $365M 失った(Reuters報道, 2024-03-11)みたいな stakes も添えてあった。で、「外部入力が検証ノードを通らずに本処理へ直行する経路」はグラフを辿れば見えるので、これも静的に追える種類の話だ。
Bun → Rust:今日一番効いた一言
Bun(Zig実装、約53万行)を Claude Code で11日で Rust に書き換えた、という登壇。1人で 6,778 コミット、+100万行、テストはスキップも削除もゼロ。最後は旧 Bun が新 Bun を採点する差分テストで詰めていた。
正直に言っておくと、この11日は「言語非依存の巨大テストスイートと、旧実装を正解(oracle)にできる」という条件に支えられた数字で、社内システムにそのまま「うちも11日で」と当てる類のものじゃない。登壇者自身、緑のテストを信じきれずカバレッジを掘りに行った、と言っていた。そこが誠実で良かった。
で、一番刺さったのがこれ:
The fix is never "be more careful." It's a mechanism.
エージェントは採点対象を最適化しに来る(空コミットで進捗を演出する、ハックを正当化するコメントを書く、遅いだけのテストを"直す")。だから「気をつけます」では直らない。grep して reject、commit禁止、台帳で管理。機構で縛れ、と。
これ、静的解析が存在する理由そのものなんだよね。人間のレビュアーの注意力を品質の最後の砦にするな。機械に毎回チェックさせろ。Shingan を作ってる動機を、Bun のリライト登壇で言語化された気分だった。
結局、何だったか
今日のセッションが揃って言っていたのは「機構で潰せ」で、その機構の置き場所は evalループだったり、差分テストだったり、入力品質ゲートだったり、ほとんどは実行時だった。ランタイム観測も実行時検証も要る。そこは疑ってない。
僕の賭けは、そのうち 構造で決まる部分は実行前まで前倒しできる ということだ。
終了条件のないループも、決定的計算の混入も、検証ノードを通らない入力経路も、グラフの形の問題だから、回す前に読める。
実行時の機構が「起きた失敗を捕まえる」なら、静的解析は「起きようがない状態にする」。両方やればいい。コンパイラとテストの関係と同じだ。
それを「ワークフローのコンパイル時」に拾うのが Shingan でやりたいことで、今日は機構の側の土台が業界全体で固まりつつあるのを確認できた。前倒しの部分はまだ空いてる。急ぐ理由が増えた。