brindle 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. {brindle-0.2.0 → brindle-0.3.0}/.gitignore +1 -0
  2. {brindle-0.2.0 → brindle-0.3.0}/PKG-INFO +52 -11
  3. {brindle-0.2.0 → brindle-0.3.0}/README.md +51 -10
  4. {brindle-0.2.0 → brindle-0.3.0}/pyproject.toml +3 -1
  5. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/agents.py +205 -128
  6. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/autopilot.py +77 -14
  7. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/budget.py +46 -5
  8. brindle-0.3.0/src/brindle/builtin_agents/developer-codex.md +6 -0
  9. brindle-0.3.0/src/brindle/builtin_agents/developer-heavy.md +7 -0
  10. brindle-0.3.0/src/brindle/builtin_agents/developer-light.md +7 -0
  11. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/builtin_agents/developer.md +4 -3
  12. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/builtin_agents/reviewer-codex.md +4 -9
  13. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/builtin_agents/reviewer-local.md +3 -6
  14. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/builtin_agents/reviewer.md +4 -9
  15. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/ci_adapters.py +83 -11
  16. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/ci_client.py +368 -13
  17. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/ci_federation.py +250 -0
  18. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/ci_hosts.py +65 -6
  19. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/cli.py +129 -14
  20. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/config.py +32 -4
  21. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/cost.py +149 -1
  22. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/cull.py +61 -0
  23. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/db.py +45 -3
  24. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/doctor.py +80 -0
  25. brindle-0.3.0/src/brindle/escapes.py +176 -0
  26. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/gates.py +82 -20
  27. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/history.py +2 -1
  28. brindle-0.3.0/src/brindle/keys_cmds.py +103 -0
  29. brindle-0.2.0/src/brindle/pro/credentials.py → brindle-0.3.0/src/brindle/keystore.py +145 -39
  30. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/learning.py +31 -6
  31. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/mcp_server.py +47 -28
  32. brindle-0.3.0/src/brindle/model_access.py +293 -0
  33. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/native/runner.py +4 -1
  34. brindle-0.3.0/src/brindle/pane_auth.py +56 -0
  35. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/pipeline.py +111 -11
  36. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/pool.py +15 -11
  37. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/pricing.py +2 -1
  38. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/pro/account.py +68 -8
  39. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/pro/auth.py +29 -6
  40. brindle-0.3.0/src/brindle/pro/brief_learning.py +164 -0
  41. brindle-0.3.0/src/brindle/pro/credentials.py +53 -0
  42. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/pro/learning.py +71 -6
  43. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/pro/managed_models.py +53 -3
  44. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/pro/org_profiles.py +33 -0
  45. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/pro/team_policy.py +58 -5
  46. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/procs.py +23 -0
  47. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/profiles.py +33 -2
  48. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/providers.py +162 -9
  49. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/quota.py +14 -0
  50. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/secrets.py +54 -8
  51. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/watch_costs.py +7 -2
  52. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/workspaces.py +27 -7
  53. brindle-0.2.0/src/brindle/builtin_agents/developer-codex.md +0 -21
  54. brindle-0.2.0/src/brindle/builtin_agents/developer-heavy.md +0 -23
  55. {brindle-0.2.0 → brindle-0.3.0}/LICENSE +0 -0
  56. {brindle-0.2.0 → brindle-0.3.0}/SCHEDULE-A +0 -0
  57. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/__init__.py +0 -0
  58. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/__main__.py +0 -0
  59. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/account.py +0 -0
  60. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/airgap.py +0 -0
  61. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/antigravity.py +0 -0
  62. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/builtin_agents/__init__.py +0 -0
  63. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/builtin_agents/backend-auditor.md +0 -0
  64. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/builtin_agents/developer-local.md +0 -0
  65. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/builtin_agents/qa.md +0 -0
  66. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/builtin_agents/subagent.md +0 -0
  67. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/builtin_agents/supervisor.md +0 -0
  68. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/builtin_rules/__init__.py +0 -0
  69. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/builtin_rules/security/backend.md +0 -0
  70. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/builtin_rules/style/minimal-diff.md +0 -0
  71. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/builtin_rules/tests/only.md +0 -0
  72. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/codemap.py +0 -0
  73. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/codex_hook.py +0 -0
  74. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/conflicts.py +0 -0
  75. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/cost_estimate.py +0 -0
  76. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/demo.py +0 -0
  77. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/detect.py +0 -0
  78. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/events.py +0 -0
  79. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/git.py +0 -0
  80. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/guardrails.py +0 -0
  81. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/inbox.py +0 -0
  82. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/learned_rules.py +0 -0
  83. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/legacy.py +0 -0
  84. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/native/__init__.py +0 -0
  85. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/native/client.py +0 -0
  86. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/native/loop.py +0 -0
  87. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/native/permissions.py +0 -0
  88. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/native/serve.py +0 -0
  89. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/native/tools.py +0 -0
  90. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/permissions.py +0 -0
  91. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/plugins.py +0 -0
  92. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/policy.py +0 -0
  93. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/pro/__init__.py +0 -0
  94. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/pro/_files.py +0 -0
  95. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/pro/audit_chain.py +0 -0
  96. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/pro/audit_export.py +0 -0
  97. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/pro/cost_centers.py +0 -0
  98. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/pro/features.py +0 -0
  99. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/pro/keys.py +0 -0
  100. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/pro/license.py +0 -0
  101. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/pro/loopback.py +0 -0
  102. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/pro/orgkey.py +0 -0
  103. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/pro/rollout.py +0 -0
  104. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/pro/settings_sync.py +0 -0
  105. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/pro/status.py +0 -0
  106. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/pro/team_events.py +0 -0
  107. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/repo_cmds.py +0 -0
  108. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/repos.py +0 -0
  109. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/rewind.py +0 -0
  110. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/rule_checks.py +0 -0
  111. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/savings.py +0 -0
  112. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/scratch.py +0 -0
  113. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/services.py +0 -0
  114. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/sessions.py +0 -0
  115. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/sidebar_follow.py +0 -0
  116. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/status_cache.py +0 -0
  117. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/tasks.py +0 -0
  118. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/tmux.py +0 -0
  119. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/usage.py +0 -0
  120. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/view.py +0 -0
  121. {brindle-0.2.0 → brindle-0.3.0}/src/brindle/watch.py +0 -0
