brindle 0.0.2__tar.gz → 0.0.4__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 (88) hide show
  1. {brindle-0.0.2 → brindle-0.0.4}/PKG-INFO +109 -134
  2. {brindle-0.0.2 → brindle-0.0.4}/README.md +108 -133
  3. {brindle-0.0.2 → brindle-0.0.4}/pyproject.toml +1 -1
  4. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/agents.py +81 -24
  5. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/autopilot.py +67 -25
  6. brindle-0.0.4/src/brindle/ci_adapters.py +662 -0
  7. brindle-0.0.4/src/brindle/ci_client.py +1371 -0
  8. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/cli.py +139 -77
  9. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/config.py +1 -1
  10. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/cull.py +34 -12
  11. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/db.py +66 -11
  12. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/doctor.py +73 -13
  13. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/mcp_server.py +162 -41
  14. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/native/client.py +25 -1
  15. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/pro/account.py +3 -2
  16. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/pro/auth.py +24 -4
  17. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/pro/license.py +47 -31
  18. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/providers.py +182 -32
  19. brindle-0.0.4/src/brindle/repo_cmds.py +74 -0
  20. brindle-0.0.4/src/brindle/repos.py +245 -0
  21. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/sessions.py +35 -4
  22. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/tasks.py +45 -11
  23. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/tmux.py +161 -36
  24. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/view.py +24 -1
  25. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/watch.py +34 -13
  26. brindle-0.0.2/src/brindle/ci.py +0 -948
  27. {brindle-0.0.2 → brindle-0.0.4}/.gitignore +0 -0
  28. {brindle-0.0.2 → brindle-0.0.4}/LICENSE +0 -0
  29. {brindle-0.0.2 → brindle-0.0.4}/SCHEDULE-A +0 -0
  30. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/__init__.py +0 -0
  31. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/__main__.py +0 -0
  32. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/account.py +0 -0
  33. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/airgap.py +0 -0
  34. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/antigravity.py +0 -0
  35. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/builtin_agents/__init__.py +0 -0
  36. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/builtin_agents/developer-codex.md +0 -0
  37. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/builtin_agents/developer-heavy.md +0 -0
  38. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/builtin_agents/developer-local.md +0 -0
  39. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/builtin_agents/developer.md +0 -0
  40. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/builtin_agents/reviewer-codex.md +0 -0
  41. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/builtin_agents/reviewer-local.md +0 -0
  42. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/builtin_agents/reviewer.md +0 -0
  43. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/builtin_agents/subagent.md +0 -0
  44. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/builtin_agents/supervisor.md +0 -0
  45. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/codemap.py +0 -0
  46. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/codex_hook.py +0 -0
  47. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/demo.py +0 -0
  48. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/detect.py +0 -0
  49. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/events.py +0 -0
  50. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/gates.py +0 -0
  51. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/git.py +0 -0
  52. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/history.py +0 -0
  53. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/inbox.py +0 -0
  54. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/learning.py +0 -0
  55. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/legacy.py +0 -0
  56. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/native/__init__.py +0 -0
  57. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/native/loop.py +0 -0
  58. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/native/permissions.py +0 -0
  59. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/native/runner.py +0 -0
  60. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/native/serve.py +0 -0
  61. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/native/tools.py +0 -0
  62. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/permissions.py +0 -0
  63. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/pipeline.py +0 -0
  64. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/plugins.py +0 -0
  65. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/policy.py +0 -0
  66. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/pool.py +0 -0
  67. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/pro/__init__.py +0 -0
  68. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/pro/_files.py +0 -0
  69. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/pro/audit_chain.py +0 -0
  70. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/pro/credentials.py +0 -0
  71. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/pro/features.py +0 -0
  72. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/pro/keys.py +0 -0
  73. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/pro/learning.py +0 -0
  74. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/pro/loopback.py +0 -0
  75. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/pro/orgkey.py +0 -0
  76. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/pro/settings_sync.py +0 -0
  77. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/pro/team_events.py +0 -0
  78. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/pro/team_policy.py +0 -0
  79. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/procs.py +0 -0
  80. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/profiles.py +0 -0
  81. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/quota.py +0 -0
  82. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/savings.py +0 -0
  83. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/scratch.py +0 -0
  84. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/services.py +0 -0
  85. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/sidebar_follow.py +0 -0
  86. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/status_cache.py +0 -0
  87. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/usage.py +0 -0
  88. {brindle-0.0.2 → brindle-0.0.4}/src/brindle/workspaces.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brindle
