brigade-cli 0.10.3__tar.gz → 0.12.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/PKG-INFO +31 -14
  2. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/QUICKSTART.md +30 -9
  3. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/README.md +29 -12
  4. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/pyproject.toml +7 -7
  5. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/__init__.py +1 -1
  6. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/aboyeur.py +107 -37
  7. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/agents.py +79 -6
  8. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/center_cmd.py +3 -2
  9. brigade_cli-0.12.0/src/brigade/cli/__init__.py +149 -0
  10. brigade_cli-0.12.0/src/brigade/cli/_common.py +59 -0
  11. brigade_cli-0.12.0/src/brigade/cli/add.py +20 -0
  12. brigade_cli-0.12.0/src/brigade/cli/budgets.py +30 -0
  13. brigade_cli-0.12.0/src/brigade/cli/center.py +326 -0
  14. brigade_cli-0.12.0/src/brigade/cli/chat.py +91 -0
  15. brigade_cli-0.12.0/src/brigade/cli/completions.py +17 -0
  16. brigade_cli-0.12.0/src/brigade/cli/context.py +87 -0
  17. brigade_cli-0.12.0/src/brigade/cli/daily.py +225 -0
  18. brigade_cli-0.12.0/src/brigade/cli/doctor.py +25 -0
  19. brigade_cli-0.12.0/src/brigade/cli/dogfood.py +90 -0
  20. brigade_cli-0.12.0/src/brigade/cli/friction.py +94 -0
  21. brigade_cli-0.12.0/src/brigade/cli/handoff.py +390 -0
  22. brigade_cli-0.12.0/src/brigade/cli/handoff_template.py +25 -0
  23. brigade_cli-0.12.0/src/brigade/cli/hermes_fragments.py +19 -0
  24. brigade_cli-0.12.0/src/brigade/cli/ingest.py +35 -0
  25. brigade_cli-0.12.0/src/brigade/cli/init.py +112 -0
  26. brigade_cli-0.12.0/src/brigade/cli/learn.py +189 -0
  27. brigade_cli-0.12.0/src/brigade/cli/memory.py +128 -0
  28. brigade_cli-0.12.0/src/brigade/cli/notifications.py +106 -0
  29. brigade_cli-0.12.0/src/brigade/cli/openclaw_fragments.py +19 -0
  30. brigade_cli-0.12.0/src/brigade/cli/operator.py +419 -0
  31. brigade_cli-0.12.0/src/brigade/cli/pantry.py +90 -0
  32. brigade_cli-0.12.0/src/brigade/cli/projects.py +127 -0
  33. brigade_cli-0.12.0/src/brigade/cli/reconfigure.py +46 -0
  34. brigade_cli-0.12.0/src/brigade/cli/release.py +289 -0
  35. brigade_cli-0.12.0/src/brigade/cli/repos.py +867 -0
  36. brigade_cli-0.12.0/src/brigade/cli/research.py +183 -0
  37. brigade_cli-0.12.0/src/brigade/cli/roadmap.py +58 -0
  38. brigade_cli-0.12.0/src/brigade/cli/roster.py +47 -0
  39. brigade_cli-0.12.0/src/brigade/cli/run.py +238 -0
  40. brigade_cli-0.12.0/src/brigade/cli/runbook.py +90 -0
  41. brigade_cli-0.12.0/src/brigade/cli/runs.py +56 -0
  42. brigade_cli-0.12.0/src/brigade/cli/scrub.py +35 -0
  43. brigade_cli-0.12.0/src/brigade/cli/security.py +231 -0
  44. brigade_cli-0.12.0/src/brigade/cli/skills.py +280 -0
  45. brigade_cli-0.12.0/src/brigade/cli/status.py +20 -0
  46. brigade_cli-0.12.0/src/brigade/cli/tools.py +494 -0
  47. brigade_cli-0.12.0/src/brigade/cli/untrusted.py +47 -0
  48. brigade_cli-0.12.0/src/brigade/cli/work.py +2044 -0
  49. brigade_cli-0.12.0/src/brigade/completions.py +91 -0
  50. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/doctor.py +120 -27
  51. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/dogfood_cmd.py +4 -34
  52. brigade_cli-0.12.0/src/brigade/friction_cmd.py +615 -0
  53. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/install.py +55 -8
  54. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/learn_cmd.py +3 -0
  55. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/localio.py +60 -2
  56. brigade_cli-0.12.0/src/brigade/mcp_server.py +96 -0
  57. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/memory_cmd.py +201 -2
  58. brigade_cli-0.12.0/src/brigade/operator_cmd/__init__.py +95 -0
  59. brigade_cli-0.12.0/src/brigade/operator_cmd/adoption.py +407 -0
  60. brigade_cli-0.12.0/src/brigade/operator_cmd/guide.py +190 -0
  61. brigade_cli-0.12.0/src/brigade/operator_cmd/health.py +528 -0
  62. brigade_cli-0.12.0/src/brigade/operator_cmd/lifecycle.py +705 -0
  63. brigade_cli-0.12.0/src/brigade/operator_cmd/migration.py +589 -0
  64. brigade_cli-0.12.0/src/brigade/operator_cmd/surfaces.py +1045 -0
  65. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/proc.py +1 -0
  66. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/projects_cmd.py +15 -0
  67. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/prompt.py +6 -0
  68. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/repos_cmd.py +78 -2
  69. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/llm.py +6 -5
  70. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research_cmd.py +3 -1
  71. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/roster.py +14 -0
  72. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/roster_cmd.py +18 -0
  73. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/runbook_cmd.py +51 -3
  74. brigade_cli-0.12.0/src/brigade/runguard.py +192 -0
  75. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/scrub.py +63 -3
  76. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/security_cmd.py +112 -5
  77. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/selection.py +6 -0
  78. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/skills_cmd.py +58 -82
  79. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/status.py +25 -4
  80. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/depth/workspace.json +1 -1
  81. brigade_cli-0.12.0/src/brigade/templates/harnesses/amp.json +11 -0
  82. brigade_cli-0.12.0/src/brigade/templates/harnesses/crush.json +11 -0
  83. brigade_cli-0.12.0/src/brigade/templates/harnesses/grok.json +11 -0
  84. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/hermes/memory-handoff.harness.json +1 -1
  85. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/hermes/model-lanes.harness.json +1 -1
  86. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/hermes/workspace.harness.json +1 -1
  87. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/hooks/pre-push +15 -1
  88. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/chat-memory-sweep.example.json +1 -1
  89. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/memory-care.example.json +1 -1
  90. brigade_cli-0.12.0/src/brigade/templates/openhands/memory-handoffs/TEMPLATE.md +64 -0
  91. brigade_cli-0.12.0/src/brigade/templates/pi/memory-handoffs/TEMPLATE.md +64 -0
  92. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/policies/personal.json +1 -1
  93. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/policies/public-content.json +1 -1
  94. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/policies/public-repo.json +1 -1
  95. brigade_cli-0.12.0/src/brigade/templates/qwen/memory-handoffs/TEMPLATE.md +64 -0
  96. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/AGENTS.md +9 -0
  97. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/toml_compat.py +9 -0
  98. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/tools_cmd.py +31 -5
  99. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/work_cmd/__init__.py +108 -80
  100. brigade_cli-0.12.0/src/brigade/work_cmd/backup.py +174 -0
  101. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/work_cmd/config.py +4 -5
  102. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/work_cmd/helpers.py +17 -19
  103. brigade_cli-0.12.0/src/brigade/work_cmd/imports.py +934 -0
  104. brigade_cli-0.12.0/src/brigade/work_cmd/reviews.py +1328 -0
  105. brigade_cli-0.12.0/src/brigade/work_cmd/scanners.py +1178 -0
  106. brigade_cli-0.12.0/src/brigade/work_cmd/services.py +1105 -0
  107. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/work_cmd/session.py +20 -23
  108. brigade_cli-0.12.0/src/brigade/work_cmd/sweeps.py +771 -0
  109. brigade_cli-0.12.0/src/brigade/work_cmd/verification.py +724 -0
  110. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/PKG-INFO +31 -14
  111. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/SOURCES.txt +88 -4
  112. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_aboyeur.py +177 -0
  113. brigade_cli-0.12.0/tests/test_actionqueue.py +143 -0
  114. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_agents.py +76 -0
  115. brigade_cli-0.12.0/tests/test_completions.py +46 -0
  116. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_doctor.py +104 -1
  117. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_dogfood_cmd.py +3 -2
  118. brigade_cli-0.12.0/tests/test_friction_cmd.py +117 -0
  119. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_ingest.py +71 -0
  120. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_init.py +56 -0
  121. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_install.py +3 -1
  122. brigade_cli-0.12.0/tests/test_localio.py +38 -0
  123. brigade_cli-0.12.0/tests/test_mcp_server.py +60 -0
  124. brigade_cli-0.12.0/tests/test_memory_search_mcp.py +89 -0
  125. brigade_cli-0.12.0/tests/test_operator_checkup.py +72 -0
  126. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_operator_cmd.py +4 -4
  127. brigade_cli-0.12.0/tests/test_parity_backlog.py +92 -0
  128. brigade_cli-0.12.0/tests/test_pre_push_hook.py +30 -0
  129. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_prompt.py +4 -4
  130. brigade_cli-0.12.0/tests/test_read_only_enforcement.py +47 -0
  131. brigade_cli-0.12.0/tests/test_reportstore.py +150 -0
  132. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_repos_cmd.py +41 -1
  133. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_cmd.py +20 -0
  134. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_llm.py +15 -1
  135. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_roster.py +43 -0
  136. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_roster_cmd.py +7 -0
  137. brigade_cli-0.12.0/tests/test_run_cli.py +688 -0
  138. brigade_cli-0.12.0/tests/test_runbook_import.py +48 -0
  139. brigade_cli-0.12.0/tests/test_runguard.py +141 -0
  140. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_security_cmd.py +2 -2
  141. brigade_cli-0.12.0/tests/test_security_diff.py +62 -0
  142. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_selection.py +19 -1
  143. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_skills_cmd.py +3 -0
  144. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_status.py +17 -0
  145. brigade_cli-0.12.0/tests/test_subprocess_guards.py +51 -0
  146. brigade_cli-0.12.0/tests/test_work_cmd_backup.py +448 -0
  147. brigade_cli-0.12.0/tests/test_work_cmd_imports.py +3170 -0
  148. brigade_cli-0.12.0/tests/test_work_cmd_ledger.py +1055 -0
  149. brigade_cli-0.12.0/tests/test_work_cmd_reviews.py +727 -0
  150. brigade_cli-0.12.0/tests/test_work_cmd_scanners.py +719 -0
  151. brigade_cli-0.12.0/tests/test_work_cmd_services.py +2699 -0
  152. brigade_cli-0.12.0/tests/test_work_cmd_session.py +1699 -0
  153. brigade_cli-0.12.0/tests/test_work_cmd_sweeps.py +699 -0
  154. brigade_cli-0.12.0/tests/test_work_cmd_verification.py +259 -0
  155. brigade_cli-0.10.3/src/brigade/cli.py +0 -7063
  156. brigade_cli-0.10.3/src/brigade/operator_cmd.py +0 -3317
  157. brigade_cli-0.10.3/src/brigade/work_cmd/services.py +0 -6127
  158. brigade_cli-0.10.3/tests/test_run_cli.py +0 -244
  159. brigade_cli-0.10.3/tests/test_work_cmd.py +0 -11652
  160. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/LICENSE +0 -0
  161. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/MANIFEST.in +0 -0
  162. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/setup.cfg +0 -0
  163. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/__main__.py +0 -0
  164. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/actionqueue.py +0 -0
  165. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/add.py +0 -0
  166. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/budgets.py +0 -0
  167. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/budgets_cmd.py +0 -0
  168. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/chat_cmd.py +0 -0
  169. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/config.py +0 -0
  170. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/context_cmd.py +0 -0
  171. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/daily_cmd.py +0 -0
  172. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/fragments.py +0 -0
  173. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/handoff.py +0 -0
  174. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/handoff_cmd.py +0 -0
  175. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/hermes_adapter.py +0 -0
  176. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/ingest.py +0 -0
  177. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/managed.py +0 -0
  178. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/notifications_cmd.py +0 -0
  179. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/pantry_cmd.py +0 -0
  180. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/phases_cmd.py +0 -0
  181. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/py.typed +0 -0
  182. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/reconfigure.py +0 -0
  183. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/registry.py +0 -0
  184. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/release_cmd.py +0 -0
  185. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/reportstore.py +0 -0
  186. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/__init__.py +0 -0
  187. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/config.py +0 -0
  188. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/engine.py +0 -0
  189. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/extract.py +0 -0
  190. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/handoff.py +0 -0
  191. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/registry.py +0 -0
  192. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/report.py +0 -0
  193. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/sources/__init__.py +0 -0
  194. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/sources/cli.py +0 -0
  195. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/sources/local.py +0 -0
  196. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/sources/web.py +0 -0
  197. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/types.py +0 -0
  198. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/roadmap_cmd.py +0 -0
  199. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/runs_cmd.py +0 -0
  200. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/station.py +0 -0
  201. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/adal/memory-handoffs/TEMPLATE.md +0 -0
  202. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/aider/memory-handoffs/TEMPLATE.md +0 -0
  203. {brigade_cli-0.10.3/src/brigade/templates/antigravity → brigade_cli-0.12.0/src/brigade/templates/amp}/memory-handoffs/TEMPLATE.md +0 -0
  204. {brigade_cli-0.10.3/src/brigade/templates/claude → brigade_cli-0.12.0/src/brigade/templates/antigravity}/memory-handoffs/TEMPLATE.md +0 -0
  205. {brigade_cli-0.10.3/src/brigade/templates/codex → brigade_cli-0.12.0/src/brigade/templates/claude}/memory-handoffs/TEMPLATE.md +0 -0
  206. {brigade_cli-0.10.3/src/brigade/templates/continue → brigade_cli-0.12.0/src/brigade/templates/codex}/memory-handoffs/TEMPLATE.md +0 -0
  207. {brigade_cli-0.10.3/src/brigade/templates/copilot → brigade_cli-0.12.0/src/brigade/templates/continue}/memory-handoffs/TEMPLATE.md +0 -0
  208. {brigade_cli-0.10.3/src/brigade/templates/cursor → brigade_cli-0.12.0/src/brigade/templates/copilot}/memory-handoffs/TEMPLATE.md +0 -0
  209. {brigade_cli-0.10.3/src/brigade/templates/goose → brigade_cli-0.12.0/src/brigade/templates/crush}/memory-handoffs/TEMPLATE.md +0 -0
  210. {brigade_cli-0.10.3/src/brigade/templates/hermes → brigade_cli-0.12.0/src/brigade/templates/cursor}/memory-handoffs/TEMPLATE.md +0 -0
  211. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/depth/repo.json +0 -0
  212. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/generic/harness-adapter-checklist.md +0 -0
  213. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/generic/memory-contract.md +0 -0
  214. {brigade_cli-0.10.3/src/brigade/templates/kimi → brigade_cli-0.12.0/src/brigade/templates/goose}/memory-handoffs/TEMPLATE.md +0 -0
  215. {brigade_cli-0.10.3/src/brigade/templates/opencode → brigade_cli-0.12.0/src/brigade/templates/grok}/memory-handoffs/TEMPLATE.md +0 -0
  216. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/handoff/handoff-sources.example.json +0 -0
  217. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/handoff/openclaw-ingest-receipt.example.json +0 -0
  218. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/adal.json +0 -0
  219. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/aider.json +0 -0
  220. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/antigravity.json +0 -0
  221. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/claude.json +0 -0
  222. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/codex.json +0 -0
  223. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/continue.json +0 -0
  224. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/copilot.json +0 -0
  225. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/cursor.json +0 -0
  226. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/goose.json +0 -0
  227. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/hermes.json +0 -0
  228. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/kimi.json +0 -0
  229. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/openclaw.json +0 -0
  230. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/opencode.json +0 -0
  231. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/openhands.json +0 -0
  232. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/pi.json +0 -0
  233. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/qwen.json +0 -0
  234. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/hermes/README.md +0 -0
  235. {brigade_cli-0.10.3/src/brigade/templates/openhands → brigade_cli-0.12.0/src/brigade/templates/hermes}/memory-handoffs/TEMPLATE.md +0 -0
  236. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/includes/publisher.json +0 -0
  237. {brigade_cli-0.10.3/src/brigade/templates/pi → brigade_cli-0.12.0/src/brigade/templates/kimi}/memory-handoffs/TEMPLATE.md +0 -0
  238. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/backup-restic.md +0 -0
  239. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/chat-surface-crawlers.md +0 -0
  240. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/content-safety.md +0 -0
  241. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/handoff-flow.md +0 -0
  242. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/memory-architecture.md +0 -0
  243. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/memory-care-staleness.md +0 -0
  244. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/memory-scanner.md +0 -0
  245. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/multi-workspace-handoff-admin.md +0 -0
  246. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/obsidian-notes.md +0 -0
  247. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/pipeline-standups.md +0 -0
  248. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/tokenjuice-output-compaction.md +0 -0
  249. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/openclaw/README.md +0 -0
  250. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/openclaw/acp-escalation.openclaw.json +0 -0
  251. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/openclaw/memory-sweep-cron.openclaw.json +0 -0
  252. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/openclaw/model-aliases.openclaw.json +0 -0
  253. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/openclaw/ollama-memory-search.openclaw.json +0 -0
  254. {brigade_cli-0.10.3/src/brigade/templates/qwen → brigade_cli-0.12.0/src/brigade/templates/opencode}/memory-handoffs/TEMPLATE.md +0 -0
  255. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/scripts/backup-restic.sh +0 -0
  256. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/skills/note/SKILL.md +0 -0
  257. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/CLAUDE.md +0 -0
  258. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/HEARTBEAT.md +0 -0
  259. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/IDENTITY.md +0 -0
  260. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/INSTALL_FOR_AGENTS.md +0 -0
  261. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/MEMORY.md +0 -0
  262. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/SAFETY_RULES.md +0 -0
  263. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/SOUL.md +0 -0
  264. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/TOOLS.md +0 -0
  265. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/USER.md +0 -0
  266. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/rules/acceptance-driven-work.md +0 -0
  267. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/rules/issue-tdd-loop.md +0 -0
  268. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates.py +0 -0
  269. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/untrusted.py +0 -0
  270. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/untrusted_cmd.py +0 -0
  271. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/work_cmd/constants.py +0 -0
  272. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/work_cmd/ledger.py +0 -0
  273. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/dependency_links.txt +0 -0
  274. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/entry_points.txt +0 -0
  275. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/requires.txt +0 -0
  276. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/top_level.txt +0 -0
  277. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_add.py +0 -0
  278. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_budgets.py +0 -0
  279. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_cli_alias.py +0 -0
  280. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_cli_help.py +0 -0
  281. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_config.py +0 -0
  282. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_fragments.py +0 -0
  283. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_gitignore.py +0 -0
  284. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_handoff.py +0 -0
  285. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_handoff_cmd.py +0 -0
  286. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_managed.py +0 -0
  287. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_memory_cmd.py +0 -0
  288. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_neutrality.py +0 -0
  289. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_notifications_cmd.py +0 -0
  290. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_pantry_cmd.py +0 -0
  291. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase100_cmd.py +0 -0
  292. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase101_cmd.py +0 -0
  293. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase165_cmd.py +0 -0
  294. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase36_cmd.py +0 -0
  295. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase37_cmd.py +0 -0
  296. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase38_cmd.py +0 -0
  297. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase39_cmd.py +0 -0
  298. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase40_cmd.py +0 -0
  299. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase41_cmd.py +0 -0
  300. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase42_cmd.py +0 -0
  301. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase43_cmd.py +0 -0
  302. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase44_cmd.py +0 -0
  303. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase45_cmd.py +0 -0
  304. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase46_50_cmd.py +0 -0
  305. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase51_55_cmd.py +0 -0
  306. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase56_60_cmd.py +0 -0
  307. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase96_cmd.py +0 -0
  308. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase97_cmd.py +0 -0
  309. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase98_cmd.py +0 -0
  310. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase99_cmd.py +0 -0
  311. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_privacy_regression.py +0 -0
  312. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_proc.py +0 -0
  313. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_reconfigure.py +0 -0
  314. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_registry.py +0 -0
  315. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_release_cmd.py +0 -0
  316. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_cli_sources.py +0 -0
  317. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_config.py +0 -0
  318. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_engine.py +0 -0
  319. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_extract.py +0 -0
  320. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_handoff.py +0 -0
  321. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_local_sources.py +0 -0
  322. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_registry.py +0 -0
  323. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_report.py +0 -0
  324. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_types.py +0 -0
  325. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_web.py +0 -0
  326. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_roadmap_cmd.py +0 -0
  327. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_runbook_cmd.py +0 -0
  328. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_runs_cmd.py +0 -0
  329. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_scrub.py +0 -0
  330. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_station.py +0 -0
  331. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_toml_compat.py +0 -0
  332. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_untrusted.py +0 -0
  333. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_untrusted_cmd.py +0 -0
  334. {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_work_cmd_facade.py +0 -0
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brigade-cli
3
- Version: 0.10.3
3
+ Version: 0.12.0
4
4
  Summary: AI agent memory, handoffs, and local guardrails for Codex, Claude Code, OpenCode, Hermes, and OpenClaw.
5
- Author-email: Solomon Neas <srneas@gmail.com>
5
+ Author-email: Solomon Neas <me@solomonneas.dev>
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://brigade.tools
8
8
  Project-URL: Repository, https://github.com/escoffier-labs/brigade
@@ -35,7 +35,7 @@ Dynamic: license-file
35
35
  <h1 align="center">Brigade CLI</h1>
36
36
 
37
37
  <p align="center">
38
- <strong>AI agent memory, handoffs, and local guardrails for Codex, Claude Code, OpenCode, and a dozen other harnesses.</strong>
38
+ <strong>AI agent memory, handoffs, and local guardrails for Codex, Claude Code, OpenCode, and over a dozen other harnesses.</strong>
39
39
  </p>
40
40
 
41
41
  <p align="center">
@@ -47,36 +47,47 @@ Dynamic: license-file
47
47
 
48
48
  Your agents run loops. Brigade keeps the receipts.
49
49
 
50
+ ## Try it in 60 seconds
51
+
52
+ ```bash
53
+ pipx install brigade-cli
54
+ brigade operator quickstart --target ./my-repo --harnesses codex # wire one repo
55
+ brigade operator doctor --target ./my-repo --profile local-operator # verify
56
+ ```
57
+
58
+ That installs the CLI, wires memory, handoffs, and local guardrails into one repo for a single harness, and prints a readiness check. Nothing leaves your machine and no daemon is started. Add `--dry-run` to preview the file-by-file plan before anything is written. More harnesses, workspace setups, and the homegrown-adoption path are under [Install](#install).
59
+
50
60
  ## Why I built this
51
61
 
52
62
  I run an always-on OpenClaw agent next to daily Codex and Claude Code sessions, and I have since January. Every one of those tools wakes up empty. Whatever a session learned about my machine, my rules, or yesterday's dead ends scattered across tool-specific folders and died there.
53
63
 
54
64
  So I hand-rolled the fixes, one incident at a time: a slim `MEMORY.md` index pointing at small memory cards instead of one giant file, a handoff note format every harness could write, an ingest cron that filed the good notes into durable memory every 30 minutes, staleness checks so old cards stopped being trusted forever.
55
65
 
56
- Two incidents shaped the design more than anything I planned. First, a nightly "dreaming" job that auto-promoted session fragments bloated `MEMORY.md` to 41KB, way past the 12KB bootstrap budget, so every session started with truncated memory and nobody noticed for weeks. Auto-promotion died that day. Everything goes through review now. Second, I found 195 handoff notes sitting unread across 35 repos because the ingester had a hardcoded three-repo allowlist and nothing warned about the coverage gap. Silence is the failure mode. Every part of Brigade that lints, warns, or writes a receipt exists because something once failed in silence.
66
+ Two incidents shaped the design more than anything I planned. First, a nightly "dreaming" job that promoted raw session fragments straight into memory bloated `MEMORY.md` to 41KB, way past the 12KB bootstrap budget, so every session started with truncated memory and nobody noticed for weeks. Blind auto-promotion died that day. Now nothing reaches memory unlinted: a note has to name a target and clear the guards, the safe ones file themselves, and only the risky few wait for review. Second, I found 195 handoff notes sitting unread across 35 repos because the ingester had a hardcoded three-repo allowlist and nothing warned about the coverage gap. Silence is the failure mode. Every part of Brigade that lints, warns, or writes a receipt exists because something once failed in silence.
57
67
 
58
- That system now runs 482 memory cards and survives daily multi-agent work. But explaining it to anyone meant: clone six repos, write these crons, keep your index slim, watch for staleness, and whatever you do, turn auto-promotion off. Brigade is that setup packaged as one installable CLI. The full production stack is documented in the [solos-cookbook](https://github.com/escoffier-labs/solos-cookbook) if you want to see where it came from.
68
+ That system now runs 482 memory cards and survives daily multi-agent work. But explaining it to anyone meant: clone six repos, write these crons, keep your index slim, watch for staleness, and never let a note reach memory unlinted. Brigade is that setup packaged as one installable CLI. The full production stack is documented in the [solos-cookbook](https://github.com/escoffier-labs/solos-cookbook) if you want to see where it came from.
59
69
 
60
70
  ## The loop
61
71
 
62
- Writer harnesses leave handoff notes as they work. A memory owner (OpenClaw, Hermes, or just you) ingests the ones worth keeping. Brigade lints, guards, and routes everything in between, and every consequential action lands a receipt in a plain file you can grep, diff, and prune.
72
+ Writer harnesses leave handoff notes as they work. Brigade lints, guards, and classifies each one, then files the safe, targeted notes into durable memory on its own. A memory owner (OpenClaw, Hermes, or just you) only steps in for the ambiguous few. Every consequential action lands a receipt in a plain file you can grep, diff, and prune.
63
73
 
64
74
  1. agents write handoff notes into their own local inboxes
65
- 2. Brigade lints and scans them before they can become memory
66
- 3. safe targeted notes get filed into durable memory by the owner
67
- 4. ambiguous or risky notes wait for your review
75
+ 2. Brigade lints and classifies each one before it can become memory
76
+ 3. safe, targeted notes file themselves into durable memory automatically
77
+ 4. only the ambiguous or risky few wait for your review
68
78
  5. future sessions start with better context, and receipts show what happened
69
79
 
70
80
  ```mermaid
71
81
  flowchart LR
72
82
  WRITERS["writer harnesses<br/>Codex · Claude Code · OpenCode · ..."]
73
- BRIGADE["Brigade<br/>lint · guard · route · receipts"]
74
- REVIEW["operator review<br/>safe · ambiguous · risky"]
83
+ BRIGADE["Brigade<br/>lint · guard · classify · receipts"]
75
84
  OWNER["memory owner<br/>OpenClaw / Hermes / you"]
76
85
  MEM["durable memory<br/>MEMORY.md index · memory cards"]
86
+ REVIEW["review inbox<br/>ambiguous · risky"]
77
87
 
78
- WRITERS -- handoff notes --> BRIGADE --> REVIEW
79
- REVIEW -- safe targeted notes --> OWNER --> MEM
88
+ WRITERS -- handoff notes --> BRIGADE --> OWNER
89
+ OWNER -- safe targeted, auto-filed --> MEM
90
+ OWNER -. ambiguous or risky .-> REVIEW
80
91
  MEM -. context .-> WRITERS
81
92
 
82
93
  classDef brigade fill:#2563eb,stroke:#1d4ed8,color:#fff;
@@ -140,19 +151,24 @@ Each writer gets its own local inbox; one canonical owner ingests. Brigade keeps
140
151
  | Kimi Code | `kimi` | `.kimi/memory-handoffs/` |
141
152
  | AdaL | `adal` | `.adal/memory-handoffs/` |
142
153
  | OpenHands | `openhands` | `.openhands/memory-handoffs/` |
154
+ | Grok CLI | `grok` | `.grok/memory-handoffs/` |
155
+ | Amp | `amp` | `.amp/memory-handoffs/` |
156
+ | Crush | `crush` | `.crush/memory-handoffs/` |
143
157
  | Hermes | `hermes` | `.hermes/memory-handoffs/` |
144
158
  | OpenClaw | `openclaw` | usually the memory owner, not a writer |
145
159
 
146
- All of them get handoff templates, ingest source coverage, and projected tools/skills. Per-harness details are in the [technical guide](docs/technical-guide.md).
160
+ All of them get handoff templates and ingest source coverage. Most also get projected tools and skills in their native format (some as `rules` or `instructions`, a few not yet); the per-harness matrix is in the [technical guide](docs/technical-guide.md).
147
161
 
148
162
  ## Beyond memory
149
163
 
150
164
  The memory loop is the core. Around it, the same review-and-receipt pattern covers the rest of an operator's day, and you can ignore all of it until you need it:
151
165
 
152
166
  - **Daily loop**: `brigade work brief` shows pending work, imports, and warnings; `brigade daily status` keeps it bounded and cheap.
167
+ - **Friction logs**: `brigade friction scan --days 30 --import-candidates` mines recent notes, handoffs, session artifacts, and optional local agent logs for reviewable workflow friction.
153
168
  - **Security**: `brigade security scan` is a local read-only scanner for agent workspaces (secrets, risky hooks, MCP configs, prompt-injection patterns); `brigade scrub` gates content before it leaves the machine.
154
169
  - **Tools and skills**: one reviewed catalog projected into every harness's native format, with approval gates for anything that executes.
155
170
  - **Research**: `brigade research run` turns a question into a cited local report and a reviewable memory handoff.
171
+ - **Cross-model runs**: `brigade run "<task>"` plans, dispatches, and synthesizes one bounded task across the agent CLIs in your roster, so an expensive model can think while cheaper ones do the grunt work. Rosters pin a model per agent, plans can stage dependent workers, and `--worktree` runs everything in a detached git checkout that comes back as a reviewable `changes.patch`. A dirty-tree guard and a run lock keep agents away from your work in progress.
156
172
  - **Fleet and release**: health evidence across your local repos and release-readiness receipts, with no publish step.
157
173
 
158
174
  The full tour of every station lives in [docs/overview.md](docs/overview.md).
@@ -188,6 +204,7 @@ That pause is the point. Agent memory should be useful, not noisy.
188
204
  - [Handoff promotion](docs/handoff-promotion.md): how notes move toward memory.
189
205
  - [Repo fleet](docs/repo-fleet.md) and [Tool catalog](docs/tool-catalog.md).
190
206
  - [Command inventory](docs/command-inventory.md): every public CLI command.
207
+ - [Maintainers](MAINTAINERS.md), [Governance](GOVERNANCE.md), [Security](SECURITY.md), and [Contributing](CONTRIBUTING.md).
191
208
  - [Roadmap](ROADMAP.md) and [roadmap archive](docs/roadmap-archive.md).
192
209
 
193
210
  Project identity: GitHub [`escoffier-labs/brigade`](https://github.com/escoffier-labs/brigade), website [brigade.tools](https://brigade.tools), PyPI [`brigade-cli`](https://pypi.org/project/brigade-cli/), command `brigade`. The name comes from the kitchen: a *brigade de cuisine* runs the line, and *mise en place* means the station is prepped before service. Set up the rules, memory, tools, and receipts before the session gets expensive.
@@ -1,6 +1,6 @@
1
1
  # Quickstart
2
2
 
3
- Five minutes from clone to a working agent kitchen.
3
+ Five minutes from install to a working agent kitchen.
4
4
 
5
5
  ## 1. Install
6
6
 
@@ -23,7 +23,19 @@ python3 -m pipx ensurepath
23
23
 
24
24
  ## First install
25
25
 
26
- The fastest path is to run `brigade init` with no flags and answer the prompts:
26
+ The canonical first-run command is `brigade operator quickstart`. It installs the template files, wires the operator config, and runs the health checks in one shot:
27
+
28
+ ```bash
29
+ # Code repo with Codex as the writer
30
+ brigade operator quickstart --target ./my-repo --harnesses codex
31
+
32
+ # OpenClaw or Hermes workspace instead of a code repo
33
+ brigade operator quickstart --target ~/agent-workspace --depth workspace --harnesses openclaw,hermes --owner openclaw
34
+ ```
35
+
36
+ Pass `--dry-run` first to preview the planned steps without writing anything.
37
+
38
+ Two commands share this surface: `brigade init` installs the template files only, and `brigade operator quickstart` wraps it (init, then operator config, then doctor). Use `init` when you want the interactive harness picker or just the files:
27
39
 
28
40
  ```bash
29
41
  $ brigade init --target ~/agent-kitchen
@@ -43,8 +55,11 @@ Which harnesses do you use? (type numbers separated by space/comma to toggle, en
43
55
  [ ] 12. Kimi Code
44
56
  [ ] 13. AdaL
45
57
  [ ] 14. OpenHands
46
- [ ] 15. OpenClaw
47
- [ ] 16. Hermes (experimental)
58
+ [ ] 15. Grok CLI
59
+ [ ] 16. Amp
60
+ [ ] 17. Crush
61
+ [ ] 18. OpenClaw
62
+ [ ] 19. Hermes (experimental)
48
63
 
49
64
  Depth? (type a number, enter for default)
50
65
  * 1. repo (handoff flow + publish guard)
@@ -58,24 +73,30 @@ Defaults are claude harness, repo depth, no includes. Enter ships the install.
58
73
 
59
74
  ## CI / scripted install
60
75
 
61
- Pass flags directly to skip the prompt:
76
+ Pass flags directly to skip the prompt. The same flags work on `operator quickstart`:
62
77
 
63
78
  ```bash
64
79
  # Claude Code + Codex + OpenClaw, full workspace
65
- brigade init --target ~/agent-kitchen \
80
+ brigade operator quickstart --target ~/agent-kitchen \
66
81
  --depth workspace \
67
82
  --harnesses claude,codex,openclaw
68
83
 
69
84
  # Codex-only project, minimal install
70
- brigade init --target ./my-project --depth repo --harnesses codex
85
+ brigade operator quickstart --target ./my-project --depth repo --harnesses codex
71
86
 
72
- # Generic layout, no harness-specific files
87
+ # Template files only, no harness-specific files
73
88
  brigade init --target ./my-project --harnesses none
74
89
  ```
75
90
 
76
91
  ## Verifying
77
92
 
78
- After install, `brigade doctor --target <path>` reports the apparent harness shape and checks every configured inbox and adapter:
93
+ After install, check the operator profile:
94
+
95
+ ```bash
96
+ brigade operator doctor --target <path> --profile local-operator
97
+ ```
98
+
99
+ For the file-by-file view, `brigade doctor --target <path>` reports the apparent harness shape and checks every configured inbox and adapter:
79
100
 
80
101
  ```
81
102
  brigade doctor: target /home/you/agent-kitchen
@@ -5,7 +5,7 @@
5
5
  <h1 align="center">Brigade CLI</h1>
6
6
 
7
7
  <p align="center">
8
- <strong>AI agent memory, handoffs, and local guardrails for Codex, Claude Code, OpenCode, and a dozen other harnesses.</strong>
8
+ <strong>AI agent memory, handoffs, and local guardrails for Codex, Claude Code, OpenCode, and over a dozen other harnesses.</strong>
9
9
  </p>
10
10
 
11
11
  <p align="center">
@@ -17,36 +17,47 @@
17
17
 
18
18
  Your agents run loops. Brigade keeps the receipts.
19
19
 
20
+ ## Try it in 60 seconds
21
+
22
+ ```bash
23
+ pipx install brigade-cli
24
+ brigade operator quickstart --target ./my-repo --harnesses codex # wire one repo
25
+ brigade operator doctor --target ./my-repo --profile local-operator # verify
26
+ ```
27
+
28
+ That installs the CLI, wires memory, handoffs, and local guardrails into one repo for a single harness, and prints a readiness check. Nothing leaves your machine and no daemon is started. Add `--dry-run` to preview the file-by-file plan before anything is written. More harnesses, workspace setups, and the homegrown-adoption path are under [Install](#install).
29
+
20
30
  ## Why I built this
21
31
 
22
32
  I run an always-on OpenClaw agent next to daily Codex and Claude Code sessions, and I have since January. Every one of those tools wakes up empty. Whatever a session learned about my machine, my rules, or yesterday's dead ends scattered across tool-specific folders and died there.
23
33
 
24
34
  So I hand-rolled the fixes, one incident at a time: a slim `MEMORY.md` index pointing at small memory cards instead of one giant file, a handoff note format every harness could write, an ingest cron that filed the good notes into durable memory every 30 minutes, staleness checks so old cards stopped being trusted forever.
25
35
 
26
- Two incidents shaped the design more than anything I planned. First, a nightly "dreaming" job that auto-promoted session fragments bloated `MEMORY.md` to 41KB, way past the 12KB bootstrap budget, so every session started with truncated memory and nobody noticed for weeks. Auto-promotion died that day. Everything goes through review now. Second, I found 195 handoff notes sitting unread across 35 repos because the ingester had a hardcoded three-repo allowlist and nothing warned about the coverage gap. Silence is the failure mode. Every part of Brigade that lints, warns, or writes a receipt exists because something once failed in silence.
36
+ Two incidents shaped the design more than anything I planned. First, a nightly "dreaming" job that promoted raw session fragments straight into memory bloated `MEMORY.md` to 41KB, way past the 12KB bootstrap budget, so every session started with truncated memory and nobody noticed for weeks. Blind auto-promotion died that day. Now nothing reaches memory unlinted: a note has to name a target and clear the guards, the safe ones file themselves, and only the risky few wait for review. Second, I found 195 handoff notes sitting unread across 35 repos because the ingester had a hardcoded three-repo allowlist and nothing warned about the coverage gap. Silence is the failure mode. Every part of Brigade that lints, warns, or writes a receipt exists because something once failed in silence.
27
37
 
28
- That system now runs 482 memory cards and survives daily multi-agent work. But explaining it to anyone meant: clone six repos, write these crons, keep your index slim, watch for staleness, and whatever you do, turn auto-promotion off. Brigade is that setup packaged as one installable CLI. The full production stack is documented in the [solos-cookbook](https://github.com/escoffier-labs/solos-cookbook) if you want to see where it came from.
38
+ That system now runs 482 memory cards and survives daily multi-agent work. But explaining it to anyone meant: clone six repos, write these crons, keep your index slim, watch for staleness, and never let a note reach memory unlinted. Brigade is that setup packaged as one installable CLI. The full production stack is documented in the [solos-cookbook](https://github.com/escoffier-labs/solos-cookbook) if you want to see where it came from.
29
39
 
30
40
  ## The loop
31
41
 
32
- Writer harnesses leave handoff notes as they work. A memory owner (OpenClaw, Hermes, or just you) ingests the ones worth keeping. Brigade lints, guards, and routes everything in between, and every consequential action lands a receipt in a plain file you can grep, diff, and prune.
42
+ Writer harnesses leave handoff notes as they work. Brigade lints, guards, and classifies each one, then files the safe, targeted notes into durable memory on its own. A memory owner (OpenClaw, Hermes, or just you) only steps in for the ambiguous few. Every consequential action lands a receipt in a plain file you can grep, diff, and prune.
33
43
 
34
44
  1. agents write handoff notes into their own local inboxes
35
- 2. Brigade lints and scans them before they can become memory
36
- 3. safe targeted notes get filed into durable memory by the owner
37
- 4. ambiguous or risky notes wait for your review
45
+ 2. Brigade lints and classifies each one before it can become memory
46
+ 3. safe, targeted notes file themselves into durable memory automatically
47
+ 4. only the ambiguous or risky few wait for your review
38
48
  5. future sessions start with better context, and receipts show what happened
39
49
 
40
50
  ```mermaid
41
51
  flowchart LR
42
52
  WRITERS["writer harnesses<br/>Codex · Claude Code · OpenCode · ..."]
43
- BRIGADE["Brigade<br/>lint · guard · route · receipts"]
44
- REVIEW["operator review<br/>safe · ambiguous · risky"]
53
+ BRIGADE["Brigade<br/>lint · guard · classify · receipts"]
45
54
  OWNER["memory owner<br/>OpenClaw / Hermes / you"]
46
55
  MEM["durable memory<br/>MEMORY.md index · memory cards"]
56
+ REVIEW["review inbox<br/>ambiguous · risky"]
47
57
 
48
- WRITERS -- handoff notes --> BRIGADE --> REVIEW
49
- REVIEW -- safe targeted notes --> OWNER --> MEM
58
+ WRITERS -- handoff notes --> BRIGADE --> OWNER
59
+ OWNER -- safe targeted, auto-filed --> MEM
60
+ OWNER -. ambiguous or risky .-> REVIEW
50
61
  MEM -. context .-> WRITERS
51
62
 
52
63
  classDef brigade fill:#2563eb,stroke:#1d4ed8,color:#fff;
@@ -110,19 +121,24 @@ Each writer gets its own local inbox; one canonical owner ingests. Brigade keeps
110
121
  | Kimi Code | `kimi` | `.kimi/memory-handoffs/` |
111
122
  | AdaL | `adal` | `.adal/memory-handoffs/` |
112
123
  | OpenHands | `openhands` | `.openhands/memory-handoffs/` |
124
+ | Grok CLI | `grok` | `.grok/memory-handoffs/` |
125
+ | Amp | `amp` | `.amp/memory-handoffs/` |
126
+ | Crush | `crush` | `.crush/memory-handoffs/` |
113
127
  | Hermes | `hermes` | `.hermes/memory-handoffs/` |
114
128
  | OpenClaw | `openclaw` | usually the memory owner, not a writer |
115
129
 
116
- All of them get handoff templates, ingest source coverage, and projected tools/skills. Per-harness details are in the [technical guide](docs/technical-guide.md).
130
+ All of them get handoff templates and ingest source coverage. Most also get projected tools and skills in their native format (some as `rules` or `instructions`, a few not yet); the per-harness matrix is in the [technical guide](docs/technical-guide.md).
117
131
 
118
132
  ## Beyond memory
119
133
 
120
134
  The memory loop is the core. Around it, the same review-and-receipt pattern covers the rest of an operator's day, and you can ignore all of it until you need it:
121
135
 
122
136
  - **Daily loop**: `brigade work brief` shows pending work, imports, and warnings; `brigade daily status` keeps it bounded and cheap.
137
+ - **Friction logs**: `brigade friction scan --days 30 --import-candidates` mines recent notes, handoffs, session artifacts, and optional local agent logs for reviewable workflow friction.
123
138
  - **Security**: `brigade security scan` is a local read-only scanner for agent workspaces (secrets, risky hooks, MCP configs, prompt-injection patterns); `brigade scrub` gates content before it leaves the machine.
124
139
  - **Tools and skills**: one reviewed catalog projected into every harness's native format, with approval gates for anything that executes.
125
140
  - **Research**: `brigade research run` turns a question into a cited local report and a reviewable memory handoff.
141
+ - **Cross-model runs**: `brigade run "<task>"` plans, dispatches, and synthesizes one bounded task across the agent CLIs in your roster, so an expensive model can think while cheaper ones do the grunt work. Rosters pin a model per agent, plans can stage dependent workers, and `--worktree` runs everything in a detached git checkout that comes back as a reviewable `changes.patch`. A dirty-tree guard and a run lock keep agents away from your work in progress.
126
142
  - **Fleet and release**: health evidence across your local repos and release-readiness receipts, with no publish step.
127
143
 
128
144
  The full tour of every station lives in [docs/overview.md](docs/overview.md).
@@ -158,6 +174,7 @@ That pause is the point. Agent memory should be useful, not noisy.
158
174
  - [Handoff promotion](docs/handoff-promotion.md): how notes move toward memory.
159
175
  - [Repo fleet](docs/repo-fleet.md) and [Tool catalog](docs/tool-catalog.md).
160
176
  - [Command inventory](docs/command-inventory.md): every public CLI command.
177
+ - [Maintainers](MAINTAINERS.md), [Governance](GOVERNANCE.md), [Security](SECURITY.md), and [Contributing](CONTRIBUTING.md).
161
178
  - [Roadmap](ROADMAP.md) and [roadmap archive](docs/roadmap-archive.md).
162
179
 
163
180
  Project identity: GitHub [`escoffier-labs/brigade`](https://github.com/escoffier-labs/brigade), website [brigade.tools](https://brigade.tools), PyPI [`brigade-cli`](https://pypi.org/project/brigade-cli/), command `brigade`. The name comes from the kitchen: a *brigade de cuisine* runs the line, and *mise en place* means the station is prepped before service. Set up the rules, memory, tools, and receipts before the session gets expensive.
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "brigade-cli"
7
- version = "0.10.3"
7
+ version = "0.12.0"
8
8
  description = "AI agent memory, handoffs, and local guardrails for Codex, Claude Code, OpenCode, Hermes, and OpenClaw."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
11
11
  license = { text = "MIT" }
12
- authors = [{ name = "Solomon Neas", email = "srneas@gmail.com" }] # content-guard: allow email
12
+ authors = [{ name = "Solomon Neas", email = "me@solomonneas.dev" }] # content-guard: allow email
13
13
  keywords = ["agents", "ai-agents", "agent-memory", "agent-handoffs", "ai-memory", "openclaw", "claude-code", "codex", "opencode", "memory", "bootstrap", "brigade", "brigade-cli", "operator", "local-first", "guardrails", "agents-md"]
14
14
  classifiers = [
15
15
  "Development Status :: 3 - Alpha",
@@ -53,8 +53,8 @@ line-length = 120
53
53
  src = ["src", "tests"]
54
54
 
55
55
  [tool.ruff.lint]
56
- # Pragmatic initial gate: ruff's default rule set (pyflakes plus the
57
- # E4/E7/E9 pycodestyle errors). Deferred for later, separate passes:
58
- # import sorting (I), pyupgrade (UP), bugbear (B), the full pycodestyle
59
- # E/W set, and any type checking (mypy/pyright).
60
- select = ["E4", "E7", "E9", "F"]
56
+ # Pragmatic gate: ruff's default rule set (pyflakes plus the E4/E7/E9
57
+ # pycodestyle errors) plus bugbear (B). Deferred for later, separate
58
+ # passes: import sorting (I), pyupgrade (UP), the full pycodestyle E/W
59
+ # set, and any type checking (mypy/pyright).
60
+ select = ["B", "E4", "E7", "E9", "F"]
@@ -1,3 +1,3 @@
1
1
  """Brigade: local operator-system CLI for agent workspaces."""
2
2
 
3
- __version__ = "0.10.3"
3
+ __version__ = "0.12.0"
@@ -20,6 +20,7 @@ from .roster import Agent, Roster, is_cli_allowed, timeout_for, workers
20
20
  class Assignment:
21
21
  worker: str
22
22
  task: str
23
+ stage: int = 1
23
24
 
24
25
 
25
26
  @dataclass(frozen=True)
@@ -46,11 +47,14 @@ def build_plan_prompt(
46
47
  return (
47
48
  "You are the Brigade aboyeur. Split the user's task across the available workers.\n"
48
49
  "Return exactly one JSON object, with no prose outside JSON:\n"
49
- '{"assignments":[{"worker":"<worker-name>","task":"<specific sub-task>"}]}\n'
50
+ '{"assignments":[{"stage":1,"worker":"<worker-name>","task":"<specific sub-task>"}]}\n'
50
51
  f"{note}\n"
51
52
  f"User task:\n{task}\n\n"
52
53
  f"Available workers, excluding you:\n{worker_lines}\n\n"
53
- f"Rules:\n- Use at most {roster.max_workers} assignments.\n"
54
+ f"Rules:\n- Use at most {roster.max_workers} assignments per stage.\n"
55
+ "- Stage must be a positive integer starting at stage 1.\n"
56
+ "- Assignments in the same stage run in parallel; later stages receive earlier-stage worker results.\n"
57
+ "- Omit stage only for backwards-compatible stage 1 assignments.\n"
54
58
  "- Assign only listed workers.\n"
55
59
  "- Use zero assignments only if no worker is useful."
56
60
  f"{policy}"
@@ -147,7 +151,10 @@ def write_run_handoff(
147
151
  or "- no workers dispatched"
148
152
  )
149
153
  assignment_summary = (
150
- "\n".join(f"- {assignment.worker}: {_one_line(assignment.task)}" for assignment in assignments)
154
+ "\n".join(
155
+ f"- stage {assignment.stage} -> {assignment.worker}: {_one_line(assignment.task)}"
156
+ for assignment in assignments
157
+ )
151
158
  or "- no worker assignments"
152
159
  )
153
160
  artifact_line = f"- artifacts: `{output_dir}`" if output_dir is not None else "- artifacts: none"
@@ -232,30 +239,38 @@ def parse_plan(text: str, roster: Roster) -> list[Assignment]:
232
239
  raw_assignments = payload.get("assignments")
233
240
  if not isinstance(raw_assignments, list):
234
241
  raise ValueError("plan JSON needs an assignments list")
235
- if len(raw_assignments) > roster.max_workers:
236
- raise ValueError(f"plan has {len(raw_assignments)} assignments, limit is {roster.max_workers}")
237
242
 
238
243
  assignments: list[Assignment] = []
239
- seen: set[tuple[str, str]] = set()
244
+ seen: set[tuple[int, str, str]] = set()
245
+ stage_counts: dict[int, int] = {}
240
246
  for item in raw_assignments:
241
247
  if not isinstance(item, dict):
242
248
  raise ValueError("each assignment must be an object")
243
- worker = item.get("worker")
249
+ stage = item.get("stage", 1)
250
+ if isinstance(stage, bool) or not isinstance(stage, int) or stage < 1:
251
+ raise ValueError("assignment.stage must be a positive integer")
252
+ raw_worker = item.get("worker")
244
253
  subtask = item.get("task")
245
- if not isinstance(worker, str) or not worker.strip():
254
+ if not isinstance(raw_worker, str) or not raw_worker.strip():
246
255
  raise ValueError("assignment.worker must be a non-empty string")
256
+ worker = raw_worker.strip()
247
257
  if worker not in roster.agents:
248
258
  raise ValueError(f"assignment references unknown worker: {worker!r}")
249
259
  if worker == roster.orchestrator:
250
260
  raise ValueError("assignment cannot target the orchestrator")
251
261
  if not isinstance(subtask, str) or not subtask.strip():
252
262
  raise ValueError("assignment.task must be a non-empty string")
253
- assignment = Assignment(worker=worker.strip(), task=subtask.strip())
254
- key = (assignment.worker, assignment.task)
263
+ assignment = Assignment(worker=worker, task=subtask.strip(), stage=stage)
264
+ key = (assignment.stage, assignment.worker, assignment.task)
255
265
  if key not in seen:
256
266
  assignments.append(assignment)
257
267
  seen.add(key)
258
- return assignments
268
+ stage_counts[assignment.stage] = stage_counts.get(assignment.stage, 0) + 1
269
+
270
+ for stage, count in stage_counts.items():
271
+ if count > roster.max_workers:
272
+ raise ValueError(f"plan has {count} assignments in stage {stage}, limit is {roster.max_workers}")
273
+ return sorted(assignments, key=lambda assignment: assignment.stage)
259
274
 
260
275
 
261
276
  def _record_plan_attempt(
@@ -302,6 +317,8 @@ def _run_orchestrator(
302
317
  }
303
318
  if sandbox is not None:
304
319
  kwargs["sandbox"] = sandbox
320
+ if orchestrator.model is not None:
321
+ kwargs["model"] = orchestrator.model
305
322
  return agents.run_agent(orchestrator.cli, prompt, **kwargs)
306
323
 
307
324
 
@@ -356,13 +373,39 @@ def plan(
356
373
  raise RuntimeError(f"orchestrator returned an invalid plan: {second_exc}") from second_exc
357
374
 
358
375
 
359
- def _worker_prompt(agent: Agent, assignment: Assignment, read_only: bool = False) -> str:
376
+ def _render_prior_results(results: list[WorkerResult]) -> str:
377
+ return "\n\n".join(
378
+ "\n".join(
379
+ [
380
+ f"Worker: {result.worker}",
381
+ f"Sub-task: {result.task}",
382
+ f"Status: {'ok' if result.ok else 'failed'}",
383
+ f"Detail: {result.detail}" if result.detail else "Detail:",
384
+ "Output:",
385
+ result.text or "(no output)",
386
+ ]
387
+ )
388
+ for result in results
389
+ )
390
+
391
+
392
+ def _worker_prompt(
393
+ agent: Agent,
394
+ assignment: Assignment,
395
+ *,
396
+ prior_results: list[WorkerResult] | None = None,
397
+ read_only: bool = False,
398
+ ) -> str:
399
+ prior_context = ""
400
+ if prior_results:
401
+ prior_context = f"\n\nEarlier-stage context:\n{_render_prior_results(prior_results)}"
360
402
  policy = f"\n\n{_read_only_rules()}" if read_only else ""
361
403
  return (
362
404
  f"You are Brigade worker {agent.name}.\n"
363
405
  f"Role:\n{agent.role}\n\n"
364
406
  f"Sub-task:\n{assignment.task}\n\n"
365
407
  "Return a concise, complete result for the orchestrator to synthesize."
408
+ f"{prior_context}"
366
409
  f"{policy}"
367
410
  )
368
411
 
@@ -375,7 +418,7 @@ def dispatch(
375
418
  sandbox_read_only: bool | None = None,
376
419
  sandbox: str | None = None,
377
420
  ) -> list[WorkerResult]:
378
- def run_one(assignment: Assignment) -> WorkerResult:
421
+ def run_one(assignment: Assignment, prior_results: list[WorkerResult]) -> WorkerResult:
379
422
  agent = roster.agents[assignment.worker]
380
423
  if not is_cli_allowed(agent.cli, roster):
381
424
  return WorkerResult(
@@ -392,7 +435,13 @@ def dispatch(
392
435
  }
393
436
  if sandbox is not None:
394
437
  kwargs["sandbox"] = sandbox
395
- result = agents.run_agent(agent.cli, _worker_prompt(agent, assignment, read_only=read_only), **kwargs)
438
+ if agent.model is not None:
439
+ kwargs["model"] = agent.model
440
+ result = agents.run_agent(
441
+ agent.cli,
442
+ _worker_prompt(agent, assignment, prior_results=prior_results, read_only=read_only),
443
+ **kwargs,
444
+ )
396
445
  return WorkerResult(
397
446
  worker=assignment.worker,
398
447
  task=assignment.task,
@@ -404,25 +453,34 @@ def dispatch(
404
453
  if not assignments:
405
454
  return []
406
455
 
407
- results_by_index: dict[int, WorkerResult] = {}
408
- max_workers = min(roster.max_workers, len(assignments))
409
- with ThreadPoolExecutor(max_workers=max_workers) as executor:
410
- future_to_index = {executor.submit(run_one, assignment): index for index, assignment in enumerate(assignments)}
411
- for future in as_completed(future_to_index):
412
- index = future_to_index[future]
413
- try:
414
- results_by_index[index] = future.result()
415
- except Exception as exc: # pragma: no cover - defensive boundary
416
- assignment = assignments[index]
417
- results_by_index[index] = WorkerResult(
418
- worker=assignment.worker,
419
- task=assignment.task,
420
- text="",
421
- ok=False,
422
- detail=str(exc)[:200],
423
- )
424
-
425
- return [results_by_index[index] for index in range(len(assignments))]
456
+ all_results: list[WorkerResult] = []
457
+ stages = sorted({assignment.stage for assignment in assignments})
458
+ for stage in stages:
459
+ stage_assignments = [assignment for assignment in assignments if assignment.stage == stage]
460
+ stage_results_by_index: dict[int, WorkerResult] = {}
461
+ prior_results = list(all_results)
462
+ max_workers = min(roster.max_workers, len(stage_assignments))
463
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
464
+ future_to_index = {
465
+ executor.submit(run_one, assignment, prior_results): index
466
+ for index, assignment in enumerate(stage_assignments)
467
+ }
468
+ for future in as_completed(future_to_index):
469
+ index = future_to_index[future]
470
+ try:
471
+ stage_results_by_index[index] = future.result()
472
+ except Exception as exc: # pragma: no cover - defensive boundary
473
+ assignment = stage_assignments[index]
474
+ stage_results_by_index[index] = WorkerResult(
475
+ worker=assignment.worker,
476
+ task=assignment.task,
477
+ text="",
478
+ ok=False,
479
+ detail=str(exc)[:200],
480
+ )
481
+ all_results.extend(stage_results_by_index[index] for index in range(len(stage_assignments)))
482
+
483
+ return all_results
426
484
 
427
485
 
428
486
  def build_synth_prompt(task: str, results: list[WorkerResult], read_only: bool = False) -> str:
@@ -458,8 +516,16 @@ def _print_plan(assignments: list[Assignment]) -> None:
458
516
  if not assignments:
459
517
  print(" (no worker assignments)")
460
518
  return
461
- for assignment in assignments:
462
- print(f" -> {assignment.worker}: {assignment.task}")
519
+ stages = sorted({assignment.stage for assignment in assignments})
520
+ if len(stages) == 1:
521
+ for assignment in assignments:
522
+ print(f" -> {assignment.worker}: {assignment.task}")
523
+ return
524
+ for stage in stages:
525
+ print(f" stage {stage}:")
526
+ for assignment in assignments:
527
+ if assignment.stage == stage:
528
+ print(f" -> {assignment.worker}: {assignment.task}")
463
529
 
464
530
 
465
531
  def _print_worker_status(results: list[WorkerResult]) -> None:
@@ -473,8 +539,10 @@ def _print_worker_status(results: list[WorkerResult]) -> None:
473
539
  print(f" [{marker}] {result.worker}{detail}")
474
540
 
475
541
 
476
- def _assignment_payload(assignments: list[Assignment]) -> list[dict[str, str]]:
477
- return [{"worker": assignment.worker, "task": assignment.task} for assignment in assignments]
542
+ def _assignment_payload(assignments: list[Assignment]) -> list[dict[str, object]]:
543
+ return [
544
+ {"stage": assignment.stage, "worker": assignment.worker, "task": assignment.task} for assignment in assignments
545
+ ]
478
546
 
479
547
 
480
548
  def _worker_payload(results: list[WorkerResult]) -> list[dict[str, object]]:
@@ -504,9 +572,11 @@ def _roster_payload(roster: Roster) -> dict[str, object]:
504
572
  "max_workers": roster.max_workers,
505
573
  "timeout_seconds": roster.timeout_seconds,
506
574
  "allow_models": list(roster.allow_models),
575
+ "sandbox": roster.sandbox,
507
576
  "agents": {
508
577
  name: {
509
578
  "cli": agent.cli,
579
+ "model": agent.model,
510
580
  "role": agent.role,
511
581
  "timeout_seconds": agent.timeout_seconds,
512
582
  }