@@ -9,3 +9,4 @@ graphify-out/
9
9
  # private, kept local for now
10
10
  docs/
11
11
  .brindle/config.local.json
12
+ .brindle/bench.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brindle
3
- Version: 0.2.0
3
+ Version: 0.3.0
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.2.0`). A session started before an upgrade keeps
92
+ session shows it too (`brindle 0.3.0`). 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
@@ -350,6 +350,7 @@ your own status line prints, so what you see doesn't change.
350
350
  | `brindle transfer [REPO] [--from SESSION] [-b BRANCH]` | move a scratch session's work into a real repo |
351
351
  | `brindle ls [--all]` | workspaces and agents |
352
352
  | `brindle repo add PATH [--name ALIAS] / rm ALIAS / ls` | attach other local repos to the current session so workers can go there (brindle Pro; see "Several repos in one session") |
353
+ | `brindle keys set/unset/list [NAME]` | store model API keys (e.g. `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`) once in the OS keychain (or a 0600 file); every agent pane gets the ones its provider reads, unless you already export it. `set` reads the value from a hidden prompt or stdin, `list` shows names and the last 4 characters. `CLAUDE_CODE_OAUTH_TOKEN` (a Claude subscription token) is never stored |
353
354
  | `brindle history [--limit N] [--kind K] [--all]` | durable log of worker results, reviews, merges and milestone checks |
354
355
  | `brindle cost [--days N] [--all]` | dollar spend at list prices over the last 30 days, by model |
355
356
  | `brindle cost report [--days N] [--all]` | Pro: spend by day, profile and goal, cost per merged branch, review pass rate per worker profile |
@@ -360,7 +361,8 @@ your own status line prints, so what you see doesn't change.
360
361
  | `brindle cost requests` | Enterprise: your approval requests and their status; checks pending ones with the server (the cull pass also does, every few minutes). With cost centers, spend events also carry the repo (`owner/name`) so the org can total spend per center |
361
362
  | `brindle cost estimate [--tasks N] [--weight W] [--profile P] [--reviewer R]` | what the current goal (or N tasks) is likely to cost, as a range from this repo's history (median to p80 of past worker and review runs), plus a cheaper alternative with a different reviewer; says "low confidence" when there's too little history. Also added to `set_goal` and `assign` replies (brindle Pro) |
362
363
  | `brindle learning` | whether brindle Pro's hosted learning is on for this repo, and if not, why (nothing is learned on your machine) |
363
- | `brindle account [login\|logout\|status\|upgrade\|portal\|org]` | paid features: bare `brindle account` shows what your plan has and how to get the rest (see "brindle Pro and Team" below) |
364
+ | `brindle learning seed` | Pro: sends this repo's finished routing history to the hosted learner once, so it starts with what this machine has seen. Only the usual coarse record fields leave (kind/size one-hots, counters, HMAC keys), never task text or paths. A second run sends nothing new; decisions already sent live are skipped |
365
+ | `brindle account [login\|logout\|status\|upgrade\|portal\|seats\|org]` | paid features: bare `brindle account` shows what your plan has and how to get the rest (see "brindle Pro and Team" below) |
364
366
  | `brindle audit verify\|export\|pubkey\|ship\|prune` | the local tamper-evident audit log, and shipping it to your SIEM (brindle Enterprise; see "Audit log" below) |
365
367
  | `brindle watch [--all] [--once]` | the dashboard on its own (the same view as the sidebar): enter attaches, `p` peeks, `x` closes |
366
368
  | `brindle sidebar` | bring this session's sidebar into the tmux session you're in (also `Ctrl-b S` in a window without one); restarts it if it was closed |
@@ -525,6 +527,7 @@ Autopilot, merge gates and cleanup:
525
527
  | `max_agents` | `4` | workers running at once per session (`0`: no cap) |
526
528
  | `check_timeout` | `900` | seconds each check may take |
527
529
  | `check_concurrency` | `2` | check runs at once on this machine, across branches; the rest wait their turn (`0`: no cap). A run far past its last duration is reported to the supervisor |
530
+ | `fable_escalation` | on, unless the worker runs on a cloud account (Bedrock, Vertex or Foundry) | retry a failed heavy task once on `developer-heavy` (Fable) before handing it to you; `true` turns it on for a cloud account, `false` off |
528
531
  | `usage_limit` | `90` | autopilot stops pushing on at this % of your Claude usage limit |
529
532
  | `limit_cooldown_minutes` | `300` for Antigravity | how long a provider that hit its limit counts as unavailable |
530
533
  | `graphify` | if the graph is there | point agents at the repo's [graphify](https://github.com/safishamsi/graphify) code map (`false` turns it off) |
@@ -588,9 +591,9 @@ keep theirs):
588
591
  ```json