3
- Version: 0.0.2
3
+ Version: 0.0.4
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
@@ -45,9 +45,9 @@ curl -fsSL pawdelta.com/brindle/install | sh
45
45
  brindle demo # watch it finish a practice repo in a few minutes
46
46
  ```
47
47
 
48
- ![brindle demo: a supervisor splits a goal between two workers, each branch is reviewed and merged, and both milestones turn green once their checks pass](https://pawdelta.com/brindle/brindle-demo.gif)
48
+ ![brindle demo: a supervisor splits a goal between two workers, each branch is reviewed and merged, and both milestones turn green once their checks pass](https://pawdelta.com/brindle/brindle-demo.gif?v=0.0.2)
49
49
 
50
- *`brindle demo`, recorded on brindle 0.14.6 (sped up 4×): two workers in parallel, each branch reviewed and merged, both milestones verified by their check commands.*
50
+ *`brindle demo`, recorded on brindle 0.0.2 (sped up 4×): two workers in parallel, each branch reviewed and merged, both milestones verified by their check commands.*
51
51
 
52
52
  - **Done means a command passed.** A goal is split into milestones, each with a
53
53
  check command that brindle runs itself. A milestone is verified when its check exits
@@ -89,13 +89,9 @@ 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.11.5`). A session started before an upgrade keeps
92
+ session shows it too (`brindle 0.0.4`). 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
- ### Upgrading from copse
96
-
97
- brindle was called copse until 0.0.1, and it doesn't read anything copse saved. Install `brindle`, then move `~/.copse` to `~/.brindle` and each repo's `.copse/` to `.brindle/`. Until you do, saved permission rules, policy and config there don't apply. `brindle doctor` lists every leftover it finds, including old `copse` entries in `.agents/hooks.json` and `mcp_config.json` to remove.
98
-
99
95
  ## Quick start
100
96
 
101
97
  ```sh
@@ -345,6 +341,7 @@ your own status line prints, so what you see doesn't change.
345
341
  | `brindle delegation [conservative\|balanced\|fast]` | how readily the supervisor delegates: fewest tokens, the default, or quickest |
346
342
  | `brindle transfer [REPO] [--from SESSION] [-b BRANCH]` | move a scratch session's work into a real repo |
347
343
  | `brindle ls [--all]` | workspaces and agents |
344
+ | `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") |
348
345
  | `brindle history [--limit N] [--kind K] [--all]` | durable log of worker results, reviews, merges and milestone checks |
349
346
  | `brindle history --share [--session ID]` | a few lines about this session to paste into Slack or a post: goal, milestones verified, workers, merges, reviews (and how many by a different model), parallel speedup, tokens |
350
347
  | `brindle permissions list / check / suggestions / accept / allow / deny / forget / reset` | the rules brindle answers workers' permission requests with, and what it suggests from your approvals (see "Permission policy") |
@@ -380,6 +377,7 @@ knowing them helps when you tell the supervisor how to work.
380
377
  | `send_message` | any agent | message another agent; delivered when it's idle |
381
378
  | `read_messages` | supervisor | read the messages agents and brindle sent you and mark them read; with `message_delivery` `"pull"` (the default) you get a one-line "N new messages" notice instead of each message |
382
379
  | `list_agents` / `list_tasks` / `list_agent_profiles` | supervisor | who's running, what's queued, which profiles exist |
380
+ | `list_repos` | supervisor | the repos attached to the session (brindle Pro); `repo=<alias>` on `assign`/`handoff` puts a worker there |
383
381
  | `cancel_task` | supervisor | cancel a queued task (and its dependents) to re-plan |
