Skip to content

異常系・ログ・回復とデータ・入出力の形を 1 つの実行の核へ集め、異常系の課題 103 件を型ごとにまとめて解く #2335

Open
1 of 9 issues completed

Description

@takemi-ohama

依頼(原文)

異常系処理とログ出力、異常時の回復処理が統一されていないから起こる不具合・課題が多い。1 件ずつ対処してもきりがない

内部データ構造と出力のフォーマットの不統一の影響も大きそう

NDF は Skill の Script 化から発展してきたため、内部データ構造や入出力契約がほぼ整理されていない

出力だけでなく入力もそろえるべき

(書き手: 利用者、日付: 2026-11-01。この課題の「調べ方」の節が引用した利用者の指摘。課題のほかの節(具体例・調査・あるべき姿・進め方・構成・受け入れ条件の初版)は、起票者(課題を書いた AI)が 2026-11-01 に調査と組み直しの記録として書いた文で、利用者の指示の原文として扱わない)

解釈: 利用者が求めているのは、異常系の扱い・記録・回復と、内部データ・入力・出力の形を NDF 全体で 1 つにそろえ、同じ型の不具合を 1 件ずつではなく型ごとに無くすことである。「1 つの実行の核へ集める」「柱 9 本に分ける」は、その手段として起票者が選んだもので、下の「起票者の推定」に並べて承認ゲート 1 で確かめる。

起票者の推定

題・本文のうち、上の利用者の原文に無く、起票者が推して足した条件。承認ゲート 1 で利用者が確かめるまで、利用者の決定として扱わない。