589
592
  {
590
593
  "routing": {
591
- "light": ["developer", "developer-codex", "developer-local"],
594
+ "light": ["developer-light", "developer", "developer-codex", "developer-local"],
592
595
  "medium": ["developer", "developer-codex", "developer-heavy"],
593
- "heavy": ["developer-heavy", "developer", "developer-codex"]
596
+ "heavy": ["developer", "developer-heavy", "developer-codex"]
594
597
  }
595
598
  }
596
599
  ```
@@ -598,7 +601,9 @@ keep theirs):
598
601
  No tier prefers another provider for its own sake: all-Claude or all-Codex may
599
602
  be what works best for you, and that's for the learner to find out.
600
603
 
601
- `developer-codex` runs on Codex; `developer-heavy` on Claude Fable at high effort.
604
+ `developer-light` runs on Claude Haiku at low effort; `developer` on your Claude
605
+ Code default model; `developer-codex` on Codex; `developer-heavy` on Claude Fable
606
+ at high effort.
602
607
  A profile is skipped when its CLI isn't installed (`claude`, `codex`, `agy`), the
603
608
  local model server isn't answering, or its provider is at your `usage_limit`. If
604
609
  hosted learning is on it chooses among the profiles left; otherwise the first
@@ -761,7 +766,9 @@ brindle account login # opens your browser to sign in (--device: enter a co
761
766
  brindle account status # your plan, features, hosted learning on or off, when the entitlement expires
762
767
  brindle account savings # what hosted learning's picks gained in this repo, this month and last: estimates, from this machine's records only
763
768
  brindle account upgrade # opens the checkout for brindle Pro (and prints its URL)
764
- brindle account portal # opens the billing portal (invoices, seats, cancellation); --org ORG for a team org
769
+ brindle account upgrade --enterprise [--trial] --seats N --org ORG # brindle Enterprise for a team org; --trial: free trial, at most 10 seats
770
+ brindle account portal # opens the billing portal (invoices, payment method, cancellation); --org ORG for a team org
771
+ brindle account seats N # set a Team or Enterprise org's seat count (billing admin); --org ORG; Stripe prorates the change
765
772
  brindle account org list # the orgs you belong to; `org use <id>` switches, `org policy` shows the current one
766
773
  brindle account org policy # the org's policy, its per-role overrides, and the policy that applies to you
767
774
  brindle account org profiles # the org's shared profiles and rule packs, and which are pinned
@@ -824,6 +831,16 @@ entitlement.
824
831
  * `deny_personal_keys`: the personal model keys (`ANTHROPIC_API_KEY`,
825
832
  `OPENAI_API_KEY`, ...) are taken out of every agent pane, and a profile that
826
833
  sets one is refused.
834
+ * An org-owned key: with `provider` `anthropic` (or `openai-compatible`), set
835
+ `key_env` to the name of a variable that holds the org's key (not a personal
836
+ one like `ANTHROPIC_API_KEY`), or `key_helper` to a command, as a list of
837
+ arguments, that prints the key (for example `["vault", "kv", "get",
838
+ "-field=key", "secret/anthropic"]`). brindle runs the helper on the person's
839
+ machine, without a shell and with a timeout, each time a worker starts, and
840
+ gives the key to that worker as `ANTHROPIC_API_KEY` (`OPENAI_API_KEY` for
841
+ Codex). It passes `deny_personal_keys`; a personal key doesn't. The key never
842
+ goes through brindle's servers. When the variable is missing or the helper
843
+ fails, the worker doesn't start.
827
844
 
828
845
  Two things to know when you turn on `deny_personal_keys`:
829
846
 
@@ -984,17 +1001,31 @@ workflows call these commands:
984
1001
  commits the workflows on a `brindle/ci-setup` branch, then puts you back on
985
1002
  the branch you started on and deletes the local setup branch, so `git pull`
986
1003
  after merging the setup pull request is clean (commit or stash your changes
987
- first: it refuses to start with uncommitted work).
1004
+ first: it refuses to start with uncommitted work). With your own cloud
1005
+ (`--credential bedrock|vertex|foundry`, keyless GitHub OIDC) it can read the
1006
+ IDs from the deployment template's outputs instead of asking:
1007
+ `--from-stack NAME [--region R]` (AWS CloudFormation), `--from-terraform DIR`
1008
+ (Google) or `--from-deployment NAME -g RG` (Azure). They are defaults for
1009
+ `--cloud-var`, which still overrides them, and every ID is format-checked. A
1010
+ missing `aws`, `terraform` or `az` CLI or output is an error that names the
1011
+ `--cloud-var` to pass instead. Vertex AI and Microsoft Foundry are in
1012
+ **preview** (not yet verified end to end): `init` refuses them unless you pass
1013
+ `--preview` or set `BRINDLE_CI_PREVIEW_CLOUDS=1`, and `doctor` marks them
1014
+ `(preview)`. Bedrock is generally available.
988
1015
  - `brindle ci doctor`: which provider CLIs and credential names a runner has, and
989
- which providers CI may use on this repository.
1016
+ which providers CI may use on this repository. `--models` also makes one tiny
1017
+ call per pinned model and says why any is refused (a quota, a form not filled in).
990
1018
  - `brindle ci start`: start a run for an issue, a goal text or a dispatched run,
991
1019
  or a validation of a pull request.
992
1020
  - `brindle ci run`: run what was started: the supervisor and the result upload,
993
1021
  or the pull request's checks and reviews and the evidence upload.
994
1022
  - `brindle ci report`: tell the service how the workflow's jobs ended.
995
1023
 
996
- Credential names are reported, never values. On a repository owned by a GitHub
997
- organization, a provider signed in only with a personal subscription is not used.
1024
+ Credential names are reported, never values. CI uses API keys, cloud sign-ins
1025
+ (Bedrock, Vertex, Foundry) or identity federation only: a provider signed in
1026
+ only with a subscription (`CLAUDE_CODE_OAUTH_TOKEN`, a Codex ChatGPT login) is
1027
+ not used, on personal and organization repositories alike. Add an API key
1028
+ secret instead.
998
1029
 
999
1030
  Fix builds only fix the default branch's *required* status checks, and a new
1000
1031
  repository has none. When the default branch requires none, `brindle ci init`
@@ -1239,6 +1270,16 @@ needs no browser step. Put the key in your environment, or in a profile's
1239
1270
  `brindle doctor` names the variable each CLI gets, from your shell or from
1240
1271
  which profiles.
1241
1272
 
1273
+ A profile chooses between the two with `auth`:
1274
+
1275
+ | `auth:` | What the worker signs in with |
1276
+ |---|---|
1277
+ | `auto` (default) | A key from your environment or `brindle keys` if there is one, else the CLI's own login. |
1278
+ | `subscription` | Always the CLI's own login: brindle strips that provider's API keys from the pane. |
1279
+ | `api_key` | Only a key: the worker refuses to start without one. |
1280
+
1281
+ Your org's `deny_personal_keys` (Enterprise) still applies on top of `auth`.
1282
+
1242
1283
  ### Cheap workers
1243
1284
 
1244
1285
  By default a Claude Code worker loads everything your own `claude` does: your
@@ -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.2.0`). A session started before an upgrade keeps
62
+ session shows it too (`brindle 0.3.0`). 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
@@ -320,6 +320,7 @@ your own status line prints, so what you see doesn't change.
320
320
  | `brindle transfer [REPO] [--from SESSION] [-b BRANCH]` | move a scratch session's work into a real repo |