384
382
  | `workspace_diff` | supervisor | a worker branch's changes against its base |
385
383
  | `request_review` / `submit_review` | supervisor / reviewer | start a reviewer on a branch / record its verdict |
@@ -484,7 +482,7 @@ Autopilot, merge gates and cleanup:
484
482
  | `sidebar` | `"left"` | where the dashboard sits in each window: `"left"` of the chat, or `"bottom"` (full-width rows under it) |
485
483
  | `delegation` | `"balanced"` | how readily the supervisor hands work to workers. `"conservative"` does most work in its own chat (fewest tokens), `"fast"` splits any multi-part request across parallel workers straight away (quickest, most tokens). `brindle delegation fast` saves it for every repo and session (in `~/.brindle/config.json`; `--repo` for this repo only) and tells a running supervisor |
486
484
  | `delete_merged_branches` | `true` | removing a worktree (after a merge, `brindle rm`, `brindle prune`, session cleanup) also deletes its branch once every commit is in its base, so finished branches don't pile up. An unmerged branch is always kept; `false` keeps them all. If GitHub keeps merged PR branches, the first `brindle pr` in a repo offers to turn on its automatic deletion with your `gh` login (repo admins only) |
487
- | `pr_footer` | `true` | `brindle pr` and `brindle ci` end the PR description with one line: "🌲 Built in parallel and verified with brindle" (a link). `false` leaves it out. Never added to commit messages |
485
+ | `pr_footer` | `true` | `brindle pr` ends the PR description with one line: "🌲 Built in parallel and verified with brindle" (a link). `false` leaves it out. Never added to commit messages |
488
486
  | `message_delivery` | `"pull"` | how agent and brindle messages reach an interactive supervisor: `"pull"` keeps them unread and delivers one notice ("brindle (16:25:03): 2 new messages (from 9f742c5c, pipeline). Call read_messages."; the time keeps Claude Code from dropping a repeat; the sidebar shows an unread count), `"push"` delivers each message's text. Messages you send (`brindle send`, typing) and messages to workers are always pushed |
489
487
  | `learning` | `"auto"` | hosted learning (brindle Pro, via the API; see below): `"auto"` uses it when your plan includes it and nothing otherwise; `"cloud"`; `"off"`. Any other value means off |
490
488
  | `learning_candidates` | `[]` | the profile names the hosted learner may pick from |
@@ -672,6 +670,7 @@ how to use them, and the next step to get the rest:
672
670
  |---|---|---|
673
671
  | hosted learning | Pro | on by itself; `brindle learning` |
674
672
  | per-worktree services | Pro | `"services"` in `.brindle/config.json` |
673
+ | several repos in one session | Pro | `brindle repo add <path>` |
675
674
  | org policies + audit feed | Team | `brindle account org policy` |
676
675
  | Brindle-CI | Team | `brindle ci init` |
677
676
  | audit log, air-gap | Enterprise | `brindle audit verify`, `"airgap": true` |
@@ -689,6 +688,21 @@ brindle account org member policy-role <member> <role|none> # give a member a
689
688
  brindle account org company [link <org_id> | unlink] # link orgs you own into one company; learning is pooled only within it
690
689
  ```
691
690
 
691
+ ### Several repos in one session (brindle Pro)
692
+
693
+ A session can work across several repos. Attach another local repo you own
694
+ to the running session and the supervisor can put workers there: each gets a
695
+ worktree in that repo, that repo's own checks and review, and merges into that
696
+ repo's branch. A task may depend on one in another repo, a milestone check can
697
+ run in another repo (`check@<alias>: <command>` in `goals.md`, or `repo` in
698
+ `set_goal`), and the sidebar and `brindle ls` group workers by repo.
699
+
700
+ ```sh
701
+ brindle repo add ../pawdelta-web --name web # attach a repo to the current session (alias: web)
702
+ brindle repo ls # the repos attached to this session
703
+ brindle repo rm web # detach it (its worktrees and branches stay)
704
+ ```
705
+
692
706
  Setting up a team takes no sign-up form:
693
707
 
694
708
  ```sh