# 条件 推した出所 この要求での扱い
S1 統一の手段は、すべてのステップを通す 1 つの「実行の核」である(部品を増やすのでなく、通らせる仕組みを作る) 本文「あるべき姿」「根本の原因」 受け入れ条件 1
S2 ステップの出口は ok / gate / stopped の 3 つだけ。例外・中断・時間切れ・判定できないは stopped 本文「あるべき姿」の結果の契約 受け入れ条件 2
S3 記録は実行の識別子つきの JSON Lines 1 つの形 本文「あるべき姿」の記録 受け入れ条件 3
S4 持ち主と並行の排他は同じ lease で取る 本文「あるべき姿」の持ち主と後片付け 受け入れ条件 4・4a
S5 上限と再試行の値は 1 つの宣言に置き、timeout=<数字> の直書きを残さない 本文「あるべき姿」の再試行と上限・受け入れ条件 5 の初版 受け入れ条件 5(数え方を前提 3 で決め直した)
S6 プランは値でなく参照を持ち、実行の直前に解決する 本文「あるべき姿」の入力の解決 受け入れ条件 6
S7 再開の手段は耐久の記録(durable)1 つにする 本文「あるべき姿」の末尾 受け入れ条件 7
S8 A〜F の課題 103 件は 1 件ずつ直さず、柱を入れた後に再現の手順を通して閉じるか残すかに分ける 本文「進め方」4 受け入れ条件 8・境界の「行わない」
S9 効果は「後の 1 か月の新しい課題に占める A〜F の割合が 82% より下がる」で測る 本文の受け入れ条件 9 の初版 受け入れ条件 9(起点と数え方を足した)
S10 データの型は版を持ち、結果は status、時刻は UTC の ISO 8601 の at にそろえる 本文「あるべき姿」のデータの契約 受け入れ条件 10
S11 機械が読む出力は 1 行 JSON だけ。本文の値は <!-- ndf:... --> の機械用のブロックから読む 本文「あるべき姿」の出力の形 受け入れ条件 11
S12 引数の名前は --root / --out / --timeout などにそろえ、値の解決は「引数 → 環境変数 → 設定 → 既定値」の順 本文「あるべき姿」の入力の契約 受け入れ条件 12
S13 hook は核の対象から外す 本文の受け入れ条件 1 の初版 対象範囲の「含まない」
S14 柱 9 本(#2336〜#2344)・マイルストーン 28〜30・ループ lroot で進め、m26-3 が本番へ出た後に始める 本文「構成と進め方(2026-11-01 の組み直し)」(書き手を確かめられない。利用者の指示を受けた組み直しの可能性がある) 前提 1・2
S15 この課題の設計が終わるまで、A〜F の新しい課題は個別に直さずこの課題の子として起票する(即時修正の 4 条件と本番の停止は除く) 本文「進め方」の末尾 前提 4
S16 入口 91 本・受け渡しのファイル 114 種類・設定 15 種類・環境変数 56 種類などの数 本文「何を見つけたか」(2026-11-01 の grep) 前提 3(棚卸し P0 が確定する)

目的

  • NDF の手順のスクリプトが、成功・失敗の判定・記録・後片付け・再試行と上限・入力の解決と、受け渡すデータの名前と形を、それぞれ自分で書かずに済む状態にする。新しいステップを足しても、A〜F(失敗の判定・記録・回復・待ちと上限・解決の時点・並行の衝突)の型の不具合が同じ仕組みで防がれる
  • 達成したい状態: 入口とデータの契約が 1 か所の宣言にあり、宣言と実行の核を通らないコードを検査が落とす。既存の A〜F の課題は、型ごとの仕組みを入れた後にまとめて確かめて閉じる

具体例

スプリント m26-2(2026-10-31〜11-01)は、本番へ出すまでに conductor が手で 9 回止めて直した。どれも別々の課題として起票され、別々に直される。

起きたこと 起票 原因の型
cross-review が final: error で終わったのに drive が exit 0 を返し、承認・マージへ進んだ #2143 A 失敗の判定
打ち切った cross-refactoring の持ち主の記録に終了時刻が書かれず、24 時間「使用中」として次の実行を断った #2321 C 回復・再開
強制終了したキューを再開すると、--from が無視された #2325 C 回復・再開
最終ゲートが締め切りを理由に、develop の取り込みまで巻き戻した #2323 D 待ち・上限
全体テストの CI 待ちが、PR で外した必須チェックをメインディレクトリの宣言から読み、待ち続けた #2317 E 解決の時点
版を作った時点に決めたため、並行するループが先に出して衝突した(3 回目) #2164 E 解決の時点

1 件ずつ直しても、同じ型の新しい事例が別の部品から出る。

何を見つけたか(2026-11-01 の調査)

課題の実物: 未完了 194 件の原因の型

未完了の課題 194 件を、本文から原因の型に分けた(分類の記録は下の「調べ方」)。

型 件数 何が起きるか
A 失敗の判定 39 失敗・未収束・判定できないを ok で返す。成功を失敗と読む。報告の自由文と結果の status が切り離されている
B 記録・ログ 14 何が起きたか後から追えない。記録が漏れる・数え違う。止まった理由が途中で切れる
C 回復・再開 18 中断・強制終了の後に再開できない。持ち主・ロック・worktree・状態ファイルの残骸が次を止める
D 待ち・上限 13 上限の値と待ちの組み合わせで止まる・巻き戻す。同じ失敗の打ち直しが上限まで回る
E 解決の時点 14 プランを作る時点の値(版・PR・宣言・予算・触るファイル)が実行の時点で古い
F 並行の衝突 5 並行するスプリント・ループ・プランが同じものを触る
G 機能・方針 / H 文書 / I その他 91 不具合でないもの・文書の直し・個別の事情
  • A〜F(異常系・回復系)は 103 件。 新機能と文書(G・H)を除いた不具合 125 件の 82% に当たる。従の型まで含めると 127 件が関わる
  • マイルストーンごとの A〜F: 24 後回しの軽微な課題 42/80、26 自律実行の信頼性 23/28、03 研究基盤 12/20、23 汎用性の回復 9/23
  • 10 月以降、起票 289 件・完了 271 件。直す速さと同じ速さで新しい課題が出ている

コードの実物: 共通部品はあるが、通らずに済む

#1142 の L0 で共通部品を作った。足りないのは部品ではなく、部品を必ず通らせる仕組みである。

観点 共通部品 実物(plugins/ndf/scripts・skills/*/scripts、テストと実験版を除く)
結果の形と終了コード lib/step_result.py scripts の入口 37 本のうち 24 本が使う(hook を除き 10 本が外れる)。skills の Python 121 本のうち使うのは 11 本。全部の入口に形を強制するテストは無い
ログ 無い logging は 1 ファイル。print(..., file=sys.stderr) 52 ファイル、sys.stderr.write 5、独自の log()/info() 4 つ。実行を通して追う識別子が無い
失敗の扱い lib/proc.py(StepError) except Exception 48 か所、except の後が pass だけの箇所 49 か所。sys.exit の直書き 44 か所、raise SystemExit 11 か所
再試行 lib/waits.py(tenacity) 使う側 33 ファイル。ほかに再試行の関数が 8 個ある(design_stage._retrying・_gh_retrying・_git_retrying、paths._add_worktree_with_retry、post_queue.retry、cross-review の _retry_unmatched_scope、cross-refactoring の push_with_retry_marker など)
上限 無い timeout=<数字> の直書き 195 か所
持ち主と寿命 lib/run_state.py・lib/run_owner.py(#2229) 使う側 7 と 6。異常終了の後の後片付け(終了時刻を書く)は呼ぶ側の責任で、打ち切りの経路では書かれない(#2321)
再開 lib/durable.py(DBOS) 使う側 25。再開の入口ごとに --from や状態ファイルの扱いが違う(#2325・#1592・#1635)
シェル lib/step-result.sh 29 本のうち set -euo pipefail は 4 本

コードの実物: 内部データと出力の形がそろっていない

同じ意味の値が、ファイルごとに別の名前と別の形で書かれ、読まれる。読む側が名前と形を推して読み違えると、A(失敗の判定)か E(解決の時点)の不具合として現れる。

観点 実物
同じ意味の別名 結果を表すキーが status(57 ファイル)・result(31)・verdict(22)・decision(15)・final(13)・exit(13)・outcome(8)・code・exit_code の 9 通り。PR は pr・prs・Pull Request・pull_request、課題は issue・issues・課題・number、理由は reason・text・summary・message・why
日本語と英語のキーの混在 文字列で読むキーは 1,612 種類。うち日本語 82 種類(課題・フェーズ・実行の条件・取り込み先・触るファイル など。プランの JSON は日本語、状態と結果は英語)
型の定義 dict を文字列のキーで読む箇所が 4,500。型を持つのは dataclass 65 ファイル・NamedTuple 34・pydantic 7 で、スキーマの検査(jsonschema)は 0。状態ファイルの名前は 114 種類、版を持つのは 44 ファイル
時刻 isoformat 27 ファイル・strftime 17・time.time() 29。キーも at・started_at・ts・time・timestamp・created_at に分かれる
出力の形 1 行 JSON(step_result)、Markdown の報告(## フェーズの報告・## 作業の報告)、シェルで eval する KEY=VALUE(30 ファイル)、YAML(5)。conductor は Markdown の報告を読んで次の手を決める
文書を機械が読む 課題・PR の本文の見出し(## 受け入れ条件・## 配布の記録・## 設計の結果 など)を読むファイルが 14、本文から正規表現で値を拾うファイルが 43、Markdown の表を割って読むファイルが 24。人が書く形の揺れがそのまま判定の誤りになる

形が原因に関わる課題の例(分類の根拠の文から拾った 25 件の一部):

コードの実物: 入力の渡し方もそろっていない

同じものを受け取るのに、入口ごとに名前・渡し方・置き場所が違う。呼ぶ側(Skill の本文・プランの雛形・conductor)が入口ごとの違いを覚えて書くため、渡し忘れと渡し違いが起きる。

観点 実物
引数の読み方 argparse 59 ファイル、sys.argv を手で読む 16 ファイル。オプションは 311 種類、位置引数は 78
同じものの別の名前 リポジトリの根が --root(26 ファイル)・--repo(17)・--worktree(3)・--dir(2)・--cwd(1)。出力先が --out・--out-dir・--output・--json・--report。上限が --timeout・--budget・--budget-minutes・--limit・--max。版が --version・--to・--dev・--prod。PR は --pr のほかに位置引数で受ける入口がある
環境変数 読む環境変数が 56 種類(NDF_*・CROSS_REVIEW_TMP_DIR・CROSS_REFACTORING_TMP_DIR など)。どの入口がどれを読むかの一覧が無い
設定ファイル .ndf/ の設定と記録が 15 種類(project.json・worktree.json・supervise.json・release.json・pace.json・ask.json・review.json・runtimes.json など)。同じ値を 2 か所が持つ(本番のブランチが worktree.json の production_branch と project.json の delivery、#2097)。どのファイルをメインディレクトリから読み、どれを worktree から読むかが入口ごとに違う(#2317・#1709)
標準入力 標準入力から読むファイルが 28
値の優先順位 引数・環境変数・設定ファイル・既定値のどれが勝つかが入口ごとに違う

入力が原因に関わる課題の例:

成り立ち: NDF は Skill の手順をスクリプトへ移すことで育った。スクリプトは Skill の 1 つの手順を自動にするために書かれ、そのたびに入力・出力・状態の形をその場で決めた。入口は scripts 直下に 52 本、Skill の scripts 直下に 39 本あり、Skill の本文から呼ばれるスクリプトの行は 60 種類ある。どの入口についても、受け取るもの・返すもの・書く状態を 1 か所に宣言した契約は無い(契約は Skill の本文・README・コードの docstring に散っている)。その後スクリプト同士が互いの出力と状態を読むようになり、決めていなかった契約が、読み違いの不具合として現れている。

根本の原因: 1 つのステップが「成功・失敗の判定」「記録」「後片付け」「再試行と上限」「入力の解決」をそれぞれ自分で書き、受け渡すデータの名前と形もそれぞれが決める。部品は呼べば使えるが、呼ばなくても書けて、検査も落とさない。そのため新しいステップのたびに同じ型の不具合が生まれる。

あるべき姿

すべての手順のステップは 1 つの実行の核の中で動く。 核が次の 8 つを引き受け、ステップは仕事の中身だけを書く。

核が持つもの 今の型 決まり
結果の契約 A ステップの出口は ok / gate / stopped の 3 つだけ。例外・中断・時間切れ・判定できないは核が stopped と理由に変える。ok は欠けが無いときだけ。worker の報告も同じ形で返し、自由文から完了を読まない
記録 B 核が実行の識別子を振り、開始・終了・判断・失敗を 1 つの形の JSON Lines へ書く。標準エラーへの診断も同じ関数を通る。止まった理由は切らずに残す
持ち主と後片付け C・F 核が実行の持ち主(lease)を取り、どの終わり方でも(正常・例外・シグナル・時間切れ)終了時刻と片付けを書く。並行の排他も同じ lease で取る
再試行と上限 D 上限と再試行の方針を 1 つの表(宣言)に置き、核が当てる。同じ原因の失敗を繰り返さない(打ち直しの回数と、原因が同じかを核が見る)
入力の契約 E・A 入口が受け取るもの(引数・環境変数・設定・標準入力)を契約の一覧に宣言し、同じものには同じ名前を使う(リポジトリの根は --root、出力先は --out、上限は --timeout など)。値は 1 つの読み込み部品が「引数 → 環境変数 → 設定 → 既定値」の決まった順で解決し、どこから来た値かを記録へ書く。設定は 1 つの値を 1 か所だけが持つ
データの契約 A・E 実行・プラン・状態・記録・結果の種類ごとに型(スキーマと版)を 1 つ持ち、同じ意味には 1 つの名前を使う(結果は status、時刻は UTC の ISO 8601 の at など)。核が読み書きの両方で型を確かめ、合わなければ stopped にする
出力の形 A・B 機械が読む出力は 1 行 JSON の 1 形だけにする。Markdown は人へ見せる描画で、機械はそれを読まない(フェーズレポートも JSON を正にして Markdown を生成する)。課題・PR の本文から値を読む箇所は、本文に埋めた機械用のブロック(例 <!-- ndf:... --> の JSON)か、記録の JSON から読む
入力の解決 E プランは値でなく参照(版・PR・宣言・予算)を持ち、核がステップの直前に解決して記録する。解決できなければ流さずに止まる

再開は耐久の記録(durable)を唯一の手段にする。 --from と状態ファイルの手当ては核の上の 1 つの入口へ寄せる。

進め方

1 件ずつ直すのをやめ、型ごとにまとめて直す。

  1. 契約の棚卸しと定義(最初に行う): 入口 91 本と、入口の間で受け渡す状態・記録・結果のファイル(名前 114 種類)を一覧にし、それぞれの入力(引数・環境変数・設定・標準入力)・出力・書く状態・終了コードを 1 か所の宣言(契約の一覧)へ書き出す。.ndf/ の設定 15 種類と環境変数 56 種類も、どの入口が読むかを書く。今の振る舞いを写すだけで、変えない。ドメインの語(スプリント・プラン・ステップ・実行・課題・PR・リリース・記録)ごとに型を決め、同じ意味の別名を対応表にまとめる。以後の手順はこの一覧を正として進める
  2. 止血(先に入れる): 新しく足すステップと入口が核を通らなければ、構造の検査(scripts/check-script-structure.py)が落とす。今の外れは許可の一覧に載せ、一覧は減る方向にしか変えられない(ratchet)
  3. 核とデータの型を作る: 既存の step_result・proc・jsonio・locks・waits・durable・run_state を束ねる。新しく書くのは束ね方・記録と上限の表・データの型の定義(名前の対応表を含む)だけにする。データの型は版を持ち、古い形の読み込みは型の側の移行関数 1 か所で受ける
  4. 移す: 課題の集まる部品から順に核へ移す。分類で集まった部品は次のとおり
    • supervise のプラン・judge・キュー(supervise_lib)
    • cross-refactoring(refactor.py・持ち主・着手前テスト・最終ゲート)
    • 課題を閉じる処理(sprint-close.py・sprint_close_lib)
    • リリース(release-steps.py・release_templates.py)
    • 外部 CLI の監視と結果の受け渡し(lib/monitor_*・external-ai)
    • 使用量の帳簿(lib/usage_*)
  5. 既存の課題をまとめて閉じる: 移した部品に属する A〜F の課題は、移した後に再現の手順を 1 回ずつ通し、再現しないものを理由を添えて閉じる。1 件ずつの修正の Pull Request は作らない

この課題の設計が終わるまで、A〜F の型の新しい課題は個別に直さず、この課題の子として起票する。 例外は即時修正の 4 条件を満たすものと、本番を止めているもの。

構成と進め方(2026-11-01 の組み直し)

柱

この課題の子は柱 9 本で、未完了の課題 113 件を柱の子へ付け直した(既存の親 #2227・#2228・#2229・#2230・#715・#1142 は子ごと柱の下へ入れた)。振り分けは、課題の本文を 2 本の worker で読み、「この大方針の核・契約・型がそろえば解消する可能性があるか」で決めた。

柱 課題 マイルストーン 子の数
P0 #2336 契約の棚卸し 28 17
P1 #2337 結果の契約 29 23
P2 #2338 記録とログ 29 8
P6 #2339 データの契約 29 4
P7 #2340 出力の形 29 6
P8 #2341 入力の契約 29 14
P3 #2342 持ち主・後片付け・並行 30 19
P4 #2343 再試行と上限 30 12
P5 #2344 実行時点の解決 30 9

マイルストーン

マイルストーン 中身
28 根本解決 1: 設計と契約の棚卸し この課題の設計と、柱 P0
29 根本解決 2: 結果・記録・データ・入出力の契約 実行の核と、柱 P1・P2・P6・P7・P8
30 根本解決 3: 回復・上限・実行時点の解決 柱 P3・P4・P5

進行中のスプリント(m26-1〜m26-3)の課題は、閉じる処理が終わるまでマイルストーン 26 に残し、親だけ柱へ付け直した。

2 大方針の並列

当面の開発は「25 Neural Development Factory」とこの根本解決の 2 本を並列で進める。どちらも開発ループで回す。

Factory 根本解決
ループ 既存の l20261031(選定の対象を 25 へ戻す) 新しい lroot(選定の対象は 28 → 29 → 30)
主に触る場所 factory/ plugins/ndf/
選ぶもの 25 の課題 柱と、この課題(子の課題は選定から外し、柱の仕組みを入れた後にまとめて確かめて閉じる)

始める時期: m26-3(#2164 版数を流す時点に決める、を含む)が本番へ出た後。2 本のループが同時に版を出しても衝突しないようにするためである。

2 大方針の外の課題は、31 費用の削減とテストの軽量化・32 Skill の機能と手順・33 文書と用語・34 研究基盤・汎用性・利用者を増やす、の 4 つへまとめた。当面は 2 大方針の後に扱う。

前提

  • 前提 1: この課題(異常系・ログ・回復とデータ・入出力の形を 1 つの実行の核へ集め、異常系の課題 103 件を型ごとにまとめて解く #2335)の工程で作るのは、実行の核と契約の形を決める設計文書である。実装は柱 9 本(入口・受け渡しのファイル・設定・環境変数の契約を 1 か所に宣言し、宣言の外を検査で落とす #2336〜プランは値でなく参照を持ち、実行の直前に解決して照合する #2344)が、それぞれの設計とマイルストーン(28〜30)で行う。この課題は、柱がすべて閉じ、受け入れ条件 8・9 を確かめた時点で閉じる
  • 前提 2: 柱の実装は m26-3 が本番へ出た後に、ループ lroot で始める。始める前に plugins/ndf/ の同じファイルを触る変更が入っても、柱の設計は契約の一覧(P0)を正として読み直す
  • 前提 3: 本文の数(入口 91 本・ファイル 114 種類・timeout=<数字> 195 か所など)は 2026-11-01 の調査の値で、受け入れ条件の判定には使わない。判定は「契約の一覧にある全部」「検査が落とすか」で行う。2026-11-01 に打ち直した grep -rnE "timeout=[0-9]"(テストと experimental/ を除く)は 17 か所、timeout= 全体は 78 か所で、本文の 195 と一致しない。受け入れ条件 5 は数でなく検査で判定する形にした
  • 前提 4: この課題の設計が承認されるまで、A〜F の型の新しい課題は個別に直さず、柱の子として起票する。例外は即時修正の 4 条件を満たすものと、本番を止めているもの
  • 前提 5: 核が使う外部ライブラリは、既にある optional extras(schema の pydantic・waits の tenacity・durable の dbos と filelock)の範囲に置く。新しい依存を足すときは設計で比べて承認ゲート 1 で示す
  • 前提 6: SIGKILL・電源断のように後片付けのコードが動けない終わり方は、終わった実行では書けない。この場合は、次の実行が持ち主の記録(pid と開始時刻)から持ち主の死を判定して片付ける(受け入れ条件 4a)

対象範囲

含む:

  • plugins/ndf/scripts と plugins/ndf/skills/*/scripts の入口・ステップ(Python とシェル。テストと experimental/ を除く)
  • 入口の間で受け渡す状態・記録・結果のファイル、プランの JSON、.ndf/ の設定、入口が読む環境変数
  • 入口を呼ぶ側の書き方(Skill の本文・プランの雛形・conductor・supervisor・worker の報告の受け取り)
  • 既存の A〜F の課題 103 件の閉じる・残すの仕分け

含まない:

  • hook(plugins/ndf/hooks/ と hook から呼ぶ *-guard.sh)を核へ移すこと。hook は短い時間の上限の中で動き、核の記録と lease を持たせない
  • factory/(Neural Development Factory)と、ほかのプラグイン(playwright-kit・mcp-*)
  • G 機能・方針 / H 文書 / I その他の課題(91 件)
  • 子の課題を 1 件ずつ直す Pull Request(即時修正の 4 条件を満たすものと本番を止めているものは除く)
  • 実験版(plugins/ndf/scripts/experimental/)。実験版を本体へ移すときは、移す変更が核を通す

ドメインイベント

1 回の入口の実行で起きることを時間の順に並べる。設計のドメインモデルの節は同じ番号を引き継ぐ。

# イベント 引き金 失敗したとき 順序の前提
E1 入口が起動された Skill の本文・プラン・conductor・利用者のコマンド 契約の一覧に無い引数・環境変数を受けた → stopped(理由に名前) —
E2 入力を解決し、値ごとの出所(引数・環境変数・設定・既定値)を決めた E1 必須の値が無い・1 つの値を 2 か所の設定が持つ → stopped E1
E3 実行の識別子を振り、記録へ開始を書いた E2 記録の置き場へ書けない → 標準エラーへ理由を出して stopped E2(記録の置き場はリポジトリの根から決まる)
E4 持ち主(lease)を取った E3 他の生きた実行が持つ → 待つか stopped を結果に出す。死んだ実行の残骸 → 片付けてから取る E3
E5 プランの参照(版・記録の PR・宣言・予算)を解決して記録した E4 解決できない・承認ゲートで見た値と食い違う → stopped か gate E4(解決は lease の中で行い、並行する実行と同じ値を取り合わない)
E6 読んだデータの型と版を確かめた E5 古い版 → 型の側の移行関数で読み替える。読み替えられない → stopped E5
E7 ステップの中身を実行した E6 例外・SIGTERM・時間切れ → stopped と理由 E6
E8 失敗したステップを再試行した E7 の一時的な失敗 同じ原因の失敗が宣言の回数に達した → stopped E7
E9 書くデータの型と版を確かめた E7 合わない → 書かずに stopped E7
E10 結果を ok / gate / stopped と決まった終了コードで返した E9 か、E1〜E8 のどこかの失敗 — (欠けがあれば ok にしない) E1
E11 記録へ終了を書き、持ち主の記録に終了時刻を書いて lease を返した E10 SIGKILL などで書けない → 次の実行の E4 が片付ける(前提 6) E10
E12 人へ見せる Markdown を結果の JSON から描いた E10 描けない → 結果の JSON はそのまま返る(判定に使わない) E10
E13 中断した実行を耐久の記録から再開した 利用者・conductor の再開の指示 耐久の記録が無い・壊れている → stopped。--from の指定がある → 指定のステップから始める E3 の識別子と耐久の記録がある

洗い出しで移したもの: E3 の失敗(記録が書けない)は記録より前に起きるため、受け入れ条件 3a に標準エラーへの出し方を置いた。E11 の SIGKILL は前提 6 と受け入れ条件 4a に置いた。E2 と E4 の順(lease の置き場が入力の解決に依る)は設計で確かめる(未決)。

用語

用語 意味
実行の核 入口・ステップが必ず通る共通の実行の仕組み。入力の解決・記録・lease・型の確認・再試行と上限・結果の変換を引き受け、ステップは仕事の中身だけを書く
契約の一覧 入口ごとの入力(引数・環境変数・設定・標準入力)・出力・書く状態・終了コードと、受け渡すファイルの形を 1 か所に書いた宣言
原因の型 課題を原因で分けた A〜I の区分。A 失敗の判定・B 記録とログ・C 回復と再開・D 待ちと上限・E 解決の時点・F 並行の衝突・G 機能と方針・H 文書・I その他
柱 この課題の子で、原因の型ごとの仕組みを入れる課題(P0〜P8、#2336〜#2344)
許可の一覧 核と契約を通らない今の外れを並べた一覧。構造チェックが読み、一覧を増やす変更を落とす
機械用のブロック 課題・PR の本文に埋めた、機械が値を読むための <!-- ndf:... --> の JSON
実行の識別子 1 回の入口の実行に核が振る識別子。記録・持ち主の記録・耐久の記録をつなぐ

受け入れ条件

番号は初版(2026-11-01、起票者)を引き継ぐ。初版から書き換えた条件は、条件の下に書き換えた点を残す。どの柱が満たすかを括弧に書く。

この課題の設計(マイルストーン 28)で満たすもの

  • D1. 設計文書が「決めること(設計で)」の 5 項目それぞれに、選んだ案・比べた案・理由を持つ
  • D2. 設計文書が、実行の核の入口の形(Python とシェルのそれぞれ)と、ステップが核へ渡すもの・核から受けるものを、型の名前とフィールドで書く
  • D3. 設計文書が、契約の一覧のファイルの置き場と形(入口・ファイル・設定・環境変数の各行に何を書くか)を決め、柱 P0 がそのまま書き始められる
  • D4. 設計文書が、受け入れ条件 0〜12 のそれぞれを満たす柱を 1 つ以上指し、柱の間の順序(どの柱がどの柱の仕組みを前提にするか)を書く
  • D5. 設計文書が、引数名・出力の形・状態ファイルの形を変えるときの互換の扱い(旧い名前と旧い形をいつまで受けるか)を決める

大方針として満たすもの(柱がそろって満たす)

  • 0. 入口と受け渡しのファイルすべての契約(入力・出力・書く状態・終了コード)が契約の一覧にあり、一覧に無い入口・ファイル・キーを検査が落とす(P0)
    • 書き換えた点: 「入口 91 本」を「入口すべて」にした(数は前提 3)
  • 1. hook を除くすべての入口とステップ(plugins/ndf/scripts と plugins/ndf/skills/*/scripts)が実行の核を通り、通らないものを構造チェックが落とす。許可の一覧は空になる(P0 が検査と許可の一覧を入れ、P1〜P8 が空にする)
  • 1a. 許可の一覧に行を足す変更を、構造チェックが落とす(P0)
    • 書き換えた点: 本文「進め方」1 の ratchet を条件に足した
  • 2. 正常・例外・SIGTERM・時間切れのどの終わり方でも、結果は ok / gate / stopped のどれかと、それぞれに決まった終了コードで返る(P1)
  • 2a. 欠けか例外があるのに ok を返す経路が無いことを、契約の一覧のすべての入口を回すテストが確かめる(P1)
    • 書き換えた点: 初版の 2 を、終わり方の条件と経路の条件の 2 つに分けた
  • 2b. worker と外部 CLI の報告は構造化した結果で受け取り、自由文から完了を判定する箇所が残らない(P1・P7)
  • 3. 1 回の実行の開始から終わりまでを、実行の識別子 1 つで、1 つの記録(JSON Lines)から追える。止まった理由が切れずに残る(P2)
  • 3a. 記録の置き場へ書けないとき、核は標準エラーへ理由を出して stopped を返す(P2)
  • 4. 正常・例外・SIGTERM・時間切れのどの終わり方でも、持ち主の記録に終了時刻が書かれ、次の実行が残骸で止まらない。cross-refactoring の init が着手前テストの打ち切りで止まると、持ち主の記録が残り次の実行を 24 時間断る #2321 の再現の手順で確かめる(P3)
  • 4a. SIGKILL で終わった実行の残骸は、次の実行が持ち主の死を判定して片付け、止まらない(P3)
    • 書き換えた点: 前提 6 の終わり方を足した
  • 5. 上限(時間・予算・待ち)と再試行の回数は 1 つの宣言から読まれ、テストを除くコードの timeout=・上限と再試行の定数に数字のリテラルがあると検査が落とす(P4)
    • 書き換えた点: 初版の「timeout=<数字> の直書きが残らない」は定数(例 DEFAULT_TIMEOUT = 1800)を通すため、定数も対象にし、検査で判定する形にした
  • 5a. 同じ原因の失敗の打ち直しは宣言の回数で止まり、理由が結果に出る(P4)
  • 6. プランは版・記録の PR・宣言の値を参照で持ち、核が実行の直前に解決する。解決できなければ流さずに止まる。スプリントのプランが作った時点の版数を配布と本番のプランへ書き込み、並行する別のスプリントがその版を先に出すとプランを作り直すまで止まる #2164・リリース記録の PR を、まとめのプランを組む時点でなく実行の時点で解決する #2230 の実装をこの形へ寄せる(P5)
  • 7. 再開の手段は耐久の記録 1 つで、強制終了の後の再開で --from が効く。queue --resume の --from が、強制終了した前の起動が残っていると無視され、前の途中のステップから続く #2325 の再現の手順で確かめる(P3)
  • 8. A〜F の課題 103 件それぞれを、閉じた(再現の手順で解消を確かめた)・柱の子として残した(核の外の原因)のどちらかに分け、理由をこの課題の本文に記録する(各柱)
  • 9. すべての柱が本番へ出た日から 30 日の間に起票された課題のうち、不具合(G・H を除く)に占める A〜F の割合が 82%(2026-11-01 の値)より下がる。分類は「調べ方」と同じ手順(本文の先頭 2500 字から主の型を 1 つ)で行い、分類の記録を残す(この課題)
    • 書き換えた点: 「後の 1 か月」の起点と、割合の分母を足した
  • 10. 実行・プラン・状態・記録・結果の種類ごとに型の定義(スキーマと版)が 1 つあり、核が読み書きの両方で確かめる。合わなければ stopped を返す(P6)
  • 10a. 同じ意味の値は 1 つの名前で、名前の対応表に無い別名が残らないことを検査が確かめる(P6)
    • 書き換えた点: 初版の 10 を型の条件と名前の条件の 2 つに分けた
  • 10b. 前の版の NDF が書いた状態ファイル・記録を、新しい版の核が型の側の移行関数で読める(P6)
  • 11. 機械が次の手を決めるために読む出力は 1 行 JSON だけで、Markdown の報告・課題や PR の本文の自由な見出しと表から判定の値を読む箇所が残らない。人が書いた本文から読む値は機械用のブロックから読む(P7)
  • 12. 同じものを受け取る引数は全部の入口で同じ名前で、sys.argv を手で読む入口が無い(P8)
  • 12a. 引数・環境変数・設定・既定値の解決の順を 1 つの部品が決め、値ごとの出所が記録に残る(P8)
  • 12b. 1 つの値を持つ設定ファイルは 1 つだけで、契約の一覧に無い引数・環境変数・設定のキーを検査が落とす(P0・P8)
    • 書き換えた点: 初版の 12 を名前・解決の順・置き場の 3 つに分けた

起きてはいけないこと

  • 13. 記録(JSON Lines)・入力の出所の記録に、秘密(トークン・鍵・認証情報)の値が書かれない。秘密に当たる環境変数は出所の名前だけを書く(P2・P8)
  • 14. NDF が利用者の .ndf/ の設定を自動で書き換えない。設定の形を変えるときは、旧い形を移行関数で読み、書き換えは利用者へ案内する(P0・P6・P8)
  • 15. optional extras(pydantic・tenacity・dbos)が入っていない環境で、核は例外で落ちずに stopped と足りない extras の名前を返す(核)
  • 16. 既存の全体テストが、各柱のマージの前後で通る(各柱)

非機能の条件

大項目 条件
性能・拡張性 核を通すことによる 1 入口の起動の増分は、設計で python3 <入口> --help を変更の前後で 10 回ずつ測った中央値の差で示し、上限の値を設計文書に置く(未決)
運用・保守性 記録: 1 回の実行を実行の識別子で開始から終了まで追える(受け入れ条件 3)。秘密の値を残さない(受け入れ条件 13)。保存: 記録の置き場と寿命は設計で決める(決めること 3)
移行性 移す対象: 入口・状態ファイル・記録・プランの形。順序: 柱の順(P0 → P1・P2・P6・P7・P8 → P3・P4・P5)。並走: 移す途中は許可の一覧に載った入口が旧い形のまま動き、核を通る入口と同じ版に並ぶ。旧い版が書いた状態ファイルは移行関数で読む(受け入れ条件 10b)。切り戻し: 柱ごとの Pull Request を revert すれば前の形へ戻る
セキュリティ 秘密: 受け入れ条件 13。認証・認可(C2)と秘密の扱い(C1)は変えない。秘密を扱う lib/auth.py・lib/git-credential.sh を核へ移す変更は、承認ゲートで人が見る
システム環境 依存: 前提 5。Claude Code と Codex の両方の配布で同じ核が動く。シェルの入口は bash で動く

影響

対象 影響
公開インタフェース 変わる。入口の引数名・結果の JSON・終了コード・Skill の本文が書くコマンドの形が変わる。互換の扱いは設計が決める(受け入れ条件 D5)。旧い名前を受ける期間を持たない入口は 互換なし を付ける
データ 変わる。状態ファイル・記録・プランの JSON に型と版が付く。旧い形は移行関数で読む(受け入れ条件 10b)。利用者の .ndf/ は自動で書き換えない(受け入れ条件 14)
既存の振る舞い 変わる。今 ok を返している欠けのある結果が stopped になり、ループと承認ゲートで止まる回数が一時的に増えうる

検証手段

項目 手段
テスト uv run --frozen --project . --all-extras pytest . -q -n 4(全体テスト)。核の終わり方(正常・例外・SIGTERM・SIGKILL・時間切れ)ごとのテストと、契約の一覧の全入口を回すテスト
静的解析・構造の検査 python3 scripts/check-script-structure.py(CI のジョブ script-structure)。契約の一覧・許可の一覧・上限のリテラル・名前の対応表の検査はここへ足す
配布物の検証 claude plugin validate .
再現の手順 #2321(打ち切った持ち主の終了時刻)・#2325(強制終了の後の --from)・#2164・#2230 の再現の手順を、該当する柱のマージの前に通す
手動確認 受け入れ条件 9: すべての柱が本番へ出た日から 30 日後に、その間に起票された課題を「調べ方」と同じ手順で分類し、割合を本文に書く

前提とする取り決め

項目 参照先 / 決めたこと
プロジェクト構造 核は既存の部品と同じ plugins/ndf/scripts/lib/ に置く。既定で働くため実験版(experimental/)に置かない。factory/ を参照しない(AGENTS.md の「マーケットプレイスの構造」)
コーディング規約 AGENTS.md の「最小限のコード実装」・外部コマンドを書く前に実行して確かめる決まり。Skill の frontmatter は python3 scripts/check-skill-frontmatter.py
テスト戦略 核は単体テストで終わり方ごとの結果を担保し、入口は契約の一覧から作る契約のテストで担保する。子の課題は再現の手順で確かめる。.md の文言を照合するテストは書かない(AGENTS.md の DON'T)

境界

区分 内容
常に行う 契約の一覧を正として変更する。柱ごとに全体テストと構造チェックを通す。核を通した入口を許可の一覧から消す
確認してから行う(承認ゲート 1・2 で人が見る) 新しい依存の追加。互換なし になる入口の変更。.ndf/ の設定の形の変更(C7)。秘密を扱う部品を核へ移す変更(C1)
行わない 子の課題を 1 件ずつ直す Pull Request(前提 4 の例外を除く)。hook を核へ移す。利用者の .ndf/ を NDF が自動で書き換える。許可の一覧に行を足す

対象の課題(主の原因の型ごと)

既存の親とのつながり: #2227(結果の統一 JSON。A の親)・#2229(状態の寿命と持ち主。C の親)・#2230(記録の PR を実行の時点で解決。E)・#2334(スクリプトの結果・ログ・異常系の不統一)は、この課題の一部になる。m26-3 で実装中の #2230・#2164 は、受け入れ条件 6 の最初の事例として扱う。

決めること(設計で)

  • 実行の核の形: 装飾子か、コンテキストマネージャか、supervise.py run のような実行器か。シェルの入口の扱い
  • データの型の書き方: dataclass・pydantic・JSON Schema のどれに寄せるか。プランの JSON の日本語のキーを残すか(人が読む・書く)
  • 記録の置き場所と寿命: 実行ごとか、リポジトリで 1 つか。progress.jsonl・使用量の帳簿・ask.jsonl との関係
  • 移す順序と 1 スプリントの大きさ: 部品ごとか、型ごとか
  • 既存のマイルストーン(24・26・03・23)に散る 103 件を、この課題のマイルストーンへ寄せるか

未決

「決めること(設計で)」の 5 項目に加えて、要求の整理で残ったもの。

項目 誰が決めるか 期限
核を通す 1 入口の起動の増分の上限値(非機能の性能) この課題の設計(実測して決める) 設計 Pull Request
入力の解決(E2)と lease(E4)の順。lease の置き場が入力の解決に依るときの扱い この課題の設計 設計 Pull Request
利用者が直接呼ぶ公開の入口の範囲(互換なし を付ける対象) 柱 P0(契約の一覧に印を付ける) マイルストーン 28
2026-11-01 の分類の記録の置き場。受け入れ条件 9 の基準値 82% を同じ手順で再現できる形で残す。本文は「分類の記録に残してある」とするが、リポジトリの issues/・docs/ には見当たらない(2026-11-01 に ls issues と grep -rl 2335 issues docs で確かめた) この課題の設計 設計 Pull Request
本文の timeout=<数字> 195 か所と打ち直した値(17・78)の食い違いの出所 柱 P4(棚卸し P0 の数え方で確かめる) マイルストーン 30 の設計

調べ方

進行

モード: — / 進め方: auto

  • 要求と受け入れ条件 — 2026-11-01 09:09
  • 作業場所の用意 — 2026-11-01 10:24
  • 設計 — 2026-11-01 09:14
  • 素材の収集と出典の確定
  • ドキュメント再構成
  • ドキュメントレビュー
  • 計画 — 2026-11-01 10:20
  • 実装 — 2026-11-01 10:24
  • 構造改善 — 2026-11-01 11:33
  • 実装レビュー — 2026-11-01 12:12
  • 完了判定 — 2026-11-01 15:15
  • Pull Request — 2026-11-01 11:32
  • 確定仕様化 — 2026-11-01 17:08
  • 後片付け — 2026-11-01 17:07
  • 配布 — 2026-11-01 15:27
  • 体裁レビュー
  • リリース後テスト — 2026-11-01 17:24
  • 振り返り — 2026-11-01 17:44
  • 棚卸し — 2026-11-01 17:49

人の判断待ち(開発ループ lroot)

  • 判定: unknown(承認ゲート 1)
  • 理由: 検査の一部が終わっていない。requirements-check が exit 2 で止まり、要求の写し(issues/issue-2335-requirements.md)を読めなかった。このファイルは変更の統計に含まれるため、要求と受け入れ条件と設計が対応しているかは確かめられていない。PR 本文もマージの前に再実行が要ると書いている。Value 2 は、承認の材料がそろってから承認を求めるとしている。Value 3 は、測らずに良いとは言わないとしている。この 2 つに照らし、材料が欠けたまま従うとは決められない/設計の中身は Mission・Vision に沿う。すべての入口を 1 つの実行の核と契約の一覧へ寄せる。核と契約の一覧を通らない入口は検査で落とす。これは Value 6(根本原因の場所で直す・同じ役割を分けない)と Value 4(判断の要らない手順はスクリプトに)に合う/プロジェクトごとに違う値は、入力の解決(引数 → 環境変数 → 設定 → 既定値)で受ける。核に ai-plugins の値を置かない。これは Value 5 に合う。柱ごとに Pull Request を 3 本までに抑え、150 ms の上限は実測から置く。これは Value 10・Value 3 に合う/今回の変更は文書と用語集だけである。必ず承認が要る操作には当たらない。秘密(C1)・認証(C2)・.ndf/(C7)・配布の規則(P1)は触らない。マイルストーンの付け替えは GitHub の中なので C8 に当たらない。lib/auth.py を核へ移す件(C1)は、後の柱 P8 の Pull Request で扱うと設計に書かれている
  • 根拠の項目: Mission, Vision, Value 2, Value 3, Value 4, Value 5, Value 6, Value 10, C1, C7, C8, P1
  • 設計 Pull Request: NDF のスクリプト: すべての入口が 1 つの実行の核と契約の一覧を通り、異常系の不具合を型ごとに防げるようにする(#2335) #2359
  • 答え: approved
  • 答え方: python3 /persistent/ai/.claude/plugins/cache/ai-plugins/ndf/10.27.6/scripts/sprint-state.py loop answer lroot 2335 --approved|--rejected

振り返り: #2367 (comment)

Activity

  1. changed the title [-]異常系・ログ・回復を 1 つの実行の核へ集め、異常系の課題 103 件を型ごとにまとめて解く[/-] [+]異常系・ログ・回復とデータ・出力の形を 1 つの実行の核へ集め、異常系の課題 103 件を型ごとにまとめて解く[/+] on Nov 1, 2026
  2. changed the title [-]異常系・ログ・回復とデータ・出力の形を 1 つの実行の核へ集め、異常系の課題 103 件を型ごとにまとめて解く[/-] [+]異常系・ログ・回復とデータ・入出力の形を 1 つの実行の核へ集め、異常系の課題 103 件を型ごとにまとめて解く[/+] on Nov 1, 2026
  3. added 6 commits that reference this issue on Nov 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions