はじめに
dev-orchestra 1.2.0 で、計画を作業単位に分けて実装できるようになりました。互いに関係しない作業単位は、それぞれ専用の git worktree で並べて走り、終わったものから作業ツリーへマージされます。v1.1.0 から v1.2.1 までの変更全体は、リリースの記事にまとめています。
前回は、役割ごとに決まった指示を渡す機能を試しました。
ふだんは、オーケストレーター(Claude Code など)がこの手順をすべて自動で進めます。今回は何が起きているのかを見るために、同じコマンドを手で 1 つずつ実行しました。
試した環境です。
- Windows 11
- dev-orchestra 1.2.1
- Claude Code 2.1.309(implementer は Claude の sonnet)
- Python 3.14.7
試すときは、普段の設定とは別のフォルダを DEV_ORCHESTRA_HOME に指定し、作業用に作った小さなリポジトリ(textkit)で動かしています。
dev-orchestra は GitHub で公開しています(MIT ライセンス)。役に立ちそうと思ったら、スターを付けてもらえるとうれしいです。
手順
1. 計画に ## Units を書く
題材は、textkit という小さな Python のパッケージに、slugify(text) と word_count(text) の 2 つの関数を足す計画です。2 つは別々のファイルなので並べて作れます。README と CHANGELOG は両方の結果を書くので、最後にまとめて直します。
計画(plan.md)に、## Units の見出しと、yaml のコードブロックを書きます。
## Units
```yaml
units:
- name: slug
kind: feature
files: [textkit/slug.py, tests/test_slug.py]
depends_on: []
- name: count
kind: feature
files: [textkit/count.py, tests/test_count.py]
depends_on: []
- name: docs
kind: integration
files: [README.md, CHANGELOG.md]
depends_on: [slug, count]
```
### Unit: slug
1. Add `slugify(text)` in textkit/slug.py: lowercase, runs of non-alphanumerics become one `-`, strip `-` at both ends.
2. Add tests/test_slug.py.
-
name、kind(feature、fix、refactor、docs、test、ci、integration)、files(変えるファイル)、depends_on(先に終わっている必要がある作業単位)を書く - 作業単位ごとに
### Unit: <name>の節を作り、その作業単位の手順を書く - CHANGELOG のように全員が触るファイルは、ほかのすべてに依存する
integrationの作業単位 1 つにまとめる
実際には architect が計画を書くときに、このブロックも書きます(ファイルが分かれる作業か、手順が 4 つを超える計画のとき)。今回は手で書きました。
## Files to Modifyには、パスを`textkit/slug.py`のようにバッククォートで囲んで並べます。囲まずに書いたら、units checkが「Files to Modify にない」というwarning:を出しました(拒否はされません)。
2. units check で走らせられるか確かめる
最初、README を slug と count の両方の files に入れていました。この状態で units check を実行すると、拒否されます。
$ dev-orchestra units check
.ai/workflows/demo/plan.md: the units cannot be run:
- slug and count may run in parallel but both list README.md
終了コードは 2 です。並んで走りうる 2 つの作業単位が同じファイルを挙げると、マージでぶつかるので、走らせる前に止めてくれます。README を docs の作業単位だけに移すと通りました。
$ dev-orchestra units check
3 units in 2 waves (implementation.max_parallel: 2):
wave 1: slug, count
wave 2: docs
slug と count が 1 つ目の波で並んで走り、docs が 2 つ目の波で走る、という予定です。同時に走らせる数は implementation.max_parallel(既定 2、最大 8)で決まります。
3. 作業単位のテストをマージ後に回す設定にする
今回の環境では、implementer の Claude がシェルのコマンドを実行しようとすると承認待ちになり、テストを流せませんでした(dev-orchestra は Claude の implementer を --permission-mode acceptEdits で起動するので、ファイルの編集は通りますが、シェルのコマンドは承認が要ります)。
そこで、1.2.1 で入った implementation.unit_tests: after_merge をリポジトリの .dev-orchestra.yaml に書きました。
version: 1
implementation:
unit_tests: after_merge
こうすると、各作業単位はコードとテストを書くだけにし、テストはすべてをマージしたあとに 1 回だけ流します。テスト用のデータベースやポートを共有していて、並べて流すとぶつかるプロジェクト向けの設定です。
最初はこの設定なしで走らせました。すると片方の作業単位は、ツールの記録に
Writeが 1 度もなく、ファイルを書いていなかったのに、報告では「ファイルを作った」と書いていました。units mergeはmerged slug: 0 file(s)と出したので、マージしたファイルの数を見れば気づけます。
4. 計画を承認して、2 つの作業単位を並べて走らせる
implementer は、承認された計画でないと走りません。design approve で承認を記録します(ふだんはオーケストレーターがあなたに計画を見せ、あなたが「はい」と言ったときだけ実行します)。
$ dev-orchestra design approve
approved .ai/workflows/units/plan.md (sha256 f108913500da) for workflow units
run implementer --unit <name> --detach で、作業単位を 1 つずつ裏で走らせます。依頼のファイル(implement-request.md)は全作業単位で 1 つで、作業単位の名前・ファイル・計画の節は CLI が前に付けます。
$ dev-orchestra run implementer --unit slug --prompt-file .ai/workflows/units/execution/implement-request.md --detach
started implementer as job implementer-20261025-165514-ee46f2
$ dev-orchestra run implementer --unit count --prompt-file .ai/workflows/units/execution/implement-request.md --detach
started implementer as job implementer-20261025-165515-d7c62b
units status を見ると、2 つが running で、それぞれ別の worktree にいます。worktree は、リポジトリの隣の textkit.units フォルダに作られていました(パスは一部省略しています)。
$ dev-orchestra units status
slug feature running job implementer-20261025-165514-ee46f2 base d736c6342cc7 ...\textkit.units\d736c634-slug
count feature running job implementer-20261025-165515-d7c62b base daf7aed8edd8 ...\textkit.units\daf7aed8-count
docs integration pending job - base - -
Ready to start: none
Unit tests: after_merge (units skip their tests; run them once on the merged tree)
作業単位に渡ったプロンプトの先頭には、こんなヘッダーが付いていました(最初の試行のもの)。
# Work unit: slug (feature)
This run implements one unit of the plan, and only that unit.
Its files: textkit/slug.py, tests/test_slug.py
5. jobs wait --any で、終わったものから受け取る
1.2.0 から、jobs wait に複数のジョブ id を渡せます。--any を付けると、どれか 1 つが終わった時点で戻ります(出力は抜粋)。
$ dev-orchestra jobs wait implementer-20261025-165514-ee46f2 implementer-20261025-165515-d7c62b --any
implementer-20261025-165514-ee46f2 RUNNING
unit: slug (...\textkit.units\d736c634-slug)
elapsed: 0m30s
activity: 9 tool uses, context 51,040 tokens
+00:15 Write textkit/slug.py
+00:17 Write tests/test_slug.py
...
implementer-20261025-165515-d7c62b SUCCEEDED
unit: count (...\textkit.units\daf7aed8-count)
activity: 8 tool uses, context 49,706 tokens
+00:15 Write textkit/count.py
+00:17 Write tests/test_count.py
...
count が先に終わり、slug はまだ走っています。2 つとも 30 秒前後で終わる小さな作業でした。
6. units merge で作業ツリーに戻す
終わった作業単位から、units merge で作業ツリーに戻します。ファイルごとのマージで、index、HEAD、ブランチには触りません。
$ dev-orchestra units merge count
merged count: 2 file(s)
slug も終わるのを待ってマージしました。
$ dev-orchestra units merge slug
merged slug: 2 file(s)
$ dev-orchestra units status
slug feature merged job implementer-20261025-165514-ee46f2 base d736c6342cc7 -
count feature merged job implementer-20261025-165515-d7c62b base daf7aed8edd8 -
docs integration pending job - base - -
Ready to start: docs
マージした作業単位の worktree は片付けられています。作業ツリーには、4 つのファイルが追加された状態になりました。
$ git status --short
?? tests/test_count.py
?? tests/test_slug.py
?? textkit/count.py
?? textkit/slug.py
7. 統合の作業単位は、その場で走る
残った docs は、ほかに並んで走るものがありません。この場合、worktree を作らず、作業ツリーでそのまま走ります(--detach なしで実行しました)。
$ dev-orchestra run implementer --unit docs --prompt-file .ai/workflows/units/execution/implement-request.md
docs ユニットを実装しました。変更したのは README.md と CHANGELOG.md だけです。
...
$ dev-orchestra units status
slug feature merged ...
count feature merged ...
docs integration merged job - base - in place
Ready to start: none
その場で走った作業単位は、終わった時点でマージ済みです。README には 2 つの関数の説明が、CHANGELOG には ### Added が足されていました。
8. マージしたツリーでテストを流す
after_merge なので、最後にテストを 1 回だけ流します。
$ python -m unittest
.........
----------------------------------------------------------------------
Ran 9 tests in 0.000s
OK
budget show を見ると、3 つの作業単位を走らせても、implementer の試行は 1 回しか使っていません(出力は抜粋)。
$ dev-orchestra budget show
implementer 1/5 used
delegated runs 3/40 used
implementer pass open: slug, count, docs run (plan f108913500da)
1 つの計画の作業単位は、試行を 1 回分け合います。同じ作業単位をやり直したときだけ、もう 1 回使います。実行の回数そのもの(delegated runs)は、3 回と数えられています。
9. 競合したとき(今回は起きなかった)
今回はマージで競合しませんでした。リファレンスによると、作業単位が走っている間に作業ツリーの同じファイルが変わっていた場合などに競合し、こうなります。
-
units mergeが終了コード 7 で終わり、.ai/execution/units/<name>-conflicts.mdに指示書を残す - オーケストレーターがその指示書で review_fixer に競合を解かせる
-
units merge <name> --resolvedで解決済みとして記録する
競合は試していないので、ここでは仕組みの紹介だけにします。
まとめ
- 計画に
## Unitsのyamlを書くと、作業単位に分けて実装できる -
units checkが、ファイルが重なる作業単位を走らせる前に拒否し、波を表示する - 依存しない作業単位は 別々の git worktree で並べて走り、
units mergeでファイルごとに作業ツリーへ戻る。index やブランチには触らない - ほかと並ばない作業単位(今回の
integration)は、その場で走る -
jobs wait --anyで、終わったものから受け取れる - 1 つの計画の作業単位は、implementer の試行を 1 回分け合う
- テストを並べて流せないなら
implementation.unit_tests: after_merge。units mergeが出すファイルの数も見ておく
次回は、dev-orchestra の所要時間と、レビュアーの人数ごとの指摘数を測ります。
GitHub: istb16/dev-orchestra(スターを付けてもらえると励みになります)
連載の回をまとめて見る場合は「dev-orchestra」の一覧からどうぞ。
この記事は tk3.biz のブログ からの転載です。