321
321
  | `brindle ls [--all]` | workspaces and agents |
322
322
  | `brindle repo add PATH [--name ALIAS] / rm ALIAS / ls` | attach other local repos to the current session so workers can go there (brindle Pro; see "Several repos in one session") |
323
+ | `brindle keys set/unset/list [NAME]` | store model API keys (e.g. `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`) once in the OS keychain (or a 0600 file); every agent pane gets the ones its provider reads, unless you already export it. `set` reads the value from a hidden prompt or stdin, `list` shows names and the last 4 characters. `CLAUDE_CODE_OAUTH_TOKEN` (a Claude subscription token) is never stored |
323
324
  | `brindle history [--limit N] [--kind K] [--all]` | durable log of worker results, reviews, merges and milestone checks |
324
325
  | `brindle cost [--days N] [--all]` | dollar spend at list prices over the last 30 days, by model |
325
326
  | `brindle cost report [--days N] [--all]` | Pro: spend by day, profile and goal, cost per merged branch, review pass rate per worker profile |
@@ -330,7 +331,8 @@ your own status line prints, so what you see doesn't change.
330
331
  | `brindle cost requests` | Enterprise: your approval requests and their status; checks pending ones with the server (the cull pass also does, every few minutes). With cost centers, spend events also carry the repo (`owner/name`) so the org can total spend per center |
