brindle 0.3.6__tar.gz → 0.3.8__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.
- {brindle-0.3.6 → brindle-0.3.8}/PKG-INFO +50 -31
- {brindle-0.3.6 → brindle-0.3.8}/README.md +49 -30
- {brindle-0.3.6 → brindle-0.3.8}/pyproject.toml +1 -1
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/agents.py +39 -9
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/builtin_agents/supervisor.md +2 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/ci_client.py +14 -2
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/cli.py +26 -36
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/company_identity.py +83 -34
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/company_login.py +37 -10
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/cull.py +5 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/events.py +22 -3
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/launch_health.py +16 -3
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pane_auth.py +3 -3
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pro/account.py +1 -1
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pro/audit_chain.py +4 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pro/audit_export.py +3 -3
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pro/managed_models.py +184 -26
- brindle-0.3.8/src/brindle/pro/route_health.py +249 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pro/team_events.py +48 -14
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pro/team_policy.py +126 -27
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/quota.py +118 -10
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/sessions.py +2 -1
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/watch.py +1 -1
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/watch_costs.py +34 -55
- {brindle-0.3.6 → brindle-0.3.8}/.gitignore +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/LICENSE +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/SCHEDULE-A +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/__init__.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/__main__.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/account.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/airgap.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/antigravity.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/autopilot.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/budget.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/builtin_agents/__init__.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/builtin_agents/backend-auditor.md +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/builtin_agents/developer-codex.md +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/builtin_agents/developer-fable.md +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/builtin_agents/developer-gemini.md +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/builtin_agents/developer-heavy.md +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/builtin_agents/developer-light.md +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/builtin_agents/developer-local.md +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/builtin_agents/developer.md +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/builtin_agents/qa.md +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/builtin_agents/reviewer-codex.md +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/builtin_agents/reviewer-gemini.md +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/builtin_agents/reviewer-local.md +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/builtin_agents/reviewer.md +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/builtin_agents/subagent.md +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/builtin_rules/__init__.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/builtin_rules/security/backend.md +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/builtin_rules/style/minimal-diff.md +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/builtin_rules/tests/only.md +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/ci_adapters.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/ci_federation.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/ci_hosts.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/codemap.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/codex_hook.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/config.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/conflicts.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/cost.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/cost_estimate.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/db.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/demo.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/detect.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/doctor.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/escapes.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/gates.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/git.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/guardrails.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/history.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/identity_lock.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/inbox.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/keys_cmds.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/keystore.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/learned_rules.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/learning.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/legacy.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/mcp_server.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/model_access.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/model_availability.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/native/__init__.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/native/client.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/native/loop.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/native/permissions.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/native/runner.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/native/serve.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/native/tools.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/permissions.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pipeline.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/plugins.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/policy.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pool.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pricing.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pro/__init__.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pro/_files.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pro/auth.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pro/brief_learning.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pro/cost_centers.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pro/credentials.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pro/features.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pro/keys.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pro/learning.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pro/license.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pro/loopback.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pro/org_profiles.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pro/orgkey.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pro/rollout.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pro/settings_sync.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/pro/status.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/procs.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/profiles.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/providers.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/repo_cmds.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/repos.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/rewind.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/rule_checks.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/savings.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/scratch.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/secrets.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/self_serve.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/services.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/sidebar_follow.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/signin_pause.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/status_cache.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/tasks.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/tmux.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/usage.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/view.py +0 -0
- {brindle-0.3.6 → brindle-0.3.8}/src/brindle/workspaces.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: brindle
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.8
|
|
4
4
|
Summary: Run coding agents in parallel, each on its own git branch, with a supervisor that delegates, reviews, and merges
|
|
5
5
|
Project-URL: Homepage, https://pawdelta.com/brindle/
|
|
6
6
|
Project-URL: Documentation, https://pawdelta.com/brindle/docs/recommended-use
|
|
@@ -89,7 +89,7 @@ uv tool install --editable ~/Projects/brindle # or from a local checkout
|
|
|
89
89
|
brindle drives Claude Code, so you need that too (`npm install -g @anthropic-ai/claude-code`).
|
|
90
90
|
|
|
91
91
|
`brindle --version` prints the installed version; the tmux status bar of every brindle
|
|
92
|
-
session shows it too (`brindle 0.3.
|
|
92
|
+
session shows it too (`brindle 0.3.8`). A session started before an upgrade keeps
|
|
93
93
|
running the old code, and shows the old number, until you restart it.
|
|
94
94
|
|
|
95
95
|
## Quick start
|
|
@@ -118,11 +118,11 @@ a writable home) and, in a repo, what's set up for it, and says what to do about
|
|
|
118
118
|
anything missing. Bare `brindle` runs the essential ones (tmux, the chat's CLI)
|
|
119
119
|
before it launches anything.
|
|
120
120
|
|
|
121
|
-
That's it.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
That's it. `brindle` opens a supervisor chat (Claude Code) in your repo, with a
|
|
122
|
+
narrow sidebar on the left showing every agent: who's working, who's idle, and
|
|
123
|
+
who's waiting for your approval. Tell the supervisor what you want. It splits the
|
|
124
|
+
work between workers, each on its own branch, then reviews and merges their
|
|
125
|
+
branches. It starts in under a second.
|
|
126
126
|
|
|
127
127
|
**The sidebar follows you.** There's one sidebar pane per session root, not
|
|
128
128
|
one per window: switch to any other brindle window or session (⏎ in the
|
|
@@ -200,7 +200,7 @@ it from. When the work belongs in a real repository, run `brindle transfer ~/pat
|
|
|
200
200
|
merge. Running `brindle` in a repo also offers to bring in any scratch work that hasn't
|
|
201
201
|
been moved yet.
|
|
202
202
|
|
|
203
|
-
Or
|
|
203
|
+
Or give a supervisor its own workspace:
|
|
204
204
|
|
|
205
205
|
```sh
|
|
206
206
|
brindle new fix-login -p "Fix the login redirect bug; add a test"
|
|
@@ -242,7 +242,7 @@ for mechanical edits and a Codex reviewer for a second model's opinion.
|
|
|
242
242
|
|
|
243
243
|
## Autopilot
|
|
244
244
|
|
|
245
|
-
`brindle
|
|
245
|
+
`brindle` starts the supervisor with autopilot on. Tell it what we're building,
|
|
246
246
|
or put the goal in `.brindle/goals.md`, and it works like a project manager:
|
|
247
247
|
|
|
248
248
|
1. **Goal and milestones.** The goal is split into milestones, and each one has
|
|
@@ -337,14 +337,14 @@ your own status line prints, so what you see doesn't change.
|
|
|
337
337
|
|
|
338
338
|
| | |
|
|
339
339
|
|---|---|
|
|
340
|
-
| `brindle` | a fresh
|
|
341
|
-
| `brindle supervisor` |
|
|
340
|
+
| `brindle` | a fresh supervisor chat here, dashboard alongside |
|
|
341
|
+
| `brindle supervisor` | the same, with `--branch`/`--worktree` to run it in another worktree |
|
|
342
342
|
| `brindle init` | detect setup and test commands, write `.brindle/config.json`, check tools |
|
|
343
343
|
| `brindle demo [--local]` | watch brindle finish a tiny practice repo: parallel workers, reviews, gated merges |
|
|
344
|
-
| `brindle new BRANCH [-b BASE] [-
|
|
344
|
+
| `brindle new BRANCH [-b BASE] [-p PROMPT] [--no-agent]` | worktree + branch + a supervisor in it |
|
|
345
345
|
| `brindle continue [ID]` / `brindle -c` | resume a paused session (default: the most recent) |
|
|
346
346
|
| `brindle sessions` / `brindle prune` | list paused sessions / apply retention rules, remove merged or missing worktrees, close stale stopped agents whose work is merged or gone, and remove leftover tmux sessions |
|
|
347
|
-
| `brindle start [-
|
|
347
|
+
| `brindle start [-p PROMPT] [--no-watch] [--no-autopilot] [-b BRANCH] [-w PATH]` | the same, with options; `-b`/`-w` run it in that branch's worktree (created if needed, or the one you already made), which gets the repo's `.brindle` config |
|
|
348
348
|
| `brindle handover --to BRANCH\|PATH [-n NOTE]` | hand the session to a new supervisor there: goal and milestones, workers, queued tasks and your note move across; the old one is paused |
|
|
349
349
|
| `brindle autopilot [on\|off\|check]` | the goal's progress; turn autopilot on or off; run the checks now |
|
|
350
350
|
| `brindle audit-decision FINDING [-r RATIONALE]` | record acceptance of an audit finding with rationale so subsequent audits respect it |
|
|
@@ -378,7 +378,7 @@ your own status line prints, so what you see doesn't change.
|
|
|
378
378
|
| `brindle doctor` | check that brindle has what it needs (tmux, the agent CLIs, native profiles' model endpoints, a writable home) and, in a repo, its config, checks and code map |
|
|
379
379
|
| `brindle close AGENT` / `brindle close --exited` | hide an agent (or every stopped one) from the dashboard, stopping it if it's running; its worktree and branch stay |
|
|
380
380
|
| `brindle send AGENT MSG` | message an agent; waits in its inbox until it's idle |
|
|
381
|
-
| `brindle agent
|
|
381
|
+
| `brindle agent kill/peek/profiles` | manage agents (only a supervisor starts them) |
|
|
382
382
|
| `brindle profile new/lint/show` | write a profile file, check every profile (extends chains, rule packs, patterns), or print one as brindle resolves it |
|
|
383
383
|
| `brindle rules suggest/accept/reject` | (brindle Pro) rules suggested from review findings that keep recurring; accept one into `.brindle/rules/learned.md`, or reject it for good |
|
|
384
384
|
| `brindle agent turns AGENT` / `brindle agent rewind AGENT --to N [--profile P] [--note "..."]` | a worker's worktree is snapshotted after every turn that changed files; rewind puts it back as it was after turn N and starts a fresh session there, briefed on the task, turns 1..N and your note (any provider, Codex included). Supervisors have the `agent_turns` and `rewind_agent` tools |
|
|
@@ -871,7 +871,7 @@ how to use them, and the next step to get the rest:
|
|
|
871
871
|
| per-member budgets, pause, and admin messages (sidebar "Messages", the bottom bar, `brindle org messages`) | Team | `brindle org messages [--keep]` lists and marks read the notices your admins sent you; a pause refuses new workers and stops running ones |
|
|
872
872
|
| audit log, air-gap | Enterprise | `brindle audit verify`, `"airgap": true` |
|
|
873
873
|
| audit export | Enterprise | `"audit_export"` in `~/.brindle/config.json`, `brindle audit ship` |
|
|
874
|
-
| managed models | Enterprise | `
|
|
874
|
+
| managed models | Enterprise | `agent_setup` routes with `enforce`, and `deny_personal_keys`, in your org policy (see "Managed models" below) |
|
|
875
875
|
| wider CI, cost centers, managed rollout | Enterprise | `brindle ci init --host gitlab`, `brindle cost request`, and `min_version`, required packs and the kill switch in your org policy; admins can also shut down or throttle a member, role or org remotely (it arrives within about a minute) |
|
|
876
876
|
|
|
877
877
|
`brindle ci init` checks your plan before it changes anything: it refuses
|
|
@@ -981,20 +981,40 @@ file names, paths or branch names. See `src/brindle/pro/learning.py` and
|
|
|
981
981
|
|
|
982
982
|
### Managed models (brindle Enterprise)
|
|
983
983
|
|
|
984
|
-
An org policy can pick the
|
|
985
|
-
model keys out of their panes.
|
|
984
|
+
An org policy can pick the route its workers run on and keep people's own
|
|
985
|
+
model keys out of their panes. All of this is ignored without the Enterprise
|
|
986
986
|
entitlement.
|
|
987
987
|
|
|
988
|
-
*
|
|
989
|
-
`
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
`
|
|
997
|
-
|
|
988
|
+
* The routes live in the policy's `agent_setup` block (the same one `brindle
|
|
989
|
+
login` applies, see "Using your company's access"), one per agent. With
|
|
990
|
+
`enforce` on, an agent whose route points at a cloud or an endpoint of the
|
|
991
|
+
org's is managed: Claude Code on `bedrock` (`aws.region`; the SSO group
|
|
992
|
+
`sso_start_url`, `sso_region`, `account_id` and `role_name` is optional, all
|
|
993
|
+
four or none, and without it your own AWS sign-in applies), `vertex`
|
|
994
|
+
(`gcp.project`, `gcp.region`), `foundry` (`azure.resource` with its
|
|
995
|
+
`subscription_id`, or `azure.base_url`, the endpoint) or `gateway`
|
|
996
|
+
(`gateway.base_url`), with `model_ids` (the ids the route serves) and
|
|
997
|
+
`default_model` (what panes start on, `ANTHROPIC_MODEL`); Codex on `azure`
|
|
998
|
+
or `gateway` with `codex.base_url` (`OPENAI_BASE_URL`). The route's
|
|
999
|
+
variables are the top layer of every pane's environment, a profile that
|
|
1000
|
+
points somewhere else is refused with a message saying why, and the
|
|
1001
|
+
identity is checked at launch. With `exclusive` on as well, an agent the
|
|
1002
|
+
block gives no route at all is refused (Claude Code or Codex with no entry,
|
|
1003
|
+
and always native and Antigravity); without it such an agent runs
|
|
1004
|
+
unmanaged (use `allowed_providers` to keep it out). Without `enforce` the
|
|
1005
|
+
same block is sign-in help only.
|
|
1006
|
+
* `provider_config` is the older, org-wide form of the same thing: `provider`
|
|
1007
|
+
(`bedrock`, `vertex`, `azure` or `openai-compatible`), plus `region`,
|
|
1008
|
+
`model_ids` and `endpoint` as the provider needs them (for `vertex`,
|
|
1009
|
+
`project`; an older policy that put the project id in `endpoint` still
|
|
1010
|
+
works). The server now converts it into `agent_setup` routes (with
|
|
1011
|
+
`exclusive` on) and keeps deriving it for older clients; this client reads
|
|
1012
|
+
it whenever `agent_setup` has no `exclusive` field (a server older than
|
|
1013
|
+
routing in `agent_setup`) or routes nothing. An old
|
|
1014
|
+
`provider_config` and its `agent_setup` form give panes the same variables.
|
|
1015
|
+
* `deny_personal_keys` (in `agent_setup`, or the policy's top-level field):
|
|
1016
|
+
the personal model keys (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, ...) are
|
|
1017
|
+
taken out of every agent pane, and a profile that sets one is refused.
|
|
998
1018
|
* An org-owned key: with `provider` `anthropic` (or `openai-compatible`), set
|
|
999
1019
|
`key_env` to the name of a variable that holds the org's key (not a personal
|
|
1000
1020
|
one like `ANTHROPIC_API_KEY`), or `key_helper` to a command, as a list of
|
|
@@ -1013,10 +1033,9 @@ Two things to know when you turn on `deny_personal_keys`:
|
|
|
1013
1033
|
`api_key_env` to the name of a variable that holds the org's key (not
|
|
1014
1034
|
`OPENAI_API_KEY`).
|
|
1015
1035
|
* It can't block a Claude subscription or keychain login by itself: Claude
|
|
1016
|
-
Code signs in on its own, outside the variables brindle removes. Only
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
`provider_config`.
|
|
1036
|
+
Code signs in on its own, outside the variables brindle removes. Only an
|
|
1037
|
+
enforced route that takes Claude Code to Bedrock, Vertex or Foundry keeps
|
|
1038
|
+
it off that login. Always pair `deny_personal_keys` with one.
|
|
1020
1039
|
|
|
1021
1040
|
### Air-gapped mode (brindle Enterprise)
|
|
1022
1041
|
|
|
@@ -59,7 +59,7 @@ uv tool install --editable ~/Projects/brindle # or from a local checkout
|
|
|
59
59
|
brindle drives Claude Code, so you need that too (`npm install -g @anthropic-ai/claude-code`).
|
|
60
60
|
|
|
61
61
|
`brindle --version` prints the installed version; the tmux status bar of every brindle
|
|
62
|
-
session shows it too (`brindle 0.3.
|
|
62
|
+
session shows it too (`brindle 0.3.8`). A session started before an upgrade keeps
|
|
63
63
|
running the old code, and shows the old number, until you restart it.
|
|
64
64
|
|
|
65
65
|
## Quick start
|
|
@@ -88,11 +88,11 @@ a writable home) and, in a repo, what's set up for it, and says what to do about
|
|
|
88
88
|
anything missing. Bare `brindle` runs the essential ones (tmux, the chat's CLI)
|
|
89
89
|
before it launches anything.
|
|
90
90
|
|
|
91
|
-
That's it.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
That's it. `brindle` opens a supervisor chat (Claude Code) in your repo, with a
|
|
92
|
+
narrow sidebar on the left showing every agent: who's working, who's idle, and
|
|
93
|
+
who's waiting for your approval. Tell the supervisor what you want. It splits the
|
|
94
|
+
work between workers, each on its own branch, then reviews and merges their
|
|
95
|
+
branches. It starts in under a second.
|
|
96
96
|
|
|
97
97
|
**The sidebar follows you.** There's one sidebar pane per session root, not
|
|
98
98
|
one per window: switch to any other brindle window or session (⏎ in the
|
|
@@ -170,7 +170,7 @@ it from. When the work belongs in a real repository, run `brindle transfer ~/pat
|
|
|
170
170
|
merge. Running `brindle` in a repo also offers to bring in any scratch work that hasn't
|
|
171
171
|
been moved yet.
|
|
172
172
|
|
|
173
|
-
Or
|
|
173
|
+
Or give a supervisor its own workspace:
|
|
174
174
|
|
|
175
175
|
```sh
|
|
176
176
|
brindle new fix-login -p "Fix the login redirect bug; add a test"
|
|
@@ -212,7 +212,7 @@ for mechanical edits and a Codex reviewer for a second model's opinion.
|
|
|
212
212
|
|
|
213
213
|
## Autopilot
|
|
214
214
|
|
|
215
|
-
`brindle
|
|
215
|
+
`brindle` starts the supervisor with autopilot on. Tell it what we're building,
|
|
216
216
|
or put the goal in `.brindle/goals.md`, and it works like a project manager:
|
|
217
217
|
|
|
218
218
|
1. **Goal and milestones.** The goal is split into milestones, and each one has
|
|
@@ -307,14 +307,14 @@ your own status line prints, so what you see doesn't change.
|
|
|
307
307
|
|
|
308
308
|
| | |
|
|
309
309
|
|---|---|
|
|
310
|
-
| `brindle` | a fresh
|
|
311
|
-
| `brindle supervisor` |
|
|
310
|
+
| `brindle` | a fresh supervisor chat here, dashboard alongside |
|
|
311
|
+
| `brindle supervisor` | the same, with `--branch`/`--worktree` to run it in another worktree |
|
|
312
312
|
| `brindle init` | detect setup and test commands, write `.brindle/config.json`, check tools |
|
|
313
313
|
| `brindle demo [--local]` | watch brindle finish a tiny practice repo: parallel workers, reviews, gated merges |
|
|
314
|
-
| `brindle new BRANCH [-b BASE] [-
|
|
314
|
+
| `brindle new BRANCH [-b BASE] [-p PROMPT] [--no-agent]` | worktree + branch + a supervisor in it |
|
|
315
315
|
| `brindle continue [ID]` / `brindle -c` | resume a paused session (default: the most recent) |
|
|
316
316
|
| `brindle sessions` / `brindle prune` | list paused sessions / apply retention rules, remove merged or missing worktrees, close stale stopped agents whose work is merged or gone, and remove leftover tmux sessions |
|
|
317
|
-
| `brindle start [-
|
|
317
|
+
| `brindle start [-p PROMPT] [--no-watch] [--no-autopilot] [-b BRANCH] [-w PATH]` | the same, with options; `-b`/`-w` run it in that branch's worktree (created if needed, or the one you already made), which gets the repo's `.brindle` config |
|
|
318
318
|
| `brindle handover --to BRANCH\|PATH [-n NOTE]` | hand the session to a new supervisor there: goal and milestones, workers, queued tasks and your note move across; the old one is paused |
|
|
319
319
|
| `brindle autopilot [on\|off\|check]` | the goal's progress; turn autopilot on or off; run the checks now |
|
|
320
320
|
| `brindle audit-decision FINDING [-r RATIONALE]` | record acceptance of an audit finding with rationale so subsequent audits respect it |
|
|
@@ -348,7 +348,7 @@ your own status line prints, so what you see doesn't change.
|
|
|
348
348
|
| `brindle doctor` | check that brindle has what it needs (tmux, the agent CLIs, native profiles' model endpoints, a writable home) and, in a repo, its config, checks and code map |
|
|
349
349
|
| `brindle close AGENT` / `brindle close --exited` | hide an agent (or every stopped one) from the dashboard, stopping it if it's running; its worktree and branch stay |
|
|
350
350
|
| `brindle send AGENT MSG` | message an agent; waits in its inbox until it's idle |
|
|
351
|
-
| `brindle agent
|
|
351
|
+
| `brindle agent kill/peek/profiles` | manage agents (only a supervisor starts them) |
|
|
352
352
|
| `brindle profile new/lint/show` | write a profile file, check every profile (extends chains, rule packs, patterns), or print one as brindle resolves it |
|
|
353
353
|
| `brindle rules suggest/accept/reject` | (brindle Pro) rules suggested from review findings that keep recurring; accept one into `.brindle/rules/learned.md`, or reject it for good |
|
|
354
354
|
| `brindle agent turns AGENT` / `brindle agent rewind AGENT --to N [--profile P] [--note "..."]` | a worker's worktree is snapshotted after every turn that changed files; rewind puts it back as it was after turn N and starts a fresh session there, briefed on the task, turns 1..N and your note (any provider, Codex included). Supervisors have the `agent_turns` and `rewind_agent` tools |
|
|
@@ -841,7 +841,7 @@ how to use them, and the next step to get the rest:
|
|
|
841
841
|
| per-member budgets, pause, and admin messages (sidebar "Messages", the bottom bar, `brindle org messages`) | Team | `brindle org messages [--keep]` lists and marks read the notices your admins sent you; a pause refuses new workers and stops running ones |
|
|
842
842
|
| audit log, air-gap | Enterprise | `brindle audit verify`, `"airgap": true` |
|
|
843
843
|
| audit export | Enterprise | `"audit_export"` in `~/.brindle/config.json`, `brindle audit ship` |
|
|
844
|
-
| managed models | Enterprise | `
|
|
844
|
+
| managed models | Enterprise | `agent_setup` routes with `enforce`, and `deny_personal_keys`, in your org policy (see "Managed models" below) |
|
|
845
845
|
| wider CI, cost centers, managed rollout | Enterprise | `brindle ci init --host gitlab`, `brindle cost request`, and `min_version`, required packs and the kill switch in your org policy; admins can also shut down or throttle a member, role or org remotely (it arrives within about a minute) |
|
|
846
846
|
|
|
847
847
|
`brindle ci init` checks your plan before it changes anything: it refuses
|
|
@@ -951,20 +951,40 @@ file names, paths or branch names. See `src/brindle/pro/learning.py` and
|
|
|
951
951
|
|
|
952
952
|
### Managed models (brindle Enterprise)
|
|
953
953
|
|
|
954
|
-
An org policy can pick the
|
|
955
|
-
model keys out of their panes.
|
|
954
|
+
An org policy can pick the route its workers run on and keep people's own
|
|
955
|
+
model keys out of their panes. All of this is ignored without the Enterprise
|
|
956
956
|
entitlement.
|
|
957
957
|
|
|
958
|
-
*
|
|
959
|
-
`
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
`
|
|
967
|
-
|
|
958
|
+
* The routes live in the policy's `agent_setup` block (the same one `brindle
|
|
959
|
+
login` applies, see "Using your company's access"), one per agent. With
|
|
960
|
+
`enforce` on, an agent whose route points at a cloud or an endpoint of the
|
|
961
|
+
org's is managed: Claude Code on `bedrock` (`aws.region`; the SSO group
|
|
962
|
+
`sso_start_url`, `sso_region`, `account_id` and `role_name` is optional, all
|
|
963
|
+
four or none, and without it your own AWS sign-in applies), `vertex`
|
|
964
|
+
(`gcp.project`, `gcp.region`), `foundry` (`azure.resource` with its
|
|
965
|
+
`subscription_id`, or `azure.base_url`, the endpoint) or `gateway`
|
|
966
|
+
(`gateway.base_url`), with `model_ids` (the ids the route serves) and
|
|
967
|
+
`default_model` (what panes start on, `ANTHROPIC_MODEL`); Codex on `azure`
|
|
968
|
+
or `gateway` with `codex.base_url` (`OPENAI_BASE_URL`). The route's
|
|
969
|
+
variables are the top layer of every pane's environment, a profile that
|
|
970
|
+
points somewhere else is refused with a message saying why, and the
|
|
971
|
+
identity is checked at launch. With `exclusive` on as well, an agent the
|
|
972
|
+
block gives no route at all is refused (Claude Code or Codex with no entry,
|
|
973
|
+
and always native and Antigravity); without it such an agent runs
|
|
974
|
+
unmanaged (use `allowed_providers` to keep it out). Without `enforce` the
|
|
975
|
+
same block is sign-in help only.
|
|
976
|
+
* `provider_config` is the older, org-wide form of the same thing: `provider`
|
|
977
|
+
(`bedrock`, `vertex`, `azure` or `openai-compatible`), plus `region`,
|
|
978
|
+
`model_ids` and `endpoint` as the provider needs them (for `vertex`,
|
|
979
|
+
`project`; an older policy that put the project id in `endpoint` still
|
|
980
|
+
works). The server now converts it into `agent_setup` routes (with
|
|
981
|
+
`exclusive` on) and keeps deriving it for older clients; this client reads
|
|
982
|
+
it whenever `agent_setup` has no `exclusive` field (a server older than
|
|
983
|
+
routing in `agent_setup`) or routes nothing. An old
|
|
984
|
+
`provider_config` and its `agent_setup` form give panes the same variables.
|
|
985
|
+
* `deny_personal_keys` (in `agent_setup`, or the policy's top-level field):
|
|
986
|
+
the personal model keys (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, ...) are
|
|
987
|
+
taken out of every agent pane, and a profile that sets one is refused.
|
|
968
988
|
* An org-owned key: with `provider` `anthropic` (or `openai-compatible`), set
|
|
969
989
|
`key_env` to the name of a variable that holds the org's key (not a personal
|
|
970
990
|
one like `ANTHROPIC_API_KEY`), or `key_helper` to a command, as a list of
|
|
@@ -983,10 +1003,9 @@ Two things to know when you turn on `deny_personal_keys`:
|
|
|
983
1003
|
`api_key_env` to the name of a variable that holds the org's key (not
|
|
984
1004
|
`OPENAI_API_KEY`).
|
|
985
1005
|
* It can't block a Claude subscription or keychain login by itself: Claude
|
|
986
|
-
Code signs in on its own, outside the variables brindle removes. Only
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
`provider_config`.
|
|
1006
|
+
Code signs in on its own, outside the variables brindle removes. Only an
|
|
1007
|
+
enforced route that takes Claude Code to Bedrock, Vertex or Foundry keeps
|
|
1008
|
+
it off that login. Always pair `deny_personal_keys` with one.
|
|
990
1009
|
|
|
991
1010
|
### Air-gapped mode (brindle Enterprise)
|
|
992
1011
|
|
|
@@ -24,6 +24,7 @@ from brindle import company_identity, company_login, git, keystore, pane_auth, p
|
|
|
24
24
|
from brindle.config import RepoConfig
|
|
25
25
|
from brindle.db import DB, Agent, Workspace
|
|
26
26
|
from brindle.model_availability import ModelAvailabilityChecker, PROVISIONING_TIMEOUT, provisioning_context
|
|
27
|
+
from brindle.pro import route_health
|
|
27
28
|
from brindle.profiles import load_profile, missing_add_dirs
|
|
28
29
|
from brindle.providers import LaunchContext, get_provider, signed_out, unusable
|
|
29
30
|
from brindle.sidebar_follow import (
|
|
@@ -92,6 +93,9 @@ When you have finished:
|
|
|
92
93
|
"""
|
|
93
94
|
|
|
94
95
|
|
|
96
|
+
SUPERVISOR = "supervisor" # the only profile a launch starts
|
|
97
|
+
|
|
98
|
+
|
|
95
99
|
class AgentError(RuntimeError):
|
|
96
100
|
pass
|
|
97
101
|
|
|
@@ -182,11 +186,12 @@ def agent_env(ws: Workspace, agent_id: str, agent: Agent | None = None,
|
|
|
182
186
|
if m is _UNREAD:
|
|
183
187
|
m = managed(ws.repo_root)
|
|
184
188
|
# Claude Code's own managed settings (apiKeyHelper, a managed env) pick
|
|
185
|
-
# the credential and backend: brindle adds no
|
|
189
|
+
# the credential and backend: brindle adds no managed route env there.
|
|
186
190
|
org = agent.provider == "claude" and providers.claude_org_managed()
|
|
187
|
-
# The company's Claude Code env (`brindle login
|
|
188
|
-
# profile's env lines and the
|
|
189
|
-
#
|
|
191
|
+
# The company's Claude Code env (`brindle login`, the org's agent_setup
|
|
192
|
+
# without `enforce`): the lowest layer, so a profile's env lines and the
|
|
193
|
+
# org's managed route (an enforced agent_setup, or provider_config) both
|
|
194
|
+
# beat it; none at all under Claude Code's managed settings (stored_env).
|
|
190
195
|
company = {} if agent.provider != "claude" else company_login.stored_env()
|
|
191
196
|
managed_env = {} if org else managed_models.agent_env(m, agent.provider)
|
|
192
197
|
enforced = None if agent.provider != "claude" or org else company_identity.enforced_route_env()
|
|
@@ -236,7 +241,8 @@ def agent_env(ws: Workspace, agent_id: str, agent: Agent | None = None,
|
|
|
236
241
|
env = {**company, **recovery_keys, **profile_env, **managed_env, **env}
|
|
237
242
|
else:
|
|
238
243
|
# An enforced company identity (Enterprise): the pane runs on the route
|
|
239
|
-
# launch_problem checked, whatever a profile's env lines say.
|
|
244
|
+
# launch_problem checked, whatever a profile's env lines say. The route
|
|
245
|
+
# env is the top layer (managed_env is the same route's, from managed_models).
|
|
240
246
|
profile_env = {k: v for k, v in profile_env.items() if company_identity.profile_may_set(k)}
|
|
241
247
|
env = {**recovery_keys, **profile_env, **managed_env, **enforced, **env}
|
|
242
248
|
if agent is not None and preload_tools(agent, ws):
|
|
@@ -528,9 +534,10 @@ def _window_environment(agent: Agent, ws: Workspace, m=_UNREAD):
|
|
|
528
534
|
raise AgentError(f"managed models: {e}") from None
|
|
529
535
|
# An enforced company identity: also every account picker the tmux server
|
|
530
536
|
# would hand the pane or `brindle keys` holds for it (pane_auth covers this process's).
|
|
531
|
-
route = None if agent.provider != "claude" or org_managed else company_identity.
|
|
537
|
+
route = None if agent.provider != "claude" or org_managed else company_identity.enforced_route()
|
|
532
538
|
if route is not None:
|
|
533
|
-
extra = company_identity.enforced_deny(route, tmux.inherited_names(ws.tmux_session), credentials
|
|
539
|
+
extra = company_identity.enforced_deny(route[0], tmux.inherited_names(ws.tmux_session), credentials,
|
|
540
|
+
own=route[1])
|
|
534
541
|
deny = (*deny, *(n for n in extra if n not in deny))
|
|
535
542
|
# Claude Code's managed settings supply the credential: no stored Claude key.
|
|
536
543
|
no_stored = set(deny) | (set(providers._ENV_AUTH["claude"]) if org_managed else set())
|
|
@@ -566,6 +573,9 @@ def model_problem(profile_name: str, ws: Workspace, *, agent: Agent | None = Non
|
|
|
566
573
|
profile = guardrails.effective(load_profile(profile_name, ws.repo_root))
|
|
567
574
|
why = managed_models.refusal(m, profile) if m is not None else None
|
|
568
575
|
if why:
|
|
576
|
+
route_health.record(ws.repo_root, agent=profile.provider,
|
|
577
|
+
route=route_health.route_of(profile.provider, managed_models.agent_env(m, profile.provider), m),
|
|
578
|
+
model=profile.model, outcome="policy_refused")
|
|
569
579
|
return f"managed models: {why}"
|
|
570
580
|
if m is not None:
|
|
571
581
|
profile = managed_models.apply_profile(m, profile)
|
|
@@ -583,7 +593,24 @@ def model_problem(profile_name: str, ws: Workspace, *, agent: Agent | None = Non
|
|
|
583
593
|
account_identity=account_revision(profile.provider, effective),
|
|
584
594
|
catalog_context=provisioning_context(profile.provider, ws.path, effective),
|
|
585
595
|
timeout=PROVISIONING_TIMEOUT)
|
|
586
|
-
|
|
596
|
+
if result.status != "unavailable":
|
|
597
|
+
return None
|
|
598
|
+
route_health.record(ws.repo_root, agent=profile.provider,
|
|
599
|
+
route=route_health.route_of(profile.provider, effective, m),
|
|
600
|
+
model=profile.model, outcome="model_unavailable")
|
|
601
|
+
return result.explanation
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def route_context(db: DB, agent: Agent, ws: Workspace) -> tuple[str, str | None]:
|
|
605
|
+
"""(the route ``agent``'s calls take, its model) for a route-health event
|
|
606
|
+
about a run; (``"unknown"``, None) when they can't be read."""
|
|
607
|
+
try:
|
|
608
|
+
m = managed(ws.repo_root)
|
|
609
|
+
profile = _profile_for(db, agent, ws, m)
|
|
610
|
+
env = _effective_window_env(agent, ws, m)
|
|
611
|
+
return route_health.route_of(agent.provider, env, m), profile.model
|
|
612
|
+
except Exception: # noqa: BLE001 - a report about a run never fails over its context
|
|
613
|
+
return "unknown", None
|
|
587
614
|
|
|
588
615
|
|
|
589
616
|
_model_checker = ModelAvailabilityChecker()
|
|
@@ -741,7 +768,10 @@ def _launch(db: DB, agent: Agent, ws: Workspace, *, prompt: str | None,
|
|
|
741
768
|
return
|
|
742
769
|
# An org that enforces its company sign-in (Enterprise) refuses a
|
|
743
770
|
# different identity; one that can't be told never blocks.
|
|
744
|
-
why = company_identity.
|
|
771
|
+
why, outcome, route = company_identity.launch_check(provider.name)
|
|
772
|
+
if outcome:
|
|
773
|
+
route_health.record(ws.repo_root, agent=provider.name, route=route, model=profile.model,
|
|
774
|
+
outcome=outcome)
|
|
745
775
|
if why:
|
|
746
776
|
raise AgentError(why)
|
|
747
777
|
from brindle import signin_pause
|
|
@@ -13,6 +13,8 @@ How to work:
|
|
|
13
13
|
report.
|
|
14
14
|
- Break the request into independent, well-scoped tasks. Tasks that touch the
|
|
15
15
|
same files should go to one worker, or run one after another.
|
|
16
|
+
- Delegate to developers and other worker profiles. Start another supervisor
|
|
17
|
+
only when a piece of work is big enough to need its own workers, which is rare.
|
|
16
18
|
- Delegate with the brindle MCP tools. `assign` runs workers in parallel (their
|
|
17
19
|
results arrive later as messages). `handoff` waits for a single result.
|
|
18
20
|
Leave `isolate` on: each worker gets its own git worktree and branch cut
|
|
@@ -1995,14 +1995,26 @@ def _set_cloud(repo: str, cloud: str, values: Mapping[str, str], *, run=subproce
|
|
|
1995
1995
|
|
|
1996
1996
|
|
|
1997
1997
|
def managed_cloud(cwd: str) -> tuple[str, dict[str, str]] | None:
|
|
1998
|
-
"""On Enterprise, the cloud the org policy
|
|
1999
|
-
|
|
1998
|
+
"""On Enterprise, the cloud the org policy routes Claude to (its enforced
|
|
1999
|
+
``agent_setup`` route, else its ``provider_config``: bedrock, vertex or
|
|
2000
|
+
azure) and the region/project to prefill; else None."""
|
|
2000
2001
|
from brindle.pro import managed_models
|
|
2001
2002
|
|
|
2002
2003
|
try:
|
|
2003
2004
|
m = managed_models.current(cwd)
|
|
2004
2005
|
except Exception: # noqa: BLE001 - not managed, or unreadable: no default
|
|
2005
2006
|
return None
|
|
2007
|
+
claude = m.claude if m else None
|
|
2008
|
+
if claude is not None:
|
|
2009
|
+
cloud = {"bedrock": BEDROCK, "vertex": VERTEX, "foundry": FOUNDRY}.get(claude.route)
|
|
2010
|
+
if not cloud:
|
|
2011
|
+
return None
|
|
2012
|
+
if cloud == BEDROCK:
|
|
2013
|
+
return cloud, {"AWS_REGION": claude.aws.region}
|
|
2014
|
+
if cloud == VERTEX:
|
|
2015
|
+
return cloud, {"CLOUD_ML_REGION": claude.gcp.region,
|
|
2016
|
+
"ANTHROPIC_VERTEX_PROJECT_ID": claude.gcp.project}
|
|
2017
|
+
return cloud, {}
|
|
2006
2018
|
cfg = m.config if m else None
|
|
2007
2019
|
cloud = POLICY_CLOUDS.get(cfg.provider) if cfg else None
|
|
2008
2020
|
if not cloud:
|
|
@@ -36,8 +36,8 @@ class _App(typer.Typer):
|
|
|
36
36
|
|
|
37
37
|
app = _App(add_completion=False, help="""brindle: run coding agents in parallel, each on its own git branch.
|
|
38
38
|
|
|
39
|
-
Run `brindle` with no arguments to open (or reopen) a
|
|
40
|
-
|
|
39
|
+
Run `brindle` with no arguments to open (or reopen) a supervisor chat here, with
|
|
40
|
+
the live dashboard underneath. Outside a git repo it starts a scratch session;
|
|
41
41
|
`brindle transfer <repo>` moves that work into a real repository later.
|
|
42
42
|
|
|
43
43
|
Paid features (hosted learning, per-worktree services, team policies, CI):
|
|
@@ -157,7 +157,7 @@ def init(
|
|
|
157
157
|
typer.echo(f"optional, not set up: {', '.join(optional)} (`brindle doctor` says how)")
|
|
158
158
|
if any(c.level == doctor_mod.FAIL for c in results):
|
|
159
159
|
raise typer.Exit(1)
|
|
160
|
-
typer.secho("Ready. Commit .brindle/config.json, then run `brindle`
|
|
160
|
+
typer.secho("Ready. Commit .brindle/config.json, then run `brindle` and tell the supervisor what to build.",
|
|
161
161
|
fg="green")
|
|
162
162
|
|
|
163
163
|
|
|
@@ -176,7 +176,7 @@ def demo(
|
|
|
176
176
|
root = demo_mod.create(local=local)
|
|
177
177
|
typer.echo(f"demo repo: {root}")
|
|
178
178
|
os.chdir(root)
|
|
179
|
-
start(
|
|
179
|
+
start(prompt=None, provider=None, attach=attach, watch=True,
|
|
180
180
|
autopilot=True, branch=None, worktree=None)
|
|
181
181
|
|
|
182
182
|
|
|
@@ -185,14 +185,19 @@ def new(
|
|
|
185
185
|
branch: Optional[str] = typer.Argument(None, help="Branch for the workspace (created if needed). Required unless --pr is given."),
|
|
186
186
|
base: Optional[str] = typer.Option(None, "--base", "-b", help="Base branch (default: repo default). Not allowed with --pr."),
|
|
187
187
|
pr: Optional[int] = typer.Option(None, "--pr", help="Check out this GitHub PR's head branch (via `gh`), based on the PR's base branch. Don't also pass BRANCH or --base; the head branch is always fetched."),
|
|
188
|
-
|
|
189
|
-
prompt: Optional[str] = typer.Option(None, "--prompt", "-p", help="First message for the
|
|
190
|
-
provider: Optional[str] = typer.Option(None, help="
|
|
188
|
+
no_agent: bool = typer.Option(False, "--no-agent", help="Only create the worktree; start no supervisor."),
|
|
189
|
+
prompt: Optional[str] = typer.Option(None, "--prompt", "-p", help="First message for the supervisor."),
|
|
190
|
+
provider: Optional[str] = typer.Option(None, help="Run the supervisor on this CLI instead of Claude Code (codex, antigravity)."),
|
|
191
191
|
no_fetch: bool = typer.Option(False, "--no-fetch", help="Don't fetch the base branch first."),
|
|
192
192
|
no_setup: bool = typer.Option(False, "--no-setup", help="Skip setup commands."),
|
|
193
193
|
attach: bool = typer.Option(False, "--attach", help="Attach to the tmux session afterward."),
|
|
194
194
|
) -> None:
|
|
195
|
-
"""Create a worktree on a new branch and start
|
|
195
|
+
"""Create a worktree on a new branch and start a supervisor in it."""
|
|
196
|
+
from brindle.providers import NOT_SUPERVISOR, SUPERVISOR_PROVIDERS
|
|
197
|
+
|
|
198
|
+
if provider and provider in NOT_SUPERVISOR:
|
|
199
|
+
_fail(f"the {provider} provider can't run the supervisor; use one of: "
|
|
200
|
+
f"{', '.join(SUPERVISOR_PROVIDERS)}")
|
|
196
201
|
if pr is not None:
|
|
197
202
|
if branch:
|
|
198
203
|
_fail("pass either BRANCH or --pr, not both (--pr uses the PR's head branch)")
|
|
@@ -224,12 +229,15 @@ def new(
|
|
|
224
229
|
|
|
225
230
|
from brindle.config import load_repo_config
|
|
226
231
|
|
|
227
|
-
profile = agent or load_repo_config(ws.repo_root).default_agent
|
|
228
232
|
window = None
|
|
229
|
-
if
|
|
230
|
-
|
|
233
|
+
if not no_agent:
|
|
234
|
+
autopilot = load_repo_config(ws.repo_root).autopilot
|
|
235
|
+
a = _run(agents.spawn, db, ws, agents.SUPERVISOR, prompt=prompt, provider_name=provider,
|
|
236
|
+
autopilot=autopilot)
|
|
231
237
|
window = a.tmux_window
|
|
232
238
|
typer.echo(f" agent {a.id} ({a.profile}/{a.provider})")
|
|
239
|
+
if autopilot:
|
|
240
|
+
_say_autopilot(db, a.id)
|
|
233
241
|
typer.echo(f"\n brindle attach {ws.name}")
|
|
234
242
|
if attach:
|
|
235
243
|
_attach(ws, window)
|
|
@@ -237,7 +245,6 @@ def new(
|
|
|
237
245
|
|
|
238
246
|
@app.command()
|
|
239
247
|
def start(
|
|
240
|
-
agent: Optional[str] = typer.Option(None, "--agent", "-a", help="Agent profile (default: repo default_agent, developer)."),
|
|
241
248
|
prompt: Optional[str] = typer.Option(None, "--prompt", "-p"),
|
|
242
249
|
provider: Optional[str] = typer.Option(None),
|
|
243
250
|
attach: bool = typer.Option(True, "--attach/--no-attach"),
|
|
@@ -246,7 +253,7 @@ def start(
|
|
|
246
253
|
branch: Optional[str] = typer.Option(None, "--branch", "-b", help="Run in the worktree for this branch, creating the branch and worktree if needed."),
|
|
247
254
|
worktree: Optional[str] = typer.Option(None, "--worktree", "-w", help="Run in the worktree at this path (created with --branch if it doesn't exist)."),
|
|
248
255
|
) -> None:
|
|
249
|
-
"""Start a fresh chat with
|
|
256
|
+
"""Start a fresh supervisor chat here, with the dashboard alongside. A launch only ever starts a supervisor; it starts the other agents itself.
|
|
250
257
|
|
|
251
258
|
If a session is still running here, you choose: open it, start the new
|
|
252
259
|
one in its own worktree (both run at once), or pause it and start fresh.
|
|
@@ -284,23 +291,20 @@ def start(
|
|
|
284
291
|
except Exception:
|
|
285
292
|
pass
|
|
286
293
|
|
|
287
|
-
if agent is None:
|
|
288
|
-
agent = _run(load_repo_config, ws.repo_root).default_agent
|
|
289
|
-
|
|
290
294
|
from brindle.providers import NOT_SUPERVISOR, SUPERVISOR_PROVIDERS
|
|
291
295
|
|
|
292
296
|
if provider and provider in NOT_SUPERVISOR:
|
|
293
297
|
_fail(f"the {provider} provider can't run the supervisor; use one of: "
|
|
294
298
|
f"{', '.join(SUPERVISOR_PROVIDERS)}")
|
|
295
|
-
_preflight(
|
|
299
|
+
_preflight(agents.SUPERVISOR, provider, ws.repo_root)
|
|
296
300
|
_trust_codex_hook(ws.repo_root)
|
|
297
301
|
# Nothing slow before the chat starts: the paused session's leftover
|
|
298
302
|
# processes, old paused sessions' worktrees and the pool refill are all
|
|
299
303
|
# handled by the detached cull.
|
|
300
304
|
_pause_running(db, ws, stop_procs=False)
|
|
301
305
|
if autopilot is None:
|
|
302
|
-
autopilot =
|
|
303
|
-
a = _run(agents.spawn, db, ws,
|
|
306
|
+
autopilot = _run(load_repo_config, ws.repo_root).autopilot
|
|
307
|
+
a = _run(agents.spawn, db, ws, agents.SUPERVISOR, prompt=prompt, provider_name=provider,
|
|
304
308
|
watch_pane=watch, background_setup=True, autopilot=autopilot)
|
|
305
309
|
# After the chat's window exists and is recorded: the cull's session
|
|
306
310
|
# retention closes dropped sessions' windows by their stored pane ids,
|
|
@@ -718,7 +722,7 @@ def default(
|
|
|
718
722
|
provider: Optional[str] = typer.Option(None, "--provider", help="Run the supervisor on this CLI instead of Claude Code (codex, antigravity)."),
|
|
719
723
|
show_version: bool = typer.Option(False, "--version", help="Print brindle's version and exit."),
|
|
720
724
|
) -> None:
|
|
721
|
-
"""Bare `brindle`: a fresh
|
|
725
|
+
"""Bare `brindle`: a fresh supervisor chat here (a scratch session outside git)."""
|
|
722
726
|
if show_version:
|
|
723
727
|
from brindle import __version__
|
|
724
728
|
|
|
@@ -735,7 +739,7 @@ def default(
|
|
|
735
739
|
if cont:
|
|
736
740
|
continue_cmd(session_id=None, attach=True)
|
|
737
741
|
else:
|
|
738
|
-
start(
|
|
742
|
+
start(prompt=None, provider=provider, attach=True, watch=True,
|
|
739
743
|
autopilot=autopilot, branch=None, worktree=None)
|
|
740
744
|
|
|
741
745
|
|
|
@@ -750,7 +754,7 @@ def supervisor(
|
|
|
750
754
|
worktree: Optional[str] = typer.Option(None, "--worktree", "-w", help="Run in the worktree at this path (created with --branch if it doesn't exist)."),
|
|
751
755
|
) -> None:
|
|
752
756
|
"""Start an explicit supervisor session to coordinate coding agents."""
|
|
753
|
-
start(
|
|
757
|
+
start(prompt=prompt, provider=provider, attach=attach, watch=watch,
|
|
754
758
|
autopilot=autopilot, branch=branch, worktree=worktree)
|
|
755
759
|
|
|
756
760
|
|
|
@@ -2203,20 +2207,6 @@ def rm(
|
|
|
2203
2207
|
# -- agents -----------------------------------------------------------------
|
|
2204
2208
|
|
|
2205
2209
|
|
|
2206
|
-
@agent_app.command("spawn")
|
|
2207
|
-
def agent_spawn(
|
|
2208
|
-
profile: str,
|
|
2209
|
-
workspace: Optional[str] = typer.Option(None, "--workspace", "-w"),
|
|
2210
|
-
prompt: Optional[str] = typer.Option(None, "--prompt", "-p"),
|
|
2211
|
-
provider: Optional[str] = typer.Option(None),
|
|
2212
|
-
) -> None:
|
|
2213
|
-
"""Start another agent in an existing workspace."""
|
|
2214
|
-
db = DB()
|
|
2215
|
-
ws = _ws(db, workspace)
|
|
2216
|
-
a = _run(agents.spawn, db, ws, profile, prompt=prompt, provider_name=provider)
|
|
2217
|
-
typer.echo(f"✓ {a.id} ({a.profile}/{a.provider}) in {ws.id}")
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
2210
|
@agent_app.command("profiles")
|
|
2221
2211
|
def agent_profiles() -> None:
|
|
2222
2212
|
"""List available agent profiles."""
|