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.
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/PKG-INFO +31 -14
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/QUICKSTART.md +30 -9
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/README.md +29 -12
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/pyproject.toml +7 -7
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/__init__.py +1 -1
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/aboyeur.py +107 -37
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/agents.py +79 -6
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/center_cmd.py +3 -2
- brigade_cli-0.12.0/src/brigade/cli/__init__.py +149 -0
- brigade_cli-0.12.0/src/brigade/cli/_common.py +59 -0
- brigade_cli-0.12.0/src/brigade/cli/add.py +20 -0
- brigade_cli-0.12.0/src/brigade/cli/budgets.py +30 -0
- brigade_cli-0.12.0/src/brigade/cli/center.py +326 -0
- brigade_cli-0.12.0/src/brigade/cli/chat.py +91 -0
- brigade_cli-0.12.0/src/brigade/cli/completions.py +17 -0
- brigade_cli-0.12.0/src/brigade/cli/context.py +87 -0
- brigade_cli-0.12.0/src/brigade/cli/daily.py +225 -0
- brigade_cli-0.12.0/src/brigade/cli/doctor.py +25 -0
- brigade_cli-0.12.0/src/brigade/cli/dogfood.py +90 -0
- brigade_cli-0.12.0/src/brigade/cli/friction.py +94 -0
- brigade_cli-0.12.0/src/brigade/cli/handoff.py +390 -0
- brigade_cli-0.12.0/src/brigade/cli/handoff_template.py +25 -0
- brigade_cli-0.12.0/src/brigade/cli/hermes_fragments.py +19 -0
- brigade_cli-0.12.0/src/brigade/cli/ingest.py +35 -0
- brigade_cli-0.12.0/src/brigade/cli/init.py +112 -0
- brigade_cli-0.12.0/src/brigade/cli/learn.py +189 -0
- brigade_cli-0.12.0/src/brigade/cli/memory.py +128 -0
- brigade_cli-0.12.0/src/brigade/cli/notifications.py +106 -0
- brigade_cli-0.12.0/src/brigade/cli/openclaw_fragments.py +19 -0
- brigade_cli-0.12.0/src/brigade/cli/operator.py +419 -0
- brigade_cli-0.12.0/src/brigade/cli/pantry.py +90 -0
- brigade_cli-0.12.0/src/brigade/cli/projects.py +127 -0
- brigade_cli-0.12.0/src/brigade/cli/reconfigure.py +46 -0
- brigade_cli-0.12.0/src/brigade/cli/release.py +289 -0
- brigade_cli-0.12.0/src/brigade/cli/repos.py +867 -0
- brigade_cli-0.12.0/src/brigade/cli/research.py +183 -0
- brigade_cli-0.12.0/src/brigade/cli/roadmap.py +58 -0
- brigade_cli-0.12.0/src/brigade/cli/roster.py +47 -0
- brigade_cli-0.12.0/src/brigade/cli/run.py +238 -0
- brigade_cli-0.12.0/src/brigade/cli/runbook.py +90 -0
- brigade_cli-0.12.0/src/brigade/cli/runs.py +56 -0
- brigade_cli-0.12.0/src/brigade/cli/scrub.py +35 -0
- brigade_cli-0.12.0/src/brigade/cli/security.py +231 -0
- brigade_cli-0.12.0/src/brigade/cli/skills.py +280 -0
- brigade_cli-0.12.0/src/brigade/cli/status.py +20 -0
- brigade_cli-0.12.0/src/brigade/cli/tools.py +494 -0
- brigade_cli-0.12.0/src/brigade/cli/untrusted.py +47 -0
- brigade_cli-0.12.0/src/brigade/cli/work.py +2044 -0
- brigade_cli-0.12.0/src/brigade/completions.py +91 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/doctor.py +120 -27
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/dogfood_cmd.py +4 -34
- brigade_cli-0.12.0/src/brigade/friction_cmd.py +615 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/install.py +55 -8
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/learn_cmd.py +3 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/localio.py +60 -2
- brigade_cli-0.12.0/src/brigade/mcp_server.py +96 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/memory_cmd.py +201 -2
- brigade_cli-0.12.0/src/brigade/operator_cmd/__init__.py +95 -0
- brigade_cli-0.12.0/src/brigade/operator_cmd/adoption.py +407 -0
- brigade_cli-0.12.0/src/brigade/operator_cmd/guide.py +190 -0
- brigade_cli-0.12.0/src/brigade/operator_cmd/health.py +528 -0
- brigade_cli-0.12.0/src/brigade/operator_cmd/lifecycle.py +705 -0
- brigade_cli-0.12.0/src/brigade/operator_cmd/migration.py +589 -0
- brigade_cli-0.12.0/src/brigade/operator_cmd/surfaces.py +1045 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/proc.py +1 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/projects_cmd.py +15 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/prompt.py +6 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/repos_cmd.py +78 -2
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/llm.py +6 -5
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research_cmd.py +3 -1
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/roster.py +14 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/roster_cmd.py +18 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/runbook_cmd.py +51 -3
- brigade_cli-0.12.0/src/brigade/runguard.py +192 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/scrub.py +63 -3
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/security_cmd.py +112 -5
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/selection.py +6 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/skills_cmd.py +58 -82
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/status.py +25 -4
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/depth/workspace.json +1 -1
- brigade_cli-0.12.0/src/brigade/templates/harnesses/amp.json +11 -0
- brigade_cli-0.12.0/src/brigade/templates/harnesses/crush.json +11 -0
- brigade_cli-0.12.0/src/brigade/templates/harnesses/grok.json +11 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/hermes/memory-handoff.harness.json +1 -1
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/hermes/model-lanes.harness.json +1 -1
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/hermes/workspace.harness.json +1 -1
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/hooks/pre-push +15 -1
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/chat-memory-sweep.example.json +1 -1
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/memory-care.example.json +1 -1
- brigade_cli-0.12.0/src/brigade/templates/openhands/memory-handoffs/TEMPLATE.md +64 -0
- brigade_cli-0.12.0/src/brigade/templates/pi/memory-handoffs/TEMPLATE.md +64 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/policies/personal.json +1 -1
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/policies/public-content.json +1 -1
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/policies/public-repo.json +1 -1
- brigade_cli-0.12.0/src/brigade/templates/qwen/memory-handoffs/TEMPLATE.md +64 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/AGENTS.md +9 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/toml_compat.py +9 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/tools_cmd.py +31 -5
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/work_cmd/__init__.py +108 -80
- brigade_cli-0.12.0/src/brigade/work_cmd/backup.py +174 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/work_cmd/config.py +4 -5
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/work_cmd/helpers.py +17 -19
- brigade_cli-0.12.0/src/brigade/work_cmd/imports.py +934 -0
- brigade_cli-0.12.0/src/brigade/work_cmd/reviews.py +1328 -0
- brigade_cli-0.12.0/src/brigade/work_cmd/scanners.py +1178 -0
- brigade_cli-0.12.0/src/brigade/work_cmd/services.py +1105 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/work_cmd/session.py +20 -23
- brigade_cli-0.12.0/src/brigade/work_cmd/sweeps.py +771 -0
- brigade_cli-0.12.0/src/brigade/work_cmd/verification.py +724 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/PKG-INFO +31 -14
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/SOURCES.txt +88 -4
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_aboyeur.py +177 -0
- brigade_cli-0.12.0/tests/test_actionqueue.py +143 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_agents.py +76 -0
- brigade_cli-0.12.0/tests/test_completions.py +46 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_doctor.py +104 -1
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_dogfood_cmd.py +3 -2
- brigade_cli-0.12.0/tests/test_friction_cmd.py +117 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_ingest.py +71 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_init.py +56 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_install.py +3 -1
- brigade_cli-0.12.0/tests/test_localio.py +38 -0
- brigade_cli-0.12.0/tests/test_mcp_server.py +60 -0
- brigade_cli-0.12.0/tests/test_memory_search_mcp.py +89 -0
- brigade_cli-0.12.0/tests/test_operator_checkup.py +72 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_operator_cmd.py +4 -4
- brigade_cli-0.12.0/tests/test_parity_backlog.py +92 -0
- brigade_cli-0.12.0/tests/test_pre_push_hook.py +30 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_prompt.py +4 -4
- brigade_cli-0.12.0/tests/test_read_only_enforcement.py +47 -0
- brigade_cli-0.12.0/tests/test_reportstore.py +150 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_repos_cmd.py +41 -1
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_cmd.py +20 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_llm.py +15 -1
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_roster.py +43 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_roster_cmd.py +7 -0
- brigade_cli-0.12.0/tests/test_run_cli.py +688 -0
- brigade_cli-0.12.0/tests/test_runbook_import.py +48 -0
- brigade_cli-0.12.0/tests/test_runguard.py +141 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_security_cmd.py +2 -2
- brigade_cli-0.12.0/tests/test_security_diff.py +62 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_selection.py +19 -1
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_skills_cmd.py +3 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_status.py +17 -0
- brigade_cli-0.12.0/tests/test_subprocess_guards.py +51 -0
- brigade_cli-0.12.0/tests/test_work_cmd_backup.py +448 -0
- brigade_cli-0.12.0/tests/test_work_cmd_imports.py +3170 -0
- brigade_cli-0.12.0/tests/test_work_cmd_ledger.py +1055 -0
- brigade_cli-0.12.0/tests/test_work_cmd_reviews.py +727 -0
- brigade_cli-0.12.0/tests/test_work_cmd_scanners.py +719 -0
- brigade_cli-0.12.0/tests/test_work_cmd_services.py +2699 -0
- brigade_cli-0.12.0/tests/test_work_cmd_session.py +1699 -0
- brigade_cli-0.12.0/tests/test_work_cmd_sweeps.py +699 -0
- brigade_cli-0.12.0/tests/test_work_cmd_verification.py +259 -0
- brigade_cli-0.10.3/src/brigade/cli.py +0 -7063
- brigade_cli-0.10.3/src/brigade/operator_cmd.py +0 -3317
- brigade_cli-0.10.3/src/brigade/work_cmd/services.py +0 -6127
- brigade_cli-0.10.3/tests/test_run_cli.py +0 -244
- brigade_cli-0.10.3/tests/test_work_cmd.py +0 -11652
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/LICENSE +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/MANIFEST.in +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/setup.cfg +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/__main__.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/actionqueue.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/add.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/budgets.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/budgets_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/chat_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/config.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/context_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/daily_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/fragments.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/handoff.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/handoff_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/hermes_adapter.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/ingest.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/managed.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/notifications_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/pantry_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/phases_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/py.typed +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/reconfigure.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/registry.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/release_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/reportstore.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/__init__.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/config.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/engine.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/extract.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/handoff.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/registry.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/report.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/sources/__init__.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/sources/cli.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/sources/local.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/sources/web.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/research/types.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/roadmap_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/runs_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/station.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/adal/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/aider/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.10.3/src/brigade/templates/antigravity → brigade_cli-0.12.0/src/brigade/templates/amp}/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.10.3/src/brigade/templates/claude → brigade_cli-0.12.0/src/brigade/templates/antigravity}/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.10.3/src/brigade/templates/codex → brigade_cli-0.12.0/src/brigade/templates/claude}/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.10.3/src/brigade/templates/continue → brigade_cli-0.12.0/src/brigade/templates/codex}/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.10.3/src/brigade/templates/copilot → brigade_cli-0.12.0/src/brigade/templates/continue}/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.10.3/src/brigade/templates/cursor → brigade_cli-0.12.0/src/brigade/templates/copilot}/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.10.3/src/brigade/templates/goose → brigade_cli-0.12.0/src/brigade/templates/crush}/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.10.3/src/brigade/templates/hermes → brigade_cli-0.12.0/src/brigade/templates/cursor}/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/depth/repo.json +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/generic/harness-adapter-checklist.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/generic/memory-contract.md +0 -0
- {brigade_cli-0.10.3/src/brigade/templates/kimi → brigade_cli-0.12.0/src/brigade/templates/goose}/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.10.3/src/brigade/templates/opencode → brigade_cli-0.12.0/src/brigade/templates/grok}/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/handoff/handoff-sources.example.json +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/handoff/openclaw-ingest-receipt.example.json +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/adal.json +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/aider.json +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/antigravity.json +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/claude.json +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/codex.json +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/continue.json +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/copilot.json +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/cursor.json +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/goose.json +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/hermes.json +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/kimi.json +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/openclaw.json +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/opencode.json +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/openhands.json +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/pi.json +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/harnesses/qwen.json +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/hermes/README.md +0 -0
- {brigade_cli-0.10.3/src/brigade/templates/openhands → brigade_cli-0.12.0/src/brigade/templates/hermes}/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/includes/publisher.json +0 -0
- {brigade_cli-0.10.3/src/brigade/templates/pi → brigade_cli-0.12.0/src/brigade/templates/kimi}/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/backup-restic.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/chat-surface-crawlers.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/content-safety.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/handoff-flow.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/memory-architecture.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/memory-care-staleness.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/memory-scanner.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/multi-workspace-handoff-admin.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/obsidian-notes.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/pipeline-standups.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/memory/cards/tokenjuice-output-compaction.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/openclaw/README.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/openclaw/acp-escalation.openclaw.json +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/openclaw/memory-sweep-cron.openclaw.json +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/openclaw/model-aliases.openclaw.json +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/openclaw/ollama-memory-search.openclaw.json +0 -0
- {brigade_cli-0.10.3/src/brigade/templates/qwen → brigade_cli-0.12.0/src/brigade/templates/opencode}/memory-handoffs/TEMPLATE.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/scripts/backup-restic.sh +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/skills/note/SKILL.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/CLAUDE.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/HEARTBEAT.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/IDENTITY.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/INSTALL_FOR_AGENTS.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/MEMORY.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/SAFETY_RULES.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/SOUL.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/TOOLS.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/USER.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/rules/acceptance-driven-work.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates/workspace/rules/issue-tdd-loop.md +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/templates.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/untrusted.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/untrusted_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/work_cmd/constants.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade/work_cmd/ledger.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/dependency_links.txt +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/entry_points.txt +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/requires.txt +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/src/brigade_cli.egg-info/top_level.txt +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_add.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_budgets.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_cli_alias.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_cli_help.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_config.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_fragments.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_gitignore.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_handoff.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_handoff_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_managed.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_memory_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_neutrality.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_notifications_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_pantry_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase100_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase101_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase165_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase36_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase37_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase38_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase39_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase40_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase41_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase42_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase43_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase44_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase45_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase46_50_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase51_55_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase56_60_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase96_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase97_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase98_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_phase99_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_privacy_regression.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_proc.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_reconfigure.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_registry.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_release_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_cli_sources.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_config.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_engine.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_extract.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_handoff.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_local_sources.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_registry.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_report.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_types.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_research_web.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_roadmap_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_runbook_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_runs_cmd.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_scrub.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_station.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_toml_compat.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_untrusted.py +0 -0
- {brigade_cli-0.10.3 → brigade_cli-0.12.0}/tests/test_untrusted_cmd.py +0 -0
- {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.
|
|
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 <
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
66
|
-
3. safe targeted notes
|
|
67
|
-
4. ambiguous or risky
|
|
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 ·
|
|
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 -->
|
|
79
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
47
|
-
[ ] 16.
|
|
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
|
|
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
|
|
85
|
+
brigade operator quickstart --target ./my-project --depth repo --harnesses codex
|
|
71
86
|
|
|
72
|
-
#
|
|
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,
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
36
|
-
3. safe targeted notes
|
|
37
|
-
4. ambiguous or risky
|
|
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 ·
|
|
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 -->
|
|
49
|
-
|
|
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
|
|
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.
|
|
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 = "
|
|
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
|
|
57
|
-
#
|
|
58
|
-
# import sorting (I), pyupgrade (UP),
|
|
59
|
-
#
|
|
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"]
|
|
@@ -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(
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
assignment
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
-
|
|
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,
|
|
477
|
-
return [
|
|
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
|
}
|