331
332
  | `brindle cost estimate [--tasks N] [--weight W] [--profile P] [--reviewer R]` | what the current goal (or N tasks) is likely to cost, as a range from this repo's history (median to p80 of past worker and review runs), plus a cheaper alternative with a different reviewer; says "low confidence" when there's too little history. Also added to `set_goal` and `assign` replies (brindle Pro) |
332
333
  | `brindle learning` | whether brindle Pro's hosted learning is on for this repo, and if not, why (nothing is learned on your machine) |
333
- | `brindle account [login\|logout\|status\|upgrade\|portal\|org]` | paid features: bare `brindle account` shows what your plan has and how to get the rest (see "brindle Pro and Team" below) |
334
+ | `brindle learning seed` | Pro: sends this repo's finished routing history to the hosted learner once, so it starts with what this machine has seen. Only the usual coarse record fields leave (kind/size one-hots, counters, HMAC keys), never task text or paths. A second run sends nothing new; decisions already sent live are skipped |
335
+ | `brindle account [login\|logout\|status\|upgrade\|portal\|seats\|org]` | paid features: bare `brindle account` shows what your plan has and how to get the rest (see "brindle Pro and Team" below) |
334
336
  | `brindle audit verify\|export\|pubkey\|ship\|prune` | the local tamper-evident audit log, and shipping it to your SIEM (brindle Enterprise; see "Audit log" below) |
335
337
  | `brindle watch [--all] [--once]` | the dashboard on its own (the same view as the sidebar): enter attaches, `p` peeks, `x` closes |
336
338
  | `brindle sidebar` | bring this session's sidebar into the tmux session you're in (also `Ctrl-b S` in a window without one); restarts it if it was closed |
@@ -495,6 +497,7 @@ Autopilot, merge gates and cleanup:
495
497
  | `max_agents` | `4` | workers running at once per session (`0`: no cap) |
