Dividiendo un plan en unidades de trabajo de dev-orchestra e implementándolas en paralelo
Introducción
dev-orchestra 1.2.0 puede dividir un plan en unidades de trabajo. Las unidades que no dependen entre sí se ejecutan en paralelo, cada una en su propio git worktree, y se fusionan en el árbol de trabajo a medida que terminan. Todos los cambios de la v1.1.0 a la v1.2.1 están en el artículo de la versión (en inglés).
La vez anterior probé a pasar instrucciones fijas por rol.
Normalmente, el orquestador (Claude Code u otro agente) ejecuta todos estos pasos por ti. Esta vez, para ver qué ocurre, ejecuté los mismos comandos a mano, uno por uno.
Mi entorno:
- Windows 11
- dev-orchestra 1.2.1
- Claude Code 2.1.309 (el implementer era sonnet de Claude)
- Python 3.14.7
Para la prueba, apunté DEV_ORCHESTRA_HOME a una carpeta distinta de mi configuración habitual y trabajé en un pequeño repositorio creado para ello (textkit).
dev-orchestra está publicado en GitHub (licencia MIT). Si te parece útil, me alegraría mucho que le dieras una estrella.
Pasos
1. Escribir ## Units en el plan
El ejemplo es un plan para añadir dos funciones, slugify(text) y word_count(text), a un pequeño paquete de Python llamado textkit. Están en archivos distintos, así que se pueden hacer en paralelo. El README y el CHANGELOG describen las dos, así que se actualizan juntos al final.
En el plan (plan.md), añade un encabezado ## Units y un bloque de código 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.
- Cada unidad lleva un
name, unkind(feature,fix,refactor,docs,test,ciointegration), susfiles(lo que cambia) ydepends_on(las unidades que deben terminar antes) - Cada unidad tiene una sección
### Unit: <name>con sus pasos - Los archivos que tocan todas las partes, como el CHANGELOG, van a una sola unidad
integrationque depende de todas las demás
En la práctica, el architect escribe este bloque cuando escribe el plan (cuando el trabajo se reparte entre archivos o el plan tiene más de cuatro pasos). Esta vez lo escribí a mano.
Pon las rutas de
## Files to Modifyentre comillas invertidas, como`textkit/slug.py`. Cuando las escribí sin comillas invertidas,units checkmostró líneaswarning:diciendo que los archivos no estaban en Files to Modify (no lo rechazó).
2. Comprobar con units check que las unidades se pueden ejecutar
Al principio puse README en los files de slug y de count. En ese estado, units check lo rechaza.
$ 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
El código de salida es 2. Dos unidades que pueden ejecutarse en paralelo y citan el mismo archivo chocarían al fusionarse, así que se detiene antes de ejecutar nada. Tras dejar README solo en la unidad docs, pasó.
$ dev-orchestra units check
3 units in 2 waves (implementation.max_parallel: 2):
wave 1: slug, count
wave 2: docs
slug y count se ejecutan en paralelo en la primera ola, y docs en la segunda. Cuántas se ejecutan a la vez lo decide implementation.max_parallel (2 por defecto, hasta 8).
3. Ejecutar las pruebas de las unidades después de fusionar
En mi entorno, cada vez que el Claude del implementer intentaba ejecutar un comando de shell, se quedaba esperando aprobación, así que no podía ejecutar las pruebas. (dev-orchestra arranca un implementer de Claude con --permission-mode acceptEdits, así que las ediciones de archivos pasan, pero los comandos de shell necesitan aprobación).
Por eso puse implementation.unit_tests: after_merge, que llegó en la 1.2.1, en el .dev-orchestra.yaml del repositorio.
version: 1
implementation:
unit_tests: after_merge
Así, cada unidad solo escribe código y pruebas, y las pruebas se ejecutan una sola vez, después de fusionarlo todo. Está pensado para proyectos cuyas pruebas comparten una base de datos o un puerto y chocarían si se ejecutaran en paralelo.
La primera vez lo ejecuté sin este ajuste. El registro de herramientas de una unidad no tenía ningún
Writey nunca escribió sus archivos, pero su informe decía que los había creado.units mergemostrómerged slug: 0 file(s), así que el número de archivos fusionados es la forma de darse cuenta.
4. Aprobar el plan y ejecutar dos unidades en paralelo
El implementer solo se ejecuta con un plan aprobado. design approve registra la aprobación (normalmente el orquestador te muestra el plan y solo lo ejecuta cuando dices que sí).
$ dev-orchestra design approve
approved .ai/workflows/units/plan.md (sha256 f108913500da) for workflow units
run implementer --unit <name> --detach arranca una unidad en segundo plano. Un solo archivo de petición (implement-request.md) sirve para todas las unidades; la CLI le antepone el nombre de la unidad, sus archivos y su sección del plan.
$ 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 muestra las dos en running, cada una en su propio worktree. Los worktrees se crearon en una carpeta textkit.units junto al repositorio (rutas parcialmente omitidas).
$ 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)
El prompt que recibió una unidad empezaba con una cabecera como esta (del primer intento).
# 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. Recoger la que termine primero con jobs wait --any
Desde la 1.2.0, jobs wait acepta varios ids de trabajo. Con --any, vuelve en cuanto termina cualquiera de ellos (salida resumida).
$ 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 terminó primero mientras slug seguía en marcha. Los dos eran trabajos pequeños de unos 30 segundos.
6. Devolverlas con units merge
Cada unidad terminada se devuelve al árbol de trabajo con units merge. Fusiona archivo por archivo y nunca toca el index, HEAD ni la rama.
$ dev-orchestra units merge count
merged count: 2 file(s)
Esperé a que slug terminara y la fusioné también.
$ 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
Los worktrees de las unidades fusionadas se limpiaron. El árbol de trabajo tenía ahora cuatro archivos nuevos.
$ git status --short
?? tests/test_count.py
?? tests/test_slug.py
?? textkit/count.py
?? textkit/slug.py
7. La unidad de integración se ejecuta allí mismo
Nada puede ejecutarse junto a la unidad docs que queda. En ese caso no se crea ningún worktree y se ejecuta directamente en el árbol de trabajo (la ejecuté sin --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
(El informe del implementer está en japonés porque mi entorno pide las respuestas en japonés. Dice que solo cambió README.md y CHANGELOG.md). Una unidad que se ejecutó allí mismo cuenta como fusionada al terminar. El README ganó una descripción de las dos funciones, y el CHANGELOG una sección ### Added.
8. Ejecutar las pruebas en el árbol fusionado
Con after_merge, las pruebas se ejecutan una vez al final.
$ python -m unittest
.........
----------------------------------------------------------------------
Ran 9 tests in 0.000s
OK
budget show muestra que ejecutar tres unidades usó solo un intento del implementer (salida resumida).
$ dev-orchestra budget show
implementer 1/5 used
delegated runs 3/40 used
implementer pass open: slug, count, docs run (plan f108913500da)
Las unidades de un mismo plan comparten un intento; solo volver a ejecutar una unidad gasta otro. El número de ejecuciones en sí (delegated runs) sí cuenta tres.
9. Cuando una fusión entra en conflicto (esta vez no ocurrió)
Mis fusiones no tuvieron conflictos. Según la referencia, hay conflicto cuando, por ejemplo, el mismo archivo cambió en el árbol de trabajo mientras la unidad se ejecutaba, y entonces:
units mergetermina con el código 7 y deja unas instrucciones en.ai/execution/units/<name>-conflicts.md- El orquestador hace que el review_fixer resuelva el conflicto a partir de esas instrucciones
units merge <name> --resolvedlo registra como resuelto
No probé un conflicto, así que esto es solo una descripción de cómo funciona.
Resumen
- Escribir un bloque
yamlbajo## Unitsen el plan divide la implementación en unidades de trabajo units checkrechaza las unidades cuyos archivos se solapan antes de ejecutar nada, y muestra las olas- Las unidades independientes se ejecutan en paralelo en git worktrees separados y vuelven archivo por archivo con
units merge, que nunca toca el index ni la rama - Una unidad que no puede ejecutarse junto a ninguna otra (aquí, la unidad
integration) se ejecuta allí mismo jobs wait --anyte entrega el trabajo que termine primero- Las unidades de un mismo plan comparten un intento del implementer
- Si tus pruebas no pueden ejecutarse en paralelo, usa
implementation.unit_tests: after_merge. Y fíjate en el número de archivos que muestraunits merge
La próxima vez mido cuánto tarda dev-orchestra y cuántos hallazgos da cada número de revisores.
GitHub: istb16/dev-orchestra (una estrella siempre se agradece)
Para ver todos los artículos de la serie juntos, usa la lista de artículos de “dev-orchestra”.
Artículos relacionados
Medí dev-orchestra: el tiempo por petición y los hallazgos según el número de revisores
Con dev-orchestra 1.2.1 pedí añadir una pequeña funcionalidad y medí cuánto tardó (unos 6 minutos sin contar mi aprobación) y cuántos hallazgos se informaron y aceptaron con 1, 2 y 4 revisores. Con más revisores hubo más hallazgos aceptados, pero los que se sumaron eran sobre las pruebas.
Dando a dev-orchestra instrucciones fijas para cada rol
Probé instructions, añadido en dev-orchestra 1.2.1. Las instrucciones fijas para el implementer o los revisores que escribes en el repositorio entran en cada prompt. Compruebo con la salida real cómo se muestran, dónde entran y cómo se trata un cambio en las instrucciones.
Actualizando dev-orchestra (Claude Code / Codex / Antigravity)
Actualicé dev-orchestra de la 1.0.0 a la 1.1.1 de verdad en Claude Code, Codex y Antigravity. En Claude Code, claude plugin update por sí solo no actualizó nada; antes hubo que actualizar el marketplace. Resumo cómo comprobar tu versión, los pasos para cada CLI y qué comprobar después de actualizar, además de cómo desinstalarlo.