Repository navigation
Description
親: #2168。前提: #2186(スプリントの実行とランナー)・#2189(手元のランナー)・#2172(一覧と詳細)・#2173(承認と通知)。
具体例
2026-11-01、このリポジトリでは開発ループを 3 本並列で回すと決めた(Factory の l20261031・根本解決の lroot・開発フローの見直しの lflow。どれも同じリポジトリの develop へ入る)。そのとき決めたことは次の 4 つである。
- ループごとに選定の対象のマイルストーンを分ける
- 同時に流すプランは 1 本のループにつき 2 本までにする(全体テスト 6 本の同時実行が実測の上限)
- 版の衝突を避けるため、版数を流す時点に決める スプリントのプランが作った時点の版数を配布と本番のプランへ書き込み、並行する別のスプリントがその版を先に出すとプランを作り直すまで止まる #2164 が入るまで始めない
- 触る場所の重なり(
plugins/ndf/を 2 本が触る)は取り込みの時点で解く
Factory で同じことをしようとすると、どれも置き場が無い。この課題は、それらをすべて Factory の仕様として定義する。
依頼(原文)
Neural Development Factory ではこのあたりの並行開発のオーケストレーションはどのような構想になっていますか?(または未定義ですか?)
(書き手: 利用者(起票者がこの課題の「由来」に引用)、日付: 2026-11-01)
決まっていないこと、は全て定義する必要があります
(書き手: 利用者(同上)、日付: 2026-11-01)
恐らく、factory の場合 relay.py に相当するオーケストレーターが低コストで常に動いているのではないでしょうか
(書き手: 利用者(同上)、日付: 2026-11-01)
上の 3 つ以外の題・本文(「具体例」「何が決まっていなかったか」の表、「定義」1〜7、元の受け入れ条件 1〜13)は起票者(課題を書いた AI、2026-11-01)の文で、利用者の指示の原文としては扱わない。「定義」の値は NDF の開発ループ(plugins/ndf/skills/development-workflow/references/loop.md)と 2026-11-01 の 3 本並列の運用に合わせて起票者が決めた(判断の記録は ask.jsonl)。起票者が足した条件は次の「起票者の推定」に並べ、承認ゲート 1 で利用者が見直す。
起票者の推定
承認ゲート 1 で利用者が確かめるまで、利用者の決定として扱わない。
| # | 条件 | 推した出所 | この要求での扱い |
|---|---|---|---|
| S1 | 開発ループを Factory の新しい集約にし、リポジトリの登録に属させる。周回 1 回がスプリントの実行 1 本に当たる | 定義 1(起票者)。NDF の開発ループの「周回」の定義 | 受け入れ条件 1 |
| S2 | 課題の選定と周回の締めはランナーの中の NDF(/ndf:development-workflow --loop・sprint-state.py loop)が行い、Factory は選定の規則を持たない |
定義 1(起票者)。全体の契約の腐敗防止層の向き | 受け入れ条件 1・前提 3 |
| S3 | ループの状態は running・between_laps・awaiting_approval・stopped・cancelled の 5 つ |
定義 1(起票者) | 受け入れ条件 2・前提 4・5 |
| S4 | 予算は NDF の loop.budget と同じ 3 つ(周回の数・費用・時間) |
定義 1(起票者)・.ndf/pace.json の loop.budget |
受け入れ条件 1 |
| S5 | NDF の止まる条件はランナーが進捗の事象 loop.stopped(条件と値)で送り、Factory はそれをループの状態へ写す。Factory が足す条件は「実行の枠が空かない」だけで、空くまで between_laps で待つ |
定義 1(起票者) | 受け入れ条件 2・3 |
| S6 | 実行の枠を数えるのは動いている実行だけで、between_laps のループは枠を使わない |
定義 1(起票者) | 受け入れ条件 3 |
| S7 | 選定の対象のマイルストーンが重なる 2 つのループは同じリポジトリで同時に動かせない(開始で断る) | 定義 1(起票者)。具体例の「ループごとに選定の対象のマイルストーンを分ける」 | 受け入れ条件 4 |
| S8 | 同じ課題を 2 つの動いている実行に入れない。開始の入力とループの選定の両方で外し、ループには EXCLUDE_ISSUES を渡して NDF の loop exclude で外す |
定義 2(起票者) | 受け入れ条件 5 |
| S9 | 同じリポジトリで並行する実行を許すのは #2164 を含む NDF の版を使う実行だけで、それより古い版では 1 本に絞る | 定義 2(起票者)。具体例の「#2164 が入るまで始めない」 | 受け入れ条件 6・前提 6 |
| S10 | 触るファイルの重なりは止めずに取り込み(base-sync)の時点で NDF が解き、解けなければ attention(reason: base_conflict)を送る |
定義 2(起票者)。具体例の「取り込みの時点で解く」 | 受け入れ条件 7 |
| S11 | リポジトリごとの同時の実行の数はプランの表が決め、宣言でそれより小さくできる | 定義 2(起票者) | 受け入れ条件 8 |
| S12 | 手元のランナーは登録と心拍で max_jobs(既定 1)を申告し、サービスはそれを超えて指示を渡さない。起動時の CPU の数とメモリから目安を出し、申告が超えれば警告する |
定義 3(起票者)。2026-09-25 の実測(8 コア・30 GB で全体テスト 6 本が上限) | 受け入れ条件 9・10 |
| S13 | #2346 の relay.py のホストを手元のランナーとして登録でき、そのときの max_jobs はホストの同時のセッションの上限 |
定義 3(起票者) | 含まない(#2346 側の実装)。契約の欄だけを受け入れ条件 9 で持つ |
| S14 | 画面にループの一覧(名前・選定の対象・状態・周回の数・費用・予算の残り・最後に動いた時刻・止まった理由)と詳細(周回ごとの実行・人の判断待ちの課題)を足す。操作は始める・止める・予算を変える・人の判断待ちに答える | 定義 4(起票者) | 受け入れ条件 1・11・12 |
| S15 | 承認の依頼にループの名前と周回の番号を載せ、複数のループの依頼を 1 つの一覧に並べる | 定義 4(起票者) | 受け入れ条件 13 |
| S16 | local の形態では、Anthropic に確かめるまでサービスがジョブを自動で起こさず、次の周回を起こす前に利用者の承認を求める。確かめた結果は #2189 の本文に記録し、認められればこの置き換えを外す |
定義 5(起票者)。#2189 の「未確認」 | 受け入れ条件 14・前提 7 |
| S17 | オーケストレーターを 2 層に分ける。実行をまたぐ制御は今の実行の制御(呼ばれたときだけ動くサーバーレス。常駐のプロセスを持たない)が持ち、1 つのジョブの中の区切りはランナーが持つ | 定義 6(起票者)。利用者の 3 つ目の原文(「常に動いているのでは」)への答え | 受け入れ条件 15・前提 8 |
| S18 | ランナーは conductor の最後の result の ndf-next のブロックを読み、- 結果: が 判断が要る でなければ同じジョブの中で次の区間を起動する。区間の上限は既定 20 で、超えたら section_limit で終える。区間ごとに section.started を送る |
定義 6(起票者) | 受け入れ条件 15・16 |
| S19 | ndf-next のブロックの読み方は NDF の relay.py(relay_lib/mark.py)と同じ規則にし、規則は NDF の契約(#2336)に宣言し、ランナーは形だけを写す |
定義 6(起票者)。plugins/ は factory/ を参照しない境界 |
受け入れ条件 17 |
| S20 | local の形態で利用上限に当たったジョブは job.finished(stopped、理由 usage_limit、回復の時刻)で終え、実行の制御は回復の時刻の後に resume を起こす。アカウントの切り替えは #2346 のホストが手元で行い、サービスはアカウントを知らない |
定義 7(起票者)。手元の relay.py のアカウントの切り替え(#1389) | 受け入れ条件 18・19 |
| S21 | 進捗の事象と実行ジョブの指示の追加(loop.stopped・section.started・LOOP_NAME・EXCLUDE_ISSUES・START=loop・max_jobs)は factory/contracts/ の定義と生成した型に置く |
元の受け入れ条件 13(起票者) | 受け入れ条件 20 |
目的
- 同じリポジトリで方針の違う開発ループを複数並べる運用(2026-11-01 の 3 本並列)を、Factory の利用者が Web から同じ形で行えるようにする
- 並行する実行どうしの取り合い(課題・版・触るファイル)と、手元の機械の資源の上限を、機械で守る
- 8 時間のジョブの中で conductor の context window が埋まっても、手元の relay.py と同じく区切りで起動し直して続けられるようにする
- 利用者の 3 つの原文に答える: 並行開発のオーケストレーションの構想を、決まっていない点を残さず仕様として定義する
前提
- 前提 1: この課題は設計の課題で、成果は Factory の確定仕様(
docs/specifications/ndf-factory-*.md)へ入る設計文書と、その設計を実装する子の課題である。受け入れ条件は実装の後に観測する振る舞いとして書き、設計の承認(承認ゲート 1)で範囲を確かめる - 前提 2: 実行の制御(
factory/api/src/runs/)とランナー(factory/runner/src/)の既存の形(docs/specifications/ndf-factory-runs-and-runner.md・ndf-factory-runs-state-and-data.md)は変えずに足す。実行の枠の数の行(I34・I35)と開始のトランザクションはそのまま使う - 前提 3: Factory は NDF のループの記録(
.ndf/loop/<名>/loop.json)を直接読まず、ランナーが進捗の事象へ翻訳したものだけを読む(腐敗防止層の向き) - 前提 4: NDF の止まる条件のうち、周回の途中で止まる
gate2_returnedはループの状態のawaiting_approvalへ写し、stoppedにしない。答え(承認)でresumeが続く。ほかの条件(user_stop・mvv_changed・same_failure・budget_laps・budget_usd・budget_hours・no_issue・outside_mvv・refused)はstoppedへ写し、条件と値を止まった理由に残す。refusedは元の定義 1 の列挙に無いが、loop.mdが周回の途中で止まる条件として持つため足す 前提 5: 利用者が画面から止めたループは→ 止めたループはcancelledにし、ランナーへは NDF のloop stopに当たる知らせを渡す。周回の実行が動いていれば、今の取り消し(cancelSprintRun)と同じ形で止めるcancelledにし、周回の実行が動いていれば今の取り消し(cancelSprintRun)で止める。NDF のloop stopに当たる知らせは渡さず、ループのcancelledそのものを知らせとする(2026-11-02、設計の決定 15。取り消したループには次のジョブが起きず、止める印を書いても読む者がいないため)- 前提 6: 「スプリントのプランが作った時点の版数を配布と本番のプランへ書き込み、並行する別のスプリントがその版を先に出すとプランを作り直すまで止まる #2164 を含む NDF の版」は、スプリントのプランが作った時点の版数を配布と本番のプランへ書き込み、並行する別のスプリントがその版を先に出すとプランを作り直すまで止まる #2164 を含んで出た最初の NDF の版数を下限とし、実行が使う NDF の版数(実行ジョブの指示が持つ版)と比べて決める。下限の版数は スプリントのプランが作った時点の版数を配布と本番のプランへ書き込み、並行する別のスプリントがその版を先に出すとプランを作り直すまで止まる #2164 のリリースの後に決まり、設計はその版数を設定の値として持つ(コードに埋め込まない)
- 前提 7:
localの形態の「次の周回を起こしてよいか」の承認は、承認の依頼(NDF Factory: 承認ゲートの通知(Slack を含む)と Web からの承認 #2173)の新しい種類として画面と通知に並ぶ。今の 3 種類(ゲート 1・ゲート 2・判断)のどれかへ寄せるか新しい種類にするかは設計で決める(未決 1) - 前提 8: 「低コストで常に動いているオーケストレーター」(利用者の 3 つ目の原文)は、常駐のプロセスとしては作らない。実行をまたぐ制御は今の呼ばれたときだけ動く実行の制御(事象と見回りで起きる)に置き、ジョブの中の区切りだけをランナー(ジョブの間だけ動く)に置く。常駐の費用は増えない
- 前提 9: 区間の上限の既定 20 とジョブの打ち切りの時間 8 時間は、今の手元の relay.py の運用の値に合わせた起票者の値で、設計の時点で実測(手元の relay の区間の数の分布)があれば置き換える
- 前提 10: 利用上限の検出は
claude -pの結果(resultの文と終了の形)から読む。managed・byokの形態では上限の切り替えをせず、API の失敗は今の失敗の扱いのまま
対象範囲
含む:
- 開発ループの集約(持つ値・状態・周回の起こし方・止まる条件の写し方・承認ゲート・実行の枠との関係・同じリポジトリの複数のループ)
- 同じリポジトリで並行する実行の調整(同じ課題の取り合い・版・触るファイルの重なり・本数の上限)
- 手元のランナーの同時実行数(
max_jobsの申告と、資源の目安の警告) - ループの見え方と承認(一覧・詳細・承認の依頼の並べ方・画面からの操作)
localの形態の自動実行を確かめるまでの扱い- ジョブの中の区切り(
ndf-nextを読んで区間を起動する)と区間の上限 localの形態の利用上限での終わり方と回復の後の再開- 上の追加が要る契約(進捗の事象・実行ジョブの指示・ランナーの登録と心拍)の定義と型
含まない:
- 課題の選定の規則(NDF の開発ループが持つ。Factory は持たない)
- スプリントのプランが作った時点の版数を配布と本番のプランへ書き込み、並行する別のスプリントがその版を先に出すとプランを作り直すまで止まる #2164(版数を流す時点に決める)の実装。この課題は スプリントのプランが作った時点の版数を配布と本番のプランへ書き込み、並行する別のスプリントがその版を先に出すとプランを作り直すまで止まる #2164 を含む版かどうかで並行を絞るだけである
- relay.py が同じコンテナの複数の claude セッションを統括する(2 本目以降はホストが起動し tmux で端末を結び付ける) #2346(relay.py が複数の claude セッションを統括する)の実装と、手元のアカウントの切り替え。サービスはアカウントを知らない
- Anthropic への確認そのもの(結果は NDF Factory: 手元のマシン(利用者の環境で、利用者の claude ログインで実行する) #2189 に記録する)
- 1 本のループの中で同時に流すプランの数(具体例の「1 本のループにつき 2 本まで」)。NDF の実行計画の並列の数が持ち、Factory はジョブの数だけを数える
managed・byokの形態の利用上限の切り替え
ドメインイベント
| # | イベント | 引き金 | 失敗したとき | 順序の前提 |
|---|---|---|---|---|
| E1 | 開発ループを始めた | 利用者の画面の操作 | 選定の対象のマイルストーンが同じリポジトリの動いているループと重なる → 理由つきで断る。リポジトリの登録が無い → 断る | — |
| E2 | 周回の実行を起こした(START=loop・LOOP_NAME・EXCLUDE_ISSUES) |
E1、または E8 の後の実行の制御 | 実行の枠が空かない → between_laps で待ち、枠が空いた知らせで起こし直す。local では E10 を先に通す |
E1 か E8 |
| E3 | 周回の課題を選んだ(ランナーの中の NDF の loop select) |
E2 のジョブの conductor | 候補が 0 件 → E7(no_issue)。候補がすべて外れた → E7(outside_mvv) |
E2 |
| E4 | ジョブの中で次の区間を起動した(section.started) |
conductor の result の ndf-next(判断が要る でない) |
区間の上限を超えた → job.finished(stopped・section_limit) |
E2 |
| E5 | 承認ゲート 2 で止まった | NDF の gate2_returned |
— | E3 |
| E6 | 人の判断待ちの課題ができた(承認ゲート 1 で MVV 判定が「従う」以外) | NDF の承認ゲート 1 | 周回は残りの課題で続く | E3 |
| E7 | ループが止まった(loop.stopped) |
ランナーが送る NDF の止まる条件 | 事象が届かずにジョブが終わった → 見回りが今のジョブの失敗として扱う(ループは stopped、理由は失敗) |
E2 |
| E8 | 周回の実行が終わった(done・人へ戻した) |
job.finished と停止の後の処理 |
失敗 → NDF の same_failure の数え方に任せ、次の周回を起こす |
E2 |
| E9 | 取り込みで衝突を解けなかった(attention・base_conflict) |
ランナーの中の NDF の base-sync | 実行は止まり、画面に並ぶ | E2 |
| E10 | 次の周回を起こす承認を求めた(local の形態) |
E8 の後、形態が local |
利用者が答えない → between_laps のまま待つ |
E8 |
| E11 | 利用上限でジョブを終えた(usage_limit・回復の時刻) |
claude -p の結果(local の形態) |
回復の時刻を読めない → 設計で決める既定の待ち(未決 3) | E2 |
| E12 | 回復の後に再開を起こした(resume) |
実行の制御の見回りが回復の時刻を過ぎたと見た | 枠が空かない → 空くまで待つ | E11 |
| E13 | 利用者がループを止めた(cancelled) |
利用者の画面の操作 | ループが既に終わりの状態 → 断る | E1 |
| E14 | 手元のランナーが max_jobs を申告した |
登録と心拍 | 資源の目安を超える → 警告して申告を受ける | — |
引き金の無いイベント・失敗の経路の無いイベントは残っていない。E5 は失敗の経路を持たない(NDF の承認ゲートで止まること自体が経路である)。E7 の「事象が届かない」は前提 4 と受け入れ条件 2 で扱う。
用語
| 用語 | 意味 |
|---|---|
| 開発ループ | スプリントを 1 つ終えるたびに次のスプリントを選んで続けて始める進め方。Factory ではリポジトリの登録に属する集約で、周回ごとにスプリントの実行を 1 本起こす |
| 周回 | 開発ループの 1 回分。Factory ではスプリントの実行 1 本に当たる |
| ループの予算 | 開発ループが使ってよい量の上限(周回の数・費用・経過時間) |
| 止まる条件 | 開発ループを止める、機械で判定できる条件 |
| 区間 | 1 つの実行ジョブの中で、ランナーが起動する claude -p の 1 回分。ndf-next で次の区間へ移る |
| 区間の上限 | 1 つの実行ジョブで起動してよい区間の数の上限(既定 20) |
| 同時のジョブの数 | 手元のランナーが同時に動かす実行ジョブの数の上限(max_jobs)。ランナーが申告する |
定義
1. 開発ループ(新しい集約)
開発ループは、1 つのリポジトリの登録に属し、スプリントの実行を順に起こし続ける集約である。 周回 1 回がスプリントの実行 1 本に当たる。課題の選定と周回の締めは、ランナーの中の NDF の開発ループ(/ndf:development-workflow --loop、sprint-state.py loop)がそのまま行い、Factory は選定の規則を持たない(腐敗防止層の向きを変えない)。
| 項目 | 定義 |
|---|---|
| 持つ値 | 名前(NDF のループ名の規則)・リポジトリの登録・選定の対象のマイルストーン(1 つ以上)・予算(周回の数・費用・時間。NDF の loop.budget と同じ 3 つ)・状態・今の周回の実行・止まった理由 |
| 状態 | running(周回の実行が動いている)→ between_laps(次の実行を起こす前)→ awaiting_approval(承認ゲート 2 で止まった)→ stopped(止まる条件に当たった)/ cancelled(利用者が止めた)。写し方は前提 4・5 |
| 周回の起こし方 | 実行の制御が、前の実行の終わり(done か「人へ戻した」)を受けて、同じループの次の実行を START=loop で起こす。指示に LOOP_NAME を足し、ランナーは /ndf:development-workflow --loop <名前> で conductor を起動する |
| 止まる条件 | NDF の止まる条件(user_stop・mvv_changed・same_failure・budget_laps・budget_usd・budget_hours・no_issue・outside_mvv・gate2_returned・refused)をランナーが進捗の事象 loop.stopped(条件と値)で送り、Factory はそれをループの状態へ写す。Factory が足す条件は「実行の枠が空かない」だけで、空くまで between_laps で待つ |
| 承認ゲート | 承認ゲート 1 で MVV 判定が「従う」以外の課題は、NDF の規則どおり人の判断待ちへ移り、周回は残りで続く。人の判断待ちの課題は承認の依頼(#2173)として画面に並ぶ。承認ゲート 2 は今の承認の依頼と同じで、答えで resume が続く |
| 実行の枠 | 枠を数えるのは動いている実行だけで、ループそのものは数えない。between_laps のループは枠を使わない |
| 同じリポジトリの複数のループ | 選定の対象のマイルストーンが重なるループは同じリポジトリで同時に動かせない(開始で断る)。重ならなければ並べてよい |
2. 同じリポジトリで並行する実行の調整
| 観点 | 定義 |
|---|---|
| 同じ課題の取り合い | 同じ課題を 2 つの動いている実行に入れない。開始の入力(課題の並び)と、ループの周回の選定の両方で、動いている実行が持つ課題を外す。実行の制御は動いている実行の課題の並びをランナーへ EXCLUDE_ISSUES で渡し、NDF の loop exclude で選定から外す |
| 版 | 版数は流す時点に NDF が決める(#2164)。同じリポジトリで並行する実行を許すのは、#2164 を含む NDF の版を使う実行だけで、それより古い版では同じリポジトリの実行を 1 本に絞る(前提 6) |
| 触るファイルの重なり | 重なりは止めずに、取り込み(base-sync)の時点で NDF が解く。解けずに止まった実行は attention(reason: base_conflict)を送り、画面に並ぶ |
| 本数の上限 | リポジトリごとの同時の実行の数はプランの表が決め(今の実行の枠)、宣言でそれより小さくできる |
3. 手元のランナーの同時実行数
| 項目 | 定義 |
|---|---|
| 上限 | ランナーは登録と心拍で max_jobs(同時に動かすジョブの数、既定 1)を申告し、サービスは動いているジョブがその数に達したランナーへ指示を渡さない |
| 資源 | ランナーは起動の時点の CPU の数とメモリから上限の目安を出し、申告がそれを超えれば警告する(2026-09-25 の実測: 8 コア・30 GB で全体テスト 6 本の同時実行が上限) |
| relay のホストとの関係 | #2346(relay.py が複数の claude セッションを統括する)のホストは、手元のランナーとして登録できる。そのときの max_jobs はホストの同時のセッションの上限である |
4. 見え方と承認
| 項目 | 定義 |
|---|---|
| ループの一覧 | 画面(#2172)にリポジトリごとのループの一覧を足す。列は名前・選定の対象・状態・周回の数・費用・予算の残り・最後に動いた時刻・止まった理由 |
| ループの詳細 | 周回ごとの実行(課題・結果・版・費用)と、人の判断待ちの課題の並び |
| 承認の依頼 | 承認の依頼(#2173)にループの名前と周回の番号を載せ、複数のループの依頼を 1 つの一覧で並べる |
| 操作 | 画面から、ループを始める・止める・予算を変える・人の判断待ちに答える(NDF の loop answer) |
5. 手元のランナーの自動実行(#2189 の未確認の扱い)
確かめるまで、local の形態ではサービスがジョブを自動で起こさない。 開発ループの周回の起こし方(定義 1)は、local の形態では「次の周回を起こしてよいか」を利用者に問う承認の依頼に置き換える。Anthropic に確かめた結果は #2189 の本文に記録し、認められればこの置き換えを外す。managed・byok の形態は今のまま自動で起こす。
6. ジョブの中の区切り(relay.py の役割をランナーへ)
オーケストレーターの役割は 2 層に分ける。 実行をまたぐ制御(開始・停止の知らせ・見回り・周回の起こし方)は今の実行の制御(呼ばれたときだけ動くサーバーレス。常駐のプロセスを持たない)が持つ。1 つのジョブの中の区切りは、手元の relay.py と同じ役割をランナーが持つ。
| 項目 | 定義 |
|---|---|
| 区切りの検出 | conductor の最後の result の文に ndf-next のブロックがあり、- 結果: の行が 判断が要る でないとき、ランナーはそのジョブの中で次の区間を claude -p <ブロックのコマンド> で起動する |
| 区間の数 | 1 つのジョブの区間の上限を持ち(既定 20)、超えたら job.finished(stopped、理由 section_limit)で終える。打ち切りの時間(8 時間)はジョブ全体に掛かる |
| 事象 | 区間を起動するたびに section.started(区間の番号・コマンド)を送り、画面の詳細に区間の並びが出る |
| 状態の引き継ぎ | 区間は同じコンテナ・同じ作業場所で続くため、S3 への保存は要らない。保存はジョブの終わり(承認ゲート・終わり)だけのまま |
| 共通の部品 | ndf-next のブロックの読み方は NDF の relay.py(relay_lib/mark.py)と同じ規則にする。規則は NDF の契約(#2336)に宣言し、ランナーは形だけを写す(plugins/ は factory/ を参照しない) |
7. 利用上限での切り替え(local の形態)
| 項目 | 定義 |
|---|---|
| 検出 | conductor が利用上限で止まったことを、ランナーが claude -p の結果から読む |
| 扱い | local の形態では、ランナーはそのジョブを job.finished(stopped、理由 usage_limit、回復の時刻)で終え、実行の制御は回復の時刻の後に resume のジョブを起こす。複数のアカウントの切り替えは #2346 のホストが手元で行い、サービスはアカウントを知らない |
managed・byok |
上限の切り替えは要らない。API の失敗は今の失敗の扱いのまま |
何が決まっていなかったか(確定仕様 docs/specifications/ndf-factory-*.md を 2026-11-01 に読んだ)
| 観点 | 今の仕様 | 足りなかったもの | 埋める定義 |
|---|---|---|---|
| 開発ループ | 開始のたびに課題の番号を 1〜20 件渡す。状態の保存に .ndf/loop は入る(I42) |
ループを始める・止める・予算を持つ入口、周回、方針ごとにループを並べる形 | 1・4 |
| 同じリポジトリの並行 | 実行の枠がリポジトリごとの本数だけを数える | 同じ課題の取り合い、版の衝突、触るファイルの重なりの扱い | 2 |
| 手元のランナーの並行 | #2189 は登録・心拍・指示の受け取りだけ | 1 つのランナーが同時に動かすジョブの数 | 3 |
| 見え方と承認 | #2173 が承認の依頼を知らせる | 複数のループのどれが待っているかを並べて見る形 | 4 |
| ジョブの中の区切り | ランナーは 1 つのジョブで claude -p を 1 回だけ起動し、conductor の ndf-next を読まない(factory/runner/src/ に語が無い。2026-11-02 に grep -rn ndf-next factory/runner/src が 0 件) |
手元の relay.py が持つ「区切りで claude を起動し直す」役割。ジョブは最大 8 時間で、その間に conductor の context window が埋まる | 6 |
| 利用上限での切り替え | managed(サービスの Bedrock)と byok は上限の切り替えが要らない |
local(利用者の claude ログイン)で利用上限に当たったときの扱い。手元の relay.py はアカウントを替えて続ける(#1389) |
7 |
| 手元のランナーの自動実行 | #2189 の「未確認」: サービスが起動する自動実行が Claude のプラン(Pro・Max)の使い方として認められるか | 確かめるまでの扱い | 5 |
受け入れ条件
- 1. 利用者が画面から、リポジトリの登録ごとに開発ループを始め・止め・予算(周回の数・費用・時間)を変えられ、ループは周回ごとにスプリントの実行を
START=loop・LOOP_NAMEつきで起こし続ける。周回の課題の選定と締めはランナーの中の NDF の開発ループが行い、Factory のコードに選定の規則が無い - 2. NDF の止まる条件(前提 4 の 10 個)はすべて進捗の事象
loop.stopped(条件と値)で届き、前提 4 の写し方どおりにループの状態(stoppedかawaiting_approval)と止まった理由として画面に出る。loop.stoppedが届かずにジョブが終わったループは、失敗を理由としてstoppedになる - 3. 実行の枠が空くまで次の周回を起こさず、
between_lapsのループは枠を使わない(枠の数は動いている実行の数と等しいまま。I34 が崩れない)。枠が空いたら、待っていたループの次の周回が起きる - 4. 選定の対象のマイルストーンが重なる 2 つのループを同じリポジトリで始めようとすると、後の方を理由(重なったマイルストーンと相手のループの名前)つきで断る。重ならない 2 つは始められる
- 5. 同じ課題が 2 つの動いている実行に入らないことを、開始の入力(課題の並びで動いている実行の課題を指すと断るか外す)とループの選定(
EXCLUDE_ISSUESに動いている実行の課題が並ぶ)の両方で確かめるテストがある - 6. スプリントのプランが作った時点の版数を配布と本番のプランへ書き込み、並行する別のスプリントがその版を先に出すとプランを作り直すまで止まる #2164 を含まない NDF の版を使う実行は、同じリポジトリで 1 本に絞られる(2 本目の開始が理由つきで断られる)。下限の版数は設定の値で持つ
- 7. 取り込みで解けない衝突は
attention(reason: base_conflict)として画面に並ぶ - 8. リポジトリごとの同時の実行の数は、プランの表の値と、宣言した小さい方の値のうち小さい方で断られる
- 9. 手元のランナーは登録と心拍で
max_jobs(既定 1)を申告し、サービスは動いているジョブがその数に達したランナーへ指示を渡さない - 10. 手元のランナーは起動の時点の CPU の数とメモリから同時のジョブの数の目安を出し、申告が目安を超えると警告を出す(申告は受ける)
- 11. 画面にリポジトリごとのループの一覧(名前・選定の対象・状態・周回の数・費用・予算の残り・最後に動いた時刻・止まった理由)がある
- 12. 画面にループの詳細(周回ごとの実行の課題・結果・版・費用と、人の判断待ちの課題の並び)があり、人の判断待ちの課題に画面から答えると NDF の
loop answerが働く - 13. 承認の依頼にループの名前と周回の番号が載り、複数のループの承認の依頼が 1 つの一覧に並ぶ
- 14.
localの形態では、次の周回を起こす前に利用者の承認を求め、承認されるまで次の実行を起こさない。Anthropic の確認の結果が NDF Factory: 手元のマシン(利用者の環境で、利用者の claude ログインで実行する) #2189 にあり認められていれば、設定でこの置き換えを外せる。managed・byokは承認を挟まずに起こす - 15. ランナーは conductor の最後の
resultのndf-nextのブロックを読み、- 結果:が判断が要るでないとき同じジョブの中で次の区間を起動する。判断が要るのときとブロックが無いときは起動しない。区間ごとにsection.started(区間の番号・コマンド)が届く - 16. 区間の数が上限(既定 20)を超えたジョブは
job.finished(stopped、理由section_limit)で終わる。打ち切りの時間(8 時間)は区間の合計に掛かる - 17.
ndf-nextのブロックの読み方の規則は NDF の契約(入口・受け渡しのファイル・設定・環境変数の契約を 1 か所に宣言し、宣言の外を検査で落とす #2336 の宣言)にあり、ランナーの読み方と NDF の relay.py(relay_lib/mark.py)の読み方が同じ入力で同じ答えを返すことを確かめるテストがある。plugins/はfactory/を参照しない(python3 scripts/check-service-boundary.pyが通る) - 18.
localの形態で利用上限に当たったジョブはjob.finished(stopped、理由usage_limit、回復の時刻)で終わり、実行の制御が回復の時刻の後にresumeのジョブを起こす - 19.
managed・byokの形態ではusage_limitの扱いをせず、API の失敗は今の失敗の扱いのまま(既存のテストが通る) - 20. 進捗の事象と実行ジョブの指示の追加(
loop.stopped・section.started・LOOP_NAME・EXCLUDE_ISSUES・START=loop・max_jobs)はfactory/contracts/の定義と生成した型にあり、契約の検査が通る - 21. 既存の開始の入口(課題の番号を 1〜20 件渡すスプリントの実行)の振る舞いは変わらない(既存の Factory のテストが通る)
非機能の条件
| 大項目 | 条件 |
|---|---|
| 可用性 | ループの次の周回は、実行の制御のどの処理が途中で止まっても、見回りか知らせの再送で同じ結果へやり直せる(1 つの周回の実行を 2 本起こさない) |
| 性能・拡張性 | 常駐のプロセスを足さない(前提 8)。手元のランナーの同時のジョブの数は max_jobs を上限とする |
| 運用・保守性 | ループの状態と止まった理由、区間の並び、usage_limit の回復の時刻が画面から読める |
| 移行性 | 既存のスプリントの実行と実行ジョブの指示・進捗の事象は互換のまま足す(足す欄は省略できる)。既存のランナーの登録に max_jobs が無ければ 1 として扱う |
| セキュリティ | サービスは local の形態のアカウントと認証を知らない(アカウントの切り替えは手元で行う)。ループの操作は、そのリポジトリの登録を持つアカウントだけができる |
影響
| 対象 | 影響 |
|---|---|
| 公開インタフェース | 変わる。Factory の API にループの入口を足し、進捗の事象・実行ジョブの指示・ランナーの登録と心拍に欄を足す。どれも足すだけで、既存の欄は変えない(互換あり) |
| データ | 開発ループの集約の表(または行)を足す。既存の実行・実行ジョブ・枠の行の形は変えない |
| 既存の振る舞い | ランナーが 1 つのジョブで claude -p を複数回起動するようになる(ndf-next があるとき)。local の形態で利用上限に当たったジョブの終わり方が failed から stopped(usage_limit)に変わる |
検証手段
| 項目 | 手段 |
|---|---|
| テスト | cd factory && npm test(vitest。実行の制御・ランナー・契約・画面)、uv run --frozen --project . --all-extras pytest plugins/ndf/scripts/tests -q -n 4(NDF の契約の宣言と ndf-next の読み方) |
| 静的解析・型検査 | 契約の検査(factory/contracts/ の生成した型と定義の一致)、python3 scripts/check-service-boundary.py |
| 手動確認 | 開発版で、managed か byok の形態のリポジトリに 2 本のループ(マイルストーンが重ならない)を始め、一覧・詳細・承認の依頼の一覧に両方が並ぶことを見る(リリース後テスト) |
前提とする取り決め
| 項目 | 参照先 / 決めたこと |
|---|---|
| プロジェクト構造 | AGENTS.md の「マーケットプレイスの構造」。plugins/ と .claude-plugin/marketplace.json は factory/ を参照しない。インフラの定義と秘密はこのリポジトリに入れない |
| コーディング規約 | Factory の既存の規約(docs/specifications/ndf-factory-service-boundary.md の集約と持ち主、1 つの処理が書き換える集約は 1 つ)。開発ループの集約を書き換える処理は、スプリントの実行と実行の枠の例外に倣って設計で決める |
| テスト戦略 | 実行の制御の振る舞い(枠・断る条件・周回の起こし方)は API の単体テスト、ランナーの区間と利用上限は偽の claude を使うランナーのテスト、契約は契約の検査、画面は既存の画面のテストの階層で持つ |
境界
| 区分 | 内容 |
|---|---|
| 常に行う | 既存の Factory のテストと契約の検査、境界の検査を流す |
| 確認してから行う | 公開インタフェース(API・契約)の追加、データの表の追加。承認ゲート 1・2 で人が見る |
| 行わない | 課題の選定の規則を Factory に持つこと、常駐のプロセスを足すこと、サービスが local のアカウントを扱うこと、plugins/ から factory/ を参照すること |
未決
| # | 項目 | 誰が決めるか | 期限 |
|---|---|---|---|
| 1 | 「次の周回を起こしてよいか」の承認を、承認の依頼の今の 3 種類のどれかへ寄せるか、新しい種類にするか(前提 7) | 設計(承認ゲート 1 で利用者が見る) | 設計の PR |
| 2 | 人の判断待ちの課題(承認ゲート 1 で MVV 判定が「従う」以外)を、承認の依頼のどの種類で並べるか | 設計 | 設計の PR |
| 3 | 利用上限の回復の時刻を claude -p の結果から読めなかったときの既定の待ち |
設計 | 設計の PR |
| 4 | 開発ループの集約の置き場(表の持ち主)と、ループと実行を 1 つの処理で書き換えるかどうか | 設計 | 設計の PR |
| 5 | 「#2164 を含む NDF の版」の下限の版数の値(前提 6) | #2164 のリリース | #2164 のリリース |
| 6 | Anthropic への確認の結果(local の自動実行が Claude のプランの使い方として認められるか) |
利用者(#2189 に記録) | 定めない。結果が無い間は定義 5 の置き換えのまま |
関連
- relay.py が同じコンテナの複数の claude セッションを統括する(2 本目以降はホストが起動し tmux で端末を結び付ける) #2346(relay.py が同じコンテナの複数の claude セッションを統括する): 手元のランナーと同じ「利用者の機械の上で複数の claude を起動・監視・停止する常駐のプロセス」。起動・一覧・停止の口を Factory の契約の形で揃える
- スプリントのプランが作った時点の版数を配布と本番のプランへ書き込み、並行する別のスプリントがその版を先に出すとプランを作り直すまで止まる #2164(版数を流す時点に決める)・異常系・ログ・回復とデータ・入出力の形を 1 つの実行の核へ集め、異常系の課題 103 件を型ごとにまとめて解く #2335(根本解決)
進行
モード: — / 進め方: auto
- 要求と受け入れ条件 — 2026-11-02 01:05
- 作業場所の用意 — 2026-11-02 03:20
- 設計 — 2026-11-02 01:15
- 素材の収集と出典の確定
- ドキュメント再構成
- ドキュメントレビュー
- 計画 — 2026-11-02 03:20
- 実装 — 2026-11-02 04:21
- 構造改善 — 2026-11-02 06:03
- 実装レビュー — 2026-11-02 06:42
- 完了判定 — 2026-11-02 05:11
- Pull Request — 2026-11-02 05:29
- 確定仕様化 — 2026-11-02 12:27
- 後片付け — 2026-11-02 12:57
- 配布 — 2026-11-02 07:51
- 体裁レビュー
- リリース後テスト — 2026-11-02 12:46
- 振り返り — 2026-11-02 12:52
- 棚卸し — 2026-11-02 12:57
前提にする方針(2026-11-02 追記)
利用者の指示で、マイルストーン「25 Neural Development Factory」の説明にスプリント MVV を置いた(操作性と UX 重視・画面先行・オフライン利用・AI からも人と同じく使える・国や地域を越えて使える。#2168 の方針の表にも同じ行がある)。この課題の画面は #2382(UX の土台)の上に作り、#2383(オフラインで読める)と #2384(API と MCP から画面と同じ操作)の形に合わせる。
人の判断待ち(開発ループ lfactory2)
- 判定: follow(承認ゲート 1)
- 理由: Mission・Vision: Factory の利用者が、方針の違う開発ループを Web から同じリポジトリに並べ、始め・止め・予算の変更・一覧と詳細の確認を行えるようにする設計で、目指す姿に沿う。承認の依頼の種類を増やさず(決定 6)、承認をゲート 1・2 に寄せており、Value 2 にも反しない/Value 4・Value 5: 選定から外す課題は、conductor への文での依頼でなく環境変数 NDF_LOOP_EXCLUDE で機械的に渡す(決定 3)。並行を許す版の下限・区間の上限・local の自動の周回はコードに埋め込まず設定で受ける(決定 12、環境変数の表)/Value 3・Value 10: 区間の上限 20 は relay の記録 14 本の実測(最大 9)から、同時のジョブの数の目安は 2026-09-25 の実測から、待ちの 900 秒は NDF の既存の値から決めている。常駐のプロセスを置かず費用を増やさない(決定 1)/Value 6・Value 7: 取り合いは条件つきの書き込みとトランザクションで根本の場所で守る(決定 2・11・12)。ndf-next の読み方の正本は NDF の契約の一覧の 1 か所に置き、両方のテストが同じ例の組を読む(決定 8)。決めたことと採らなかった案の理由が記録されている/人に見てもらうべき点が 2 つある。1 つ目は、local で利用上限に当たった後、リセット時刻にサービスが自動でジョブを積むこと。これは要求の定義 5「サービスがジョブを自動で起こさない」の例外で、設計自身が承認ゲート 1 で利用者が見るとしている。2 つ目は、検査 requirements-check が「ファイルを読めない」で止まったままで、再実行と原因の確認が残っていること
- 根拠の項目: Mission, Vision, Value 2, Value 3, Value 4, Value 5, Value 6, Value 7, Value 10, C2, C3
- 設計 Pull Request: NDF Factory: 方針の違う開発ループを同じリポジトリで並べ、Web から始め・止め・見て、ジョブの中で区切りを越えて続ける(#2347) #2380
- 答え: approved
- 答え方:
python3 /persistent/ai/.claude/plugins/cache/ai-plugins/ndf/10.27.7/scripts/sprint-state.py loop answer lfactory2 2347 --approved|--rejected
振り返り: #2427 (comment)
閉じた理由
実装(PR #2393・スプリント PR #2397)がマージされ、ndf v10.27.14 で出ました。リリース後テストで不合格だった受け入れ条件 7・13(取り込みの衝突と、複数のループの承認の依頼の一覧が画面に出ない)は #2430 が受け持ちます。保留の 2 件は Factory を動かす環境が要る確認です。残りは #2430 で追うため、この課題は閉じます。
Activity
[-]NDF Factory: 開発ループと、同じリポジトリで並行するスプリントの調整を持つ[/-][+]NDF Factory: 開発ループと、同じリポジトリで並行する実行の調整・手元のランナーの同時実行数・ループの見え方を定義する[/+][-]NDF Factory: 開発ループと、同じリポジトリで並行する実行の調整・手元のランナーの同時実行数・ループの見え方を定義する[/-][+]NDF Factory: 開発ループ・並行する実行の調整・ジョブの中の区切りと利用上限・手元のランナーの同時実行数・ループの見え方を定義する[/+]Docs: #2347 の設計のレビューを直す(手動の開始とループの取り合いの直列化・既存の実行の埋め戻し・開発ループの持ち主の名前)
Docs: #2347 の設計の round 2 のレビューを直す(マイルストーンの照合を題ごとに 1 件・移行の済みの行を表に 1 つ・…
Docs: #2347 の設計の最終スイープ(移行の済みの行をトランザクションの外で強い整合で読み、照合から外す)
Merge pull request #2380 from devbasex/design/issue-2347
Merge pull request #2393 from devbasex/feat/issue-2347-lfactory2-3
Docs: 仕組みのページを、オーケストレーターの 2 層の構想で作り直す