@@ -792,129 +806,61 @@ brindle audit pubkey # this install's public key (
792
806
  one altered in place (hash or signature), one removed, inserted or reordered
793
807
  (seq and prev_hash), or a truncated tail. Verifying and exporting never need
794
808
  the entitlement, so a log keeps its value after a plan lapses.
795
- ### Brindle-CI: issues into pull requests
796
-
797
- brindle Team can run brindle with nobody at a terminal. `brindle ci run` cuts a
798
- `brindle/ci-<issue or slug>` branch, starts a supervisor with autopilot on in a
799
- detached tmux session, gives it the goal, and waits until every milestone's
800
- check passes. Then it pushes the branch and opens the pull request with `gh`
801
- (the body lists the goal, the milestones and their checks, and `Closes #N`
802
- for an issue), prints the PR URL and exits 0; with `--bundle` it writes the
803
- branch to a file instead, for `brindle ci publish` (see below). It exits 1, with what happened,
804
- when the supervisor asks for a decision (`need_user`: the question is the
805
- reason), stalls, or runs out of time. The session and its workers are always
806
- stopped at the end, and a JSON summary goes to `$GITHUB_STEP_SUMMARY` when
807
- that is set.
808
-
809
- ```sh
810
- brindle ci run --issue 42 # the goal is the issue's title and body
811
- brindle ci run --goal "Add a /health endpoint" # or typed; a goals.md-shaped text brings its milestones
812
- brindle ci run --goal-file .brindle/goals.md --timeout 90 --max-workers 2 --base develop --no-pr
813
- brindle ci init --label brindle # the GitHub Actions workflow (see below)
814
-
815
- # The same run in three steps, so no secret worth stealing is near the agents:
816
- brindle ci entitle --out ent.jwt # uses BRINDLE_PRO_TOKEN, then exits
817
- brindle ci run --issue 42 --entitlement ent.jwt --bundle out/brindle.bundle # no CI token, no push token
818
- brindle ci publish out/brindle.bundle --repo acme/api # elsewhere: pushes and opens the PR
819
- ```
820
809
 