496
498
  | `check_timeout` | `900` | seconds each check may take |
497
499
  | `check_concurrency` | `2` | check runs at once on this machine, across branches; the rest wait their turn (`0`: no cap). A run far past its last duration is reported to the supervisor |
500
+ | `fable_escalation` | on, unless the worker runs on a cloud account (Bedrock, Vertex or Foundry) | retry a failed heavy task once on `developer-heavy` (Fable) before handing it to you; `true` turns it on for a cloud account, `false` off |
498
501
  | `usage_limit` | `90` | autopilot stops pushing on at this % of your Claude usage limit |
499
502
  | `limit_cooldown_minutes` | `300` for Antigravity | how long a provider that hit its limit counts as unavailable |
500
503
  | `graphify` | if the graph is there | point agents at the repo's [graphify](https://github.com/safishamsi/graphify) code map (`false` turns it off) |
@@ -558,9 +561,9 @@ keep theirs):
558
561
  ```json
559
562
  {
560
563
  "routing": {
561
- "light": ["developer", "developer-codex", "developer-local"],
564
+ "light": ["developer-light", "developer", "developer-codex", "developer-local"],
562
565
  "medium": ["developer", "developer-codex", "developer-heavy"],
563
- "heavy": ["developer-heavy", "developer", "developer-codex"]
566
+ "heavy": ["developer", "developer-heavy", "developer-codex"]
564
567
  }
565
568
  }
566
569
  ```
@@ -568,7 +571,9 @@ keep theirs):
568
571
  No tier prefers another provider for its own sake: all-Claude or all-Codex may
569
572
  be what works best for you, and that's for the learner to find out.
570
573
 
571
- `developer-codex` runs on Codex; `developer-heavy` on Claude Fable at high effort.
574
+ `developer-light` runs on Claude Haiku at low effort; `developer` on your Claude
575
+ Code default model; `developer-codex` on Codex; `developer-heavy` on Claude Fable
576
+ at high effort.
572
577
  A profile is skipped when its CLI isn't installed (`claude`, `codex`, `agy`), the
573
578
  local model server isn't answering, or its provider is at your `usage_limit`. If
574
579
  hosted learning is on it chooses among the profiles left; otherwise the first
@@ -731,7 +736,9 @@ brindle account login # opens your browser to sign in (--device: enter a co
731
736
  brindle account status # your plan, features, hosted learning on or off, when the entitlement expires
732
737
  brindle account savings # what hosted learning's picks gained in this repo, this month and last: estimates, from this machine's records only
733
738
  brindle account upgrade # opens the checkout for brindle Pro (and prints its URL)
734
- brindle account portal # opens the billing portal (invoices, seats, cancellation); --org ORG for a team org
739
+ brindle account upgrade --enterprise [--trial] --seats N --org ORG # brindle Enterprise for a team org; --trial: free trial, at most 10 seats
740
+ brindle account portal # opens the billing portal (invoices, payment method, cancellation); --org ORG for a team org
741
+ brindle account seats N # set a Team or Enterprise org's seat count (billing admin); --org ORG; Stripe prorates the change
735
742
  brindle account org list # the orgs you belong to; `org use <id>` switches, `org policy` shows the current one
736
743
  brindle account org policy # the org's policy, its per-role overrides, and the policy that applies to you
737
744
  brindle account org profiles # the org's shared profiles and rule packs, and which are pinned
@@ -794,6 +801,16 @@ entitlement.
794
801
  * `deny_personal_keys`: the personal model keys (`ANTHROPIC_API_KEY`,
795
802
  `OPENAI_API_KEY`, ...) are taken out of every agent pane, and a profile that
796
803
  sets one is refused.
804
+ * An org-owned key: with `provider` `anthropic` (or `openai-compatible`), set
805
+ `key_env` to the name of a variable that holds the org's key (not a personal
806
+ one like `ANTHROPIC_API_KEY`), or `key_helper` to a command, as a list of
807
+ arguments, that prints the key (for example `["vault", "kv", "get",
808
+ "-field=key", "secret/anthropic"]`). brindle runs the helper on the person's
809
+ machine, without a shell and with a timeout, each time a worker starts, and
810
+ gives the key to that worker as `ANTHROPIC_API_KEY` (`OPENAI_API_KEY` for
811
+ Codex). It passes `deny_personal_keys`; a personal key doesn't. The key never
812
+ goes through brindle's servers. When the variable is missing or the helper
813
+ fails, the worker doesn't start.
797
814
 
