Skip to content

NDF Factory: オーガナイザーを Factory の側で実装し、同じリポジトリで並行する実行の本番の配布を 1 本にまとめ、承認と問いを 1 か所に集める #2439

Open

Description

@takemi-ohama

具体例

2026-11-02、3 本の開発ループ(lroot2・lflow・lfactory2)が 1 台のコンテナの tmux で動き、本番の配布がぶつかった。lflow が ndf v10.27.14 に他の 2 本の変更もまとめて出し、lroot2 の本番の Pull Request #2420 を閉じた。#2436(tmux の並列開発を計画から始める)は、これを 1 台の中のオーガナイザーで防ぐ。

Neural Development Factory で同じ 3 本を Web から始めると、周回ごとに別の実行ジョブで動く。ジョブはそれぞれ別のコンテナと別の clone を持つ(ECS の managed・byok、または利用者の手元のマシン #2189)。#2347(開発ループと並行する実行の調整)は、実行を起こす時点で課題・マイルストーン・NDF の版・同時の数の取り合いを守る。一方、承認ゲート 2 と本番の配布は実行ごとに出る。そのため 11-02 と同じ衝突が、同じリポジトリの実行ジョブの間で起きる。#2436 のオーガナイザーは 1 台の SQLite(DBOS)に閉じるので、この衝突を防げない。

Factory では、実行ジョブ 1 本が #2436 のレーン 1 本に当たる(ジョブの中で動くのはスプリント 1 本の conductor で、tmux のレーンは並ばない)。したがって Factory に要るのは #2436 の上位のまとめ役ではない。#2436 と同じオーガナイザーの役割を、レーン = 実行ジョブとして Factory の側で担う実装である(2026-11-02 の利用者の指摘で定義を改めた)。

この課題が終わると、同じリポジトリで並行する Factory の実行の本番の配布は、Factory のオーガナイザーが 1 本にまとめる。承認ゲート 2 は 1 回で済む。承認ゲートの外の問いも、リポジトリごとに 1 か所に並ぶ。

依頼(原文)

今回設計したオーガナイザーの挙動は同一コンテナ内の並列についての挙動ですが、Neural Development Factoryではさらにその上位にランナー間単位のオーガナイザーが必要になりそうです。検討して今の設計に組み込むか、別の設計PRを起こしてください

(書き手: 利用者、日付: 2026-11-02。#2436 の設計 PR #2437 の承認ゲート 1 の前)

用語が混乱してきた。1コンテナ=1ランナーっていう考え方でいいんだっけ?
それとも複数コンテナ起動できるマシン単位で1ランナー?

多分、今回のオーガナイザーの方が範囲が広くなっちゃってるね。「上位のオーガナイザー」の定義を変えないとダメかも

というより、全体のヒエラルキーを定義しないとダメかも。

Factory のオーガナイザーと 1 台の中のオーガナイザー は、対等という以上に排他ですね。
Factoryで動くときには1台の中のオーガナイザーは動かない。

Factoryのオーガナイザーは「1台の中のオーガナイザーの仕事全て+ランナー制御等固有の仕事」がその役割になるはず

(書き手: 利用者、日付: 2026-11-02。続けて受けた 2 つの指摘)

起票者の推定

利用者の原文に無く、起票者(conductor)と要求の工程が推して足した条件。S1〜S5 は起票者の判断の記録 09a63a07a4a5(ask.py decide)による。S6〜S11 は要求の工程が、人へ問わずに最も筋の通る案として選んだ。承認ゲート 1 で利用者が確かめるまで、利用者の決定として扱わない。

# 条件 推した出所 この要求での扱い
S1 #2436 へ組み込まず、別の課題と別の設計 PR にする plugins/ から factory/ を参照できない(scripts/check-service-boundary.py)。実行の基盤(DynamoDB・見回り)が違う。#2436 の設計は 6 本で 1,445 行ある 含む(この課題の範囲そのもの)
S2 Factory のオーガナイザーは常駐のプロセスにせず、Factory の API と 5 分ごとの見回りの中に置く docs/specifications/ndf-factory-dev-loops.md の概要「常駐のプロセスを持たない」 非機能の条件(可用性・運用)
S3 レーンからオーガナイザーへの依頼と答えの形(配布の依頼・承認ゲート 1/2 の依頼・問いの依頼)は #2436 が NDF の契約として公開し、この課題はランナーの翻訳で同じ形を受ける #2436 の決定 21。下流の腐敗防止層の形(ndf-factory-dev-loops.md のコンテキスト) 前提 1・受け入れ条件 10
S4 本番の配布の中身は #2436 と同じく、承認資料を作る時点の develop のコミットの範囲で決める。本番の Pull Request の規則(head が release/v<版>、develop に含まれるコミットだけ)を共有する #2436 の決定 13〜15 受け入れ条件 1・3・4
S5 本番の配布は、#2436 の一時レーンに当たる専用の実行ジョブ(配布の実行ジョブ)で流す #2436 の決定 19。Factory では実行ジョブがレーンの単位になる 受け入れ条件 6
S6 Factory の実行と Factory の外の 1 台の並列(#2436)が同じリポジトリを同時に回すときは、禁止せず、GitHub の上の本番の Pull Request で排他にする。どちらかの本番の Pull Request(head が release/v で始まり、宛先が main)が開いている間は、もう一方は配布を組まずに待つ 2 つのオーガナイザーは互いの記録を読めない(DBOS と DynamoDB)。両方が読めるのは GitHub の状態だけである。禁止すると、利用者の手元の作業を Factory が止めることになる 受け入れ条件 8
S7 Factory の実行の中では、NDF は本番の配布を自分で組まず、配布の依頼を出して周回を締める。切り替えはランナーが NDF へ渡す設定で行う #2436 の決定 11 は「ラッパーの外では今の振る舞いのまま」とする。このままでは Factory の実行の中の NDF が自分で配布する 前提 2・受け入れ条件 10
S8 同じリポジトリの実行が 1 本だけでも、配布は Factory のオーガナイザーを通す(1 本のときだけ今の経路に残さない) 経路を 2 つ持つと、1 本から 2 本へ増えた瞬間の扱いが要る。1 本でも結果は今と同じく 1 回の承認と 1 本の本番の Pull Request になる 受け入れ条件 2
S9 承認ゲート 1 の依頼は設計 PR ごとに別の承認の依頼のままにし、まとめるのは一覧への並べ方だけにする #2436 の決定 16。設計 PR は互いに独立で、コミットの範囲との結び付きが無い 受け入れ条件 9
S10 手元のランナー の改名とアカウントの「プラン」の改名は、説明文・用語集・画面の文言を対象にし、コードの識別子・API の項目名・データの項目名は変えない 識別子を変えると、API の互換と保存済みのデータの移行が要る。衝突は読み手の語の問題で、識別子は衝突していない(runnerId・plan) 前提 4・受け入れ条件 14〜16
S11 配布の実行ジョブの費用は、リポジトリを登録したアカウントの利用量に数える 同じリポジトリの実行は同じアカウントに属する。範囲に入った実行の数で割ると、計算と表示の規則が増える 前提 5・未決 1

目的

同じリポジトリで並行する Factory の実行どうしで、本番の配布をぶつけずに 1 本へまとめる。承認と問いを、リポジトリごとに 1 か所で答えられるようにする。あわせて、#2436 の階層の定義(確定仕様 https://github.com/devbasex/ai-plugins/blob/develop/docs/specifications/ndf-parallel-organizer.md )が Factory の側へ割り当てた語の衝突 2 つを解く。

前提

  • 前提 1: レーンとオーガナイザーの間の依頼と答えの形は、tmux の並列開発を計画から始める(並列の計画・レーンごとの引継ぎ文書・オーガナイザーへの指示書・--parallel で起動) #2436(tmux の並列開発を計画から始める)が NDF の契約 plugins/ndf/contracts/forms/parallel-request.json として公開し、ndf v10.27.15 で配布した(develop に plugins/ndf/scripts/parallel.py と契約がある)
  • 前提 2: NDF の側の切り替え口(本番の配布を自分で組まずに配布の依頼を出す)は、この課題が plugins/ndf/ に作る。NDF_ORGANIZER_DIR・NDF_ORGANIZER_LANE で外のオーガナイザーのレーンと判定する(確定仕様 docs/specifications/ndf-factory-organizer.md の「NDF の切り替え口」)
  • 前提 3: 承認の依頼の保存・通知・答えの経路は NDF Factory: 承認ゲートの通知(Slack を含む)と Web からの承認 #2173 のものを使う。この課題は、まとめた配布を 1 件の承認の依頼(種類 ゲート 2)として載せる
  • 前提 4: 衝突を解く改名は、ndf-factory の文脈の語だけを対象にする。ndf-workflow の「プラン」(plan.json)は変えない
  • 前提 5: 配布の実行ジョブは、リポジトリを登録したアカウントの実行として数える(S11)。利用量の表示と料金の上限での扱いは未決 1 で決める
  • 前提 6: 同じリポジトリで同時に動く実行の数は、今の上限(concurrent_repo_runs)のまま変えない
  • 前提 7: 受け入れ条件 10 の「parallel.py init・start・run が NDF_FACTORY_RUN を見て断る」処理と、NDF_FACTORY_RUN の契約の宣言は、この課題の NDF の側の変更に含めた。plugins/ndf/scripts/lib/parallel_external_cli.py が NDF_FACTORY_RUN があれば init・start・run を終了コード 1 で断り、契約は plugins/ndf/contracts/env/NDF_FACTORY_RUN.json にある(確定仕様 docs/specifications/ndf-factory-organizer.md の I68、docs/specifications/ndf-parallel-organizer.md の「対象範囲」)

対象範囲

含む:

含まない:

ドメインイベント

# イベント 引き金 失敗したとき 順序の前提
E1 実行が配布の依頼を出した 実行の中の conductor が承認ゲート 2 の手前に達した ランナーが依頼を送れずにジョブが終わった → 実行は終わりの理由つきで終わり、見回りが依頼の欠けを検出する(受け入れ条件 12) 実行の変更が develop へマージされている
E2 オーガナイザーが配布の範囲を決めた E1 が届き、流れている配布が無い develop の先頭を読めない → 次の見回りで読み直す E1
E3 承認資料をそろえた承認の依頼(ゲート 2)を作った E2 資料の生成が失敗した → 問いの依頼にして人へ戻す E2
E4 人(または pace: auto の MVV 判定)が承認の依頼に答えた E3 答えが届かない → 承認待ちのまま、実行は次の周回へ進む E3
E5 答えの時点の develop の先頭を比べ、進んでいたので承認資料を作り直して出し直した E4 で、提示したコミットから develop が進んでいた 出し直しへの答えでは比べない(1 回だけ) E4
E6 配布の実行ジョブを起こした E4 または E5 の答えが「出す」 ジョブが起動できない → 見回りが起こし直す E4・E5
E7 本番の Pull Request を 1 本作り、main へマージし、版を出した E6 途中で止まった → main へのマージの前なら差し戻せる。後なら「続きから流す」だけを出す E6
E8 範囲に入ったすべての実行へ配布の結果を返した E7 返し損ねた → 見回りが記録から返し直す。1 件の依頼に答えは 1 回 E7
E9 実行が問いの依頼を出した(承認ゲートの外) conductor が人の判断を求めた E1 と同じ —
E10 問いの答えを持ち主の実行へ返した 人が答えた 持ち主の実行が終わっていた → 持ち主のループの次の周回へ渡す E9
E11 Factory の外の本番の Pull Request が開いていたので、配布を組まずに待った E2 の時点で、他のオーガナイザーの本番の Pull Request が開いている 開いたまま動かない → 待ちの理由を一覧へ出す E1

用語

用語 意味
実行ジョブ 実行基盤が 1 回起動するコンテナ 1 つ分の実行(用語集の ndf-factory)。Factory では実行ジョブ 1 本がレーン 1 本に当たる
ランナー 1 つの実行ジョブの中で動くプログラム。1 コンテナ = 1 実行ジョブ = 1 ランナー(用語集の ndf-factory)
承認の依頼 実行ジョブが人の答えを待って終わったときに作る依頼。種類はゲート 1・ゲート 2・判断の 3 つ(用語集の ndf-factory)
見回り 実行の制御が 5 分ごとに動かす処理(用語集の ndf-factory)

オーガナイザーは役割の名前で、並行するレーンの本番の配布を 1 本にまとめ、問いを集め、答えの後の作業を持ち主のレーンへ返す。実装は 1 台の中(#2436。レーン = tmux のセッション)と Factory(この課題。レーン = 同じリポジトリの実行ジョブ)の 2 つで、上下ではなく排他である。Factory の実行ジョブの中では 1 台の中のオーガナイザーは動かない(2026-11-02 の利用者の指摘)。Factory のオーガナイザーの仕事は、1 台の中のオーガナイザーの仕事すべて(配布を 1 本にまとめる・問いを集める・答えの後の作業を持ち主へ返す・共有の作業を一時レーンへ渡す)に、Factory 固有の仕事(ランナーの制御=実行ジョブの起動・停止・答えでの再開、実行の枠と手元のマシンの枠、ジョブをまたぐ状態の置き場)を足したものである(同日の利用者の指摘)。語の定義は #2436 の変更が用語集へ足す。「上位のオーガナイザー」という呼び方は使わず、「Factory のオーガナイザー」と呼ぶ。

この課題で新しく決める語(Factory のオーガナイザーの部品の名前、マシン単位の手元の受け手の新しい名前、アカウントの契約の種類の新しい名前)は設計で決め、同じ変更で用語集へ足す。

受け入れ条件

配布のまとめ:

  • 1. 同じリポジトリで並行する 2 本以上の実行が承認ゲート 2 に達したとき、本番の Pull Request は 1 本だけ作られる。範囲に入った実行は、どれも同じ本番の Pull Request と版を配布の結果として記録に持つ
  • 2. 配布の依頼が届いた時点で流れている配布が無ければ、他の実行の依頼を待たずに配布の範囲を決め、承認の依頼を作る。同じリポジトリの実行が 1 本だけのときも同じ経路を通り、承認の依頼 1 件と本番の Pull Request 1 本になる
  • 3. 配布の範囲は、承認資料を作る時点の develop の先頭のコミット(main..<SHA>)で決まる。範囲に入った実行は、依頼を出したかどうかの印つきで承認資料に並ぶ
  • 4. 承認の答えの時点で develop が提示したコミットから進んでいれば、承認資料を作り直し、加わった範囲を添えて承認の依頼を 1 回だけ出し直す。出し直しへの答えでは比べず、出し直しで承認したコミットを出す
  • 5. 配布が流れている間に届いた依頼と develop へ入った変更は、今の配布の後に組む次の配布になる。同じリポジトリで同時に流れる配布は 1 本までである
  • 6. 本番の配布は、範囲に入った実行のどれでもない、配布専用の実行ジョブで流れる。配布専用の実行ジョブは結果を返して終わり、同じリポジトリで同時に 1 本までである
  • 7. 承認待ちの間も、範囲に入った実行は答えを待たずに次の周回へ進める(ジョブを終えて費用を止める今の形のまま)
  • 8. 同じリポジトリに、Factory の外で作られた本番の Pull Request(head が release/v で始まり、宛先が main)が開いている間は、Factory のオーガナイザーは配布を組まずに待ち、待ちの理由を承認の依頼の一覧の記録に残す。閉じた後の最初の見回りで配布を組む

承認と問いの集約:

障害と再開:

  • 12. Factory のオーガナイザーの処理が途中で止まっても、次の見回りが記録から続きを再開する。1 件の依頼に答えは 1 回だけ返り、同じ範囲で本番の Pull Request が 2 本作られることは無い
  • 13. main へのマージの後に止まった配布は、差し戻しを選べず「続きから流す」だけを出す

語の衝突:

  • 14. 用語集の ndf-factory の文脈で、マシン単位で複数の実行ジョブを受け取る手元の受け手に、「ランナー」と別の語が付く。もとの 手元のランナー は廃止した語として用語集に残り、glossary.py check が拾う。Factory の確定仕様・factory/ の説明文・画面の文言(factory/web/src/i18n/)に、廃止した語が残らない
  • 15. 用語集の ndf-factory の文脈で、アカウントの契約の種類が「料金プラン」になる。ndf-workflow の「プラン」(plan.json)とは別の語として並ぶ。Factory の確定仕様・factory/ の説明文・画面の文言に、契約の種類を指す裸の「プラン」が残らない
  • 16. 改名で、コードの識別子・API の項目名・保存済みのデータの項目名は変わらない(factory/ の型検査とテストが変更前と同じく通る)

非機能の条件

大項目 条件
可用性 常駐のプロセスを持たない。オーガナイザーの処理は API の handler と 5 分ごとの見回りの中で動き、止まっても次の見回りで続きから再開する(受け入れ条件 12)
性能・拡張性 配布の依頼が届いてから配布の実行が起動されるまで、見回り 1 回分(5 分)以内。承認資料を作る時間は含めない(2026-11-03 の承認ゲート 1 で利用者が決めた読み方)
運用・保守性 配布の範囲・出し直し・待ちの理由・配布専用の実行ジョブの結果が、リポジトリごとの記録に残り、後から辿れる
セキュリティ 承認の確定は人だけ(受け入れ条件 11)。配布専用の実行ジョブは、範囲に入った実行と同じリポジトリの権限だけを持つ

影響

対象 影響
公開インタフェース 変わる。承認の依頼の一覧の API が、リポジトリごとの一覧を返す。進捗の事象に、配布の依頼と結果の種類が加わる(加えるだけで既存の項目は変えない)。画面の文言の 2 語が変わる
データ 加わる。リポジトリごとのオーガナイザーの進み具合(受けた依頼・配布の範囲・答え)を持つ行。既存のデータの移行は無い
既存の振る舞い 変わる。Factory の実行は本番の配布を自分で流さず、配布専用の実行ジョブが流す。実行が 1 本だけのときも、承認の依頼 1 件と本番の Pull Request 1 本の結果は今と同じ

検証手段

項目 手段
テストと型検査 cd factory && npm ci && npm run check(契約の lint と、contracts・web・api・runner の型検査と vitest。オーガナイザーの集約・出し直し・待ち・再開は api の単体テストでメモリの表を使って確かめる)
契約 構造チェック(リポジトリの根の scripts/contracts_lib/model.py)が、ランナーの翻訳と #2436 の契約の例の組を照らす
境界 python3 scripts/check-service-boundary.py が通る
語 python3 plugins/ndf/scripts/glossary.py check が当たり 0 件
手動確認 同じリポジトリで 2 本の開発ループを Factory で回し、承認ゲート 2 が 1 件だけ一覧に出て、本番の Pull Request が 1 本だけ作られることを、利用者がリリース後テストで見る

前提とする取り決め

項目 参照先 / 決めたこと
プロジェクト構造 Factory のオーガナイザーとランナーの翻訳は factory/ に置く。plugins/ と .claude-plugin/marketplace.json は factory/ を参照しない(AGENTS.md の「マーケットプレイスの構造」)。NDF の側の切り替え口は plugins/ndf/ に置き、Factory を名指ししない
コーディング規約 factory/ の TypeScript の既存の書き方と、確定仕様 ndf-factory-dev-loops.md の集約・トランザクションの規則(1 つの処理が書き換える集約は 1 つ)
テスト戦略 オーガナイザーの規則は API の単体テスト(メモリの表)で、ランナーの翻訳は契約の例の組で、2 本の実行の衝突はリリース後テストで確かめる

境界

区分 内容
常に行う factory/ のテストと型検査の実行、境界のチェック、用語集のチェック
確認してから行う 公開インタフェース(API・進捗の事象・画面の文言)の変更。承認ゲート 1・2 で人が見る
行わない #2436 の 1 台の中のオーガナイザーの変更、main への出し方の規則(#2436 の決定 15)の変更、コードの識別子の改名、発表資料と記事の書き換え

未決

項目 誰が決めるか 期限
1. 配布専用の実行ジョブの費用を、利用量の表示と料金の上限(同時の数を含む)でどう数えるか。前提 5 はアカウントの利用量に数えるとだけ置いた 利用者(承認ゲート 1) 設計の承認まで
2. NDF の側の切り替え口(配布を自分で組まずに依頼を出す)は、この課題で作ると設計で決めた(確定仕様 docs/specifications/ndf-factory-organizer.md の「NDF の切り替え口」) #2436 と #2439 の設計 決定済み
3. オーガナイザーの進行の状態を DynamoDB の行で持つか、AWS の耐久ワークフローの部品(Step Functions など)を使うか。#2436 と同じく既存の部品を先に比べ、費用が増えるなら理由と代わりの案を示す 設計 設計の承認まで
4. マシン単位の手元の受け手の新しい名前 設計(用語集の語の選び方に従う) 設計の承認まで
5. parallel.py init・run の NDF_FACTORY_RUN の断りと NDF_FACTORY_RUN の契約の宣言(前提 7)は、この課題の NDF の側の変更に含めた conductor(範囲の判断) 決定済み

進行

モード: — / 進め方: auto

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

振り返り: #2490 (comment)

残っていること

実装は ndf v10.27.16 で配布した(実装の Pull Request #2483 )。残るのは検証手段の手動確認(同じリポジトリで 2 本の開発ループを Factory で回し、承認ゲート 2 が 1 件だけ一覧に出て、本番の Pull Request が 1 本だけ作られることを見る)だけである。Factory の実環境のインフラは private リポジトリが作るため、この確認を行う時期は private リポジトリ側のインフラの課題が持つ。

Activity

  1. changed the title [-]NDF Factory: ランナー間のオーガナイザーで、並行する実行の本番の配布を 1 本にまとめ、承認と問いを 1 か所に集める[/-] [+]NDF Factory: 実行間のオーガナイザーで、同じリポジトリで並行する実行の本番の配布を 1 本にまとめ、承認と問いを 1 か所に集める[/+] on Nov 2, 2026
  2. changed the title [-]NDF Factory: 実行間のオーガナイザーで、同じリポジトリで並行する実行の本番の配布を 1 本にまとめ、承認と問いを 1 か所に集める[/-] [+]NDF Factory: オーガナイザーを Factory の側で実装し、同じリポジトリで並行する実行の本番の配布を 1 本にまとめ、承認と問いを 1 か所に集める[/+] on Nov 2, 2026
  3. added 20 commits that reference this issue on Nov 2, 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