821
- With `--issue` and `--goal`, the supervisor derives the milestones and their
822
- checks itself; a goals.md-shaped goal (`# Goal`, `## Milestone`, `check:`) is
823
- recorded as written. `--max-workers` sets `max_agents` in the repo's
824
- `.brindle/config.local.json`.
825
-
826
- **Who is trusted with what.** Agents run your repo's code: its tests, its
827
- scripts, and whatever an issue talks them into. They run as the same user as
828
- brindle, so treat everything on that machine as theirs to read: environment
829
- variables, files, git and `gh` settings. brindle does take its tokens out of the
830
- environment before agents start and pushes from a clean copy of the commits,
831
- but that only makes theft harder. What actually protects a secret is that it
832
- isn't on the machine while agents run. So the work can be split:
833
-
834
- 1. `brindle ci entitle --out FILE` exchanges `BRINDLE_PRO_TOKEN` for the signed,
835
- short-lived entitlement and writes it to a file (mode 0600). Run it on a
836
- different machine from the agents (the workflow gives it its own job):
837
- on hosted runners agents have sudo, and the runner holds every secret of
838
- the job they run in. `brindle ci run` reads the file and deletes it before
839
- any agent starts.
840
- 2. `brindle ci run --entitlement FILE --bundle PATH` does the work with no CI
841
- token and no token that can write to GitHub (`--issue` needs one that can
842
- read). When the goal is verified it writes the new commits to PATH as a git
843
- bundle, and `PATH.json` with the branch, base, title and body of the pull
844
- request. `--bundle` implies `--no-pr`: nothing is pushed.
845
- 3. `brindle ci publish PATH [--repo owner/name]` runs where no agent ever ran,
846
- with the token that can push. It verifies the bundle, fetches its one
847
- `brindle/ci-…` branch into a fresh bare repo, pushes that branch to
848
- `https://github.com/<repo>` (default: `$GITHUB_REPOSITORY`) and opens the
849
- pull request with `gh`. It never checks out or runs repo code, hooks or
850
- agents, and it refuses a bundle whose branch isn't a `brindle/ci-` branch, so
851
- a run can't publish over `main`. The pull request targets `--base`, else
852
- the repository's default branch; the bundle doesn't get to choose. A
853
- hostile run can still add commits to an existing `brindle/ci-` branch.
854
-
855
- The workflow `brindle ci init` writes pins brindle to the version that wrote it,
856
- since the publishing job holds a write token. The short-lived entitlement
857
- passes between jobs as an artifact kept one day. A CI entitlement expires
858
- three hours after it's issued, so anyone who can download the repo's
859
- artifacts could use it for that long at most.
860
-
861
- `brindle ci run` without `--bundle` still pushes and opens the pull request
862
- itself. Use that only where you trust the repo's code and everyone who can
863
- steer the agents.
864
-
865
- `brindle ci init` writes `.github/workflows/brindle.yml`, which runs on
866
- `workflow_dispatch` and whenever an issue gets the label (`brindle` by
867
- default). It has three jobs:
868
-
869
- - `entitle` (no permissions) runs `brindle ci entitle` with `BRINDLE_PRO_TOKEN`
870
- on a machine that checks out and runs nothing from the repo. It hands the
871
- short-lived entitlement (three hours) to the next job as an artifact kept
872
- one day, so the CI token is never on the agents' machine.
873
- - `run` (permissions: `contents: read`, `issues: read`) checks the repo out
874
- without keeping credentials, installs tmux, brindle (pinned to the version
875
- that wrote the workflow) and Claude Code, then runs `brindle ci run --issue
876
- <number> --entitlement … --bundle …` with only `ANTHROPIC_API_KEY` and a
877
- read-only `GH_TOKEN`. brindle deletes the entitlement file before any agent
878
- starts. The job uploads the bundle as an artifact.
879
- - `publish` (permissions:
880
- `contents: write`, `pull-requests: write`) starts on a fresh machine, checks
881
- nothing out, downloads the artifact and runs `brindle ci publish`, targeting
882
- the repository's default branch.
883
-
884
- `brindle ci init` won't
885
- overwrite an existing file without `--force`. The workflow needs two secrets,
886
- `BRINDLE_PRO_TOKEN` (an org CI token, below) and `ANTHROPIC_API_KEY`, and the
887
- repo's Actions settings must allow GitHub Actions to create pull requests.
888
- The model API key is the one secret agents must have; give it a spending
889
- limit.
890
-
891
- An org admin creates the CI token; it is shown once, so store it straight away:
892
-
893
- ```sh
894
- brindle account org ci-token create "acme/api actions" --org org_... # prints cpc_... once
895
- gh secret set BRINDLE_PRO_TOKEN # paste it
896
- brindle account org ci-token list --org org_... # names, status, last used; never the secret
897
- brindle account org ci-token revoke ct_... --org org_... # CI stops at its next run
810
+ ### Brindle-CI
811
+
812
+ Brindle-CI (brindle Team) brings brindle into your CI: it fixes broken builds,
813
+ turns issues into pull requests whose checks it has verified, and reviews pull
814
+ requests with evidence. It runs on your own CI with your own model keys; the
815
+ hosted control plane decides what each run does and what it reports. The
816
+ workflows call these commands:
817
+
818
+ - `brindle ci init`: set a repository up in one go (the GitHub App, the secrets,
819
+ the workflows as a pull request, then a doctor check). Run it inside a
820
+ checkout of the repository. For Claude it asks whether to use an API key
821
+ (the `ANTHROPIC_API_KEY` secret) or Anthropic
822
+ [workload identity federation](https://platform.claude.com/docs/en/manage-claude/wif-reference)
823
+ (`--credential key|federation` answers without asking).
824
+ - `brindle ci doctor`: which provider CLIs and credential names a runner has, and
825
+ which providers CI may use on this repository.
826
+ - `brindle ci start`: start a run for an issue, a goal text or a dispatched run,
827
+ or a validation of a pull request.
828
+ - `brindle ci run`: run what was started: the supervisor and the result upload,
829
+ or the pull request's checks and reviews and the evidence upload.
830
+ - `brindle ci report`: tell the service how the workflow's jobs ended.
831
+
832
+ Credential names are reported, never values. On a repository owned by a GitHub
833
+ organization, a provider signed in only with a personal subscription is not used.
834
+
835
+ An Anthropic API key is either a workspace key (scoped to one workspace) or an
836
+ organization-level key (not scoped to a workspace). An organization-level key
837
+ needs the workspace in every request, so after the key `brindle ci init` asks for
838
+ its workspace ID (`wrkspc_...`; leave it blank for a workspace key, or pass
839
+ `--workspace-id`, default `$ANTHROPIC_WORKSPACE_ID`) and stores it as the Actions
840
+ variable `ANTHROPIC_WORKSPACE_ID`. The workflow then sets
841
+ `ANTHROPIC_CUSTOM_HEADERS="anthropic-workspace-id: <id>"`, which reaches every
842
+ Claude Code process the run starts.
843
+
844
+ With identity federation the repository stores no Anthropic key: the workflow
845
+ exchanges GitHub's OIDC token once for a short-lived Anthropic token, as a
846
+ Claude Console service account (billed as API, so it is allowed on
847
+ organization repositories), and gives the job `ANTHROPIC_AUTH_TOKEN`. `brindle
848
+ ci init` stores the rule, organization, service account and optional workspace
849
+ IDs as the Actions variables `ANTHROPIC_FEDERATION_RULE_ID`,
850
+ `ANTHROPIC_ORGANIZATION_ID`, `ANTHROPIC_SERVICE_ACCOUNT_ID` and
851
+ `ANTHROPIC_WORKSPACE_ID`. Create the federation rule in the Claude Console with
852
+ subject prefix `repo:<owner>/<name>:*`, the condition
853
+
854
+ ```text
855
+ claims.repository == "<owner>/<name>" && claims.workflow_ref.startsWith("<owner>/<name>/.github/workflows/brindle-ci-")
898
856
  ```
899
857
 
900
- On every run the token is presented to the backend, which checks it and the
901
- org's live plan and returns a signed entitlement. `brindle ci run` verifies it in
902
- memory and writes nothing to disk; `brindle ci entitle` writes only the signed
903
- entitlement, which expires soon, never the token. The token doesn't rotate, so one secret
904
- keeps working until it is revoked or the org's plan no longer includes CI. A
905
- refresh token from `brindle account login` won't do: it rotates on use.
906
-
907
- Two things to know before you add the label to your repo:
908
-
909
- - **Who can apply the label.** The issue body steers an unattended agent
910
- whose work becomes a pull request on a `brindle/ci-` branch. Only people you
911
- trust with write access should be able to apply the trigger label; on a
912
- public repo, anyone who can write the issue text is choosing what the agent
913
- is told to do. Review the pull request like any other before merging it.
914
- - **CI on the pull request.** A PR opened with the workflow's own
915
- `GITHUB_TOKEN` doesn't trigger the repo's other workflows. If you want your
916
- checks to run on brindle's PRs, set `GH_TOKEN` in the `publish` job to a
917
- GitHub App installation token or a personal access token instead.
858
+ audience `https://api.anthropic.com`, and a token lifetime of at least 7200
859
+ seconds. The condition lets only brindle's workflows mint tokens. On pull
860
+ requests the pull request's own copy of the validate workflow runs, so give
861
+ write access only to people you trust (forks never get a token). Leave the `ANTHROPIC_API_KEY` secret
862
+ unset: a key takes precedence (brindle drops an empty one before Claude Code
863
+ starts).
918
864
 