798
815
  Two things to know when you turn on `deny_personal_keys`:
799
816
 
@@ -954,17 +971,31 @@ workflows call these commands:
954
971
  commits the workflows on a `brindle/ci-setup` branch, then puts you back on
955
972
  the branch you started on and deletes the local setup branch, so `git pull`
956
973
  after merging the setup pull request is clean (commit or stash your changes
957
- first: it refuses to start with uncommitted work).
974
+ first: it refuses to start with uncommitted work). With your own cloud
975
+ (`--credential bedrock|vertex|foundry`, keyless GitHub OIDC) it can read the
976
+ IDs from the deployment template's outputs instead of asking:
977
+ `--from-stack NAME [--region R]` (AWS CloudFormation), `--from-terraform DIR`
978
+ (Google) or `--from-deployment NAME -g RG` (Azure). They are defaults for
979
+ `--cloud-var`, which still overrides them, and every ID is format-checked. A
980
+ missing `aws`, `terraform` or `az` CLI or output is an error that names the
981
+ `--cloud-var` to pass instead. Vertex AI and Microsoft Foundry are in
982
+ **preview** (not yet verified end to end): `init` refuses them unless you pass
983
+ `--preview` or set `BRINDLE_CI_PREVIEW_CLOUDS=1`, and `doctor` marks them
984
+ `(preview)`. Bedrock is generally available.
958
985
  - `brindle ci doctor`: which provider CLIs and credential names a runner has, and
959
- which providers CI may use on this repository.
986
+ which providers CI may use on this repository. `--models` also makes one tiny
987
+ call per pinned model and says why any is refused (a quota, a form not filled in).
960
988
  - `brindle ci start`: start a run for an issue, a goal text or a dispatched run,
961
989
  or a validation of a pull request.
962
990
  - `brindle ci run`: run what was started: the supervisor and the result upload,
963
991
  or the pull request's checks and reviews and the evidence upload.
964
992
  - `brindle ci report`: tell the service how the workflow's jobs ended.
965
993
 
966
- Credential names are reported, never values. On a repository owned by a GitHub
967
- organization, a provider signed in only with a personal subscription is not used.
994
+ Credential names are reported, never values. CI uses API keys, cloud sign-ins
995
+ (Bedrock, Vertex, Foundry) or identity federation only: a provider signed in
996
+ only with a subscription (`CLAUDE_CODE_OAUTH_TOKEN`, a Codex ChatGPT login) is
997
+ not used, on personal and organization repositories alike. Add an API key
998
+ secret instead.
968
999
 
969
1000
  Fix builds only fix the default branch's *required* status checks, and a new
970
1001
  repository has none. When the default branch requires none, `brindle ci init`
@@ -1209,6 +1240,16 @@ needs no browser step. Put the key in your environment, or in a profile's
1209
1240
  `brindle doctor` names the variable each CLI gets, from your shell or from
1210
1241
  which profiles.
1211
1242
 
1243
+ A profile chooses between the two with `auth`:
1244
+
1245
+ | `auth:` | What the worker signs in with |
1246
+ |---|---|
1247
+ | `auto` (default) | A key from your environment or `brindle keys` if there is one, else the CLI's own login. |
1248
+ | `subscription` | Always the CLI's own login: brindle strips that provider's API keys from the pane. |
1249
+ | `api_key` | Only a key: the worker refuses to start without one. |
1250
+
1251
+ Your org's `deny_personal_keys` (Enterprise) still applies on top of `auth`.
1252
+
1212
1253
  ### Cheap workers
1213
1254
 
1214
1255
  By default a Claude Code worker loads everything your own `claude` does: your
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "brindle"
3
- version = "0.2.0"
3
+ version = "0.3.0"
4
4
  description = "Run coding agents in parallel, each on its own git branch, with a supervisor that delegates, reviews, and merges"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -52,6 +52,8 @@ audit_export = "brindle.pro.audit_export:make"
52
52
  dev = [
53
53
  "pytest>=8",
54
54
  "pytest-xdist>=3.8.0",
55
+ "python-hcl2>=8.1.4",
56
+ "pyyaml>=6.0.3",
55
57
  ]
56
58
 
57
59
  [build-system]