919
865
  **Closing and cleaning up.** Press `x` on an agent in the sidebar (twice for one
920
866
  that's still running) or run `brindle close <id>` to stop it and hide it. Stopping means
@@ -1056,14 +1002,34 @@ change files under a directory without prompts.
1056
1002
  brindle only offers a profile whose CLI is installed and signed in. A profile on a
1057
1003
  CLI that isn't signed in is left out of the supervisor's profile list and of
1058
1004
  routing by weight, and naming it directly stops with how to sign in
1059
- (`claude auth login`, `codex login`) instead of opening the CLI's login screen.
1060
- `brindle doctor` shows each CLI's sign-in. Keys set in the environment
1061
- (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY` and the like) count as signed in.
1062
- `agy` has no sign-in status command, so brindle can't tell when it's signed out:
1063
- sign in once by running `agy` yourself. A worker that no hook reports on (Codex)
1005
+ (`claude auth login`, `codex login`, running `agy`) instead of opening the CLI's
1006
+ login screen. `brindle doctor` shows, for each installed CLI, how it's signed in
1007
+ and whether a quota limit is in effect. Sign-in is its own login, an environment
1008
+ key (by name, never its value) set in your shell or in a profile's
1009
+ `env.NAME: value` lines, signed out, or unknown when the status check gives no
1010
+ answer. Keys set in the environment (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`,
1011
+ `GEMINI_API_KEY` and the like) count as signed in; see the table below for what
1012
+ each CLI takes. A worker that no hook reports on (Codex)
1064
1013
  and that shows nothing new for 10 minutes without reporting, for example because
1065
1014
  it's signed in without a plan that includes it, is reported to its supervisor.
1066
1015
 
1016
+ #### Key login
1017
+
1018
+ | Provider | Key login (environment variables) |
1019
+ |---|---|
1020
+ | `claude` | yes: `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, `CLAUDE_CODE_OAUTH_TOKEN`, or Bedrock/Vertex/Foundry (`CLAUDE_CODE_USE_BEDROCK`, `CLAUDE_CODE_USE_VERTEX`, `CLAUDE_CODE_USE_FOUNDRY`) |
1021
+ | `codex` | yes: `OPENAI_API_KEY`, `CODEX_API_KEY` |
1022
+ | `antigravity` | `GEMINI_API_KEY`, only with `"modelProvider": "gemini"` in `agy`'s `settings.json` (see [Google Antigravity](#google-antigravity)); otherwise `agy`'s own browser sign-in |
1023
+
1024
+ Workers that run unattended should use key login where the CLI supports it.
1025
+ A personal login is one interactive session with one quota shared by every
1026
+ worker: when it expires or its quota runs out, every worker on it stalls until
1027
+ someone signs in again in a browser. A key gives workers their own limits and
1028
+ needs no browser step. Put the key in your environment, or in a profile's
1029
+ `env.NAME: value` lines in `~/.brindle/agents` (never in the repo), and
1030
+ `brindle doctor` names the variable each CLI gets, from your shell or from
1031
+ which profiles.
1032
+
1067
1033
  ### Cheap workers
1068
1034
 
1069
1035
  By default a Claude Code worker loads everything your own `claude` does: your
@@ -1298,6 +1264,15 @@ curl -fsSL https://antigravity.google/cli/install.sh | bash
1298
1264
  agy # sign in with your Google account, then quit
1299
1265
  ```
1300
1266
 
1267
+ To run unattended workers without a browser login, `agy` (1.1.13 or newer) can use
1268
+ a Gemini API key instead: put `"modelProvider": "gemini"` in
1269
+ `~/.gemini/antigravity-cli/settings.json` and set `GEMINI_API_KEY`, in your
1270
+ environment or in a profile's `env.GEMINI_API_KEY: ...` line (keep the key out of
1271
+ the repo). The key alone does nothing: without `modelProvider`, `agy` still asks
1272
+ you to sign in. `agy` takes no other key or token from the environment; its
1273
+ Google Cloud, Workforce Identity and Application Default Credentials sign-ins are
1274
+ chosen on its sign-in screen.
1275
+
1301
1276
  Then run the whole session on it with `brindle --provider antigravity`, or mix models:
1302
1277
  give a profile `provider: antigravity` (for example a `gemini-reviewer` for a second
1303
1278
  model's review) and the supervisor can hand it tasks.