brindle 0.4.0__tar.gz → 0.4.2__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 (162) hide show
  1. {brindle-0.4.0 → brindle-0.4.2}/PKG-INFO +149 -29
  2. {brindle-0.4.0 → brindle-0.4.2}/README.md +148 -28
  3. {brindle-0.4.0 → brindle-0.4.2}/pyproject.toml +1 -1
  4. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/agents.py +250 -41
  5. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/antigravity.py +205 -52
  6. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/autopilot.py +43 -51
  7. brindle-0.4.2/src/brindle/builtin_agents/developer-opencode.md +6 -0
  8. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/builtin_agents/reviewer-codex.md +3 -1
  9. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/builtin_agents/reviewer-gemini.md +6 -2
  10. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/builtin_agents/reviewer-local.md +3 -1
  11. brindle-0.4.0/src/brindle/builtin_agents/reviewer.md → brindle-0.4.2/src/brindle/builtin_agents/reviewer-opencode.md +6 -8
  12. brindle-0.4.2/src/brindle/builtin_agents/reviewer.md +36 -0
  13. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/builtin_agents/supervisor.md +1 -1
  14. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/cli.py +70 -9
  15. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/codemap.py +35 -0
  16. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/config.py +7 -5
  17. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/cull.py +10 -2
  18. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/db.py +75 -2
  19. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/demo.py +3 -0
  20. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/doctor.py +9 -5
  21. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/gates.py +186 -36
  22. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/git.py +13 -1
  23. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/inbox.py +5 -0
  24. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/launch_health.py +250 -15
  25. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/mcp_server.py +58 -6
  26. brindle-0.4.2/src/brindle/native/catalog.py +192 -0
  27. brindle-0.4.2/src/brindle/native/lsp.py +293 -0
  28. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/native/permissions.py +7 -3
  29. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/native/runner.py +10 -1
  30. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/native/tools.py +36 -5
  31. brindle-0.4.2/src/brindle/opencode.py +461 -0
  32. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/permissions.py +173 -29
  33. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/picker.py +3 -1
  34. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pipeline.py +125 -55
  35. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pro/account.py +36 -15
  36. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pro/team_policy.py +1 -1
  37. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/procs.py +1 -1
  38. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/profiles.py +35 -1
  39. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/prompt_answer.py +63 -29
  40. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/providers.py +167 -34
  41. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/self_serve.py +74 -2
  42. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/sessions.py +18 -10
  43. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/sidebar_follow.py +5 -0
  44. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/tmux.py +10 -0
  45. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/view.py +1 -0
  46. brindle-0.4.2/src/brindle/wake.py +250 -0
  47. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/watch.py +3 -2
  48. brindle-0.4.2/src/brindle/web/__init__.py +5 -0
  49. brindle-0.4.2/src/brindle/web/chat.py +125 -0
  50. brindle-0.4.2/src/brindle/web/dashboard.py +51 -0
  51. brindle-0.4.2/src/brindle/web/diff.py +41 -0
  52. brindle-0.4.2/src/brindle/web/server.py +237 -0
  53. brindle-0.4.2/src/brindle/web/static/app.js +270 -0
  54. brindle-0.4.2/src/brindle/web/static/index.html +94 -0
  55. {brindle-0.4.0 → brindle-0.4.2}/.gitignore +0 -0
  56. {brindle-0.4.0 → brindle-0.4.2}/LICENSE +0 -0
  57. {brindle-0.4.0 → brindle-0.4.2}/SCHEDULE-A +0 -0
  58. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/__init__.py +0 -0
  59. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/__main__.py +0 -0
  60. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/account.py +0 -0
  61. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/airgap.py +0 -0
  62. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/assembler.py +0 -0
  63. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/assets/braid_dark.png +0 -0
  64. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/assets/braid_light.png +0 -0
  65. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/budget.py +0 -0
  66. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/builtin_agents/__init__.py +0 -0
  67. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/builtin_agents/backend-auditor.md +0 -0
  68. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/builtin_agents/coordinator.md +0 -0
  69. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/builtin_agents/developer-codex.md +0 -0
  70. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/builtin_agents/developer-fable.md +0 -0
  71. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/builtin_agents/developer-gemini.md +0 -0
  72. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/builtin_agents/developer-heavy.md +0 -0
  73. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/builtin_agents/developer-light.md +0 -0
  74. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/builtin_agents/developer-local.md +0 -0
  75. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/builtin_agents/developer.md +0 -0
  76. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/builtin_agents/qa.md +0 -0
  77. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/builtin_agents/subagent.md +0 -0
  78. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/builtin_rules/__init__.py +0 -0
  79. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/builtin_rules/security/backend.md +0 -0
  80. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/builtin_rules/style/minimal-diff.md +0 -0
  81. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/builtin_rules/tests/only.md +0 -0
  82. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/ci_adapters.py +0 -0
  83. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/ci_client.py +0 -0
  84. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/ci_federation.py +0 -0
  85. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/ci_hosts.py +0 -0
  86. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/cloud_credential.py +0 -0
  87. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/cloud_login.py +0 -0
  88. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/codemap_docs.py +0 -0
  89. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/codex_hook.py +0 -0
  90. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/company_identity.py +0 -0
  91. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/company_login.py +0 -0
  92. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/conflicts.py +0 -0
  93. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/coordinator.py +0 -0
  94. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/cost.py +0 -0
  95. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/cost_estimate.py +0 -0
  96. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/detect.py +0 -0
  97. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/escapes.py +0 -0
  98. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/events.py +0 -0
  99. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/guardrails.py +0 -0
  100. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/history.py +0 -0
  101. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/identity_lock.py +0 -0
  102. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/kb.py +0 -0
  103. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/key_check.py +0 -0
  104. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/keys_cmds.py +0 -0
  105. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/keystore.py +0 -0
  106. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/knowledge.py +0 -0
  107. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/learned_rules.py +0 -0
  108. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/learning.py +0 -0
  109. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/legacy.py +0 -0
  110. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/local_setup.py +0 -0
  111. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/login_pause.py +0 -0
  112. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/model_access.py +0 -0
  113. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/model_availability.py +0 -0
  114. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/native/__init__.py +0 -0
  115. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/native/client.py +0 -0
  116. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/native/loop.py +0 -0
  117. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/native/serve.py +0 -0
  118. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pane_auth.py +0 -0
  119. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/plugins.py +0 -0
  120. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/policy.py +0 -0
  121. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pool.py +0 -0
  122. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pricing.py +0 -0
  123. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pro/__init__.py +0 -0
  124. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pro/_files.py +0 -0
  125. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pro/audit_chain.py +0 -0
  126. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pro/audit_export.py +0 -0
  127. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pro/auth.py +0 -0
  128. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pro/brief_learning.py +0 -0
  129. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pro/cost_centers.py +0 -0
  130. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pro/credentials.py +0 -0
  131. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pro/features.py +0 -0
  132. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pro/keys.py +0 -0
  133. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pro/learning.py +0 -0
  134. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pro/license.py +0 -0
  135. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pro/loopback.py +0 -0
  136. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pro/managed_models.py +0 -0
  137. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pro/org_profiles.py +0 -0
  138. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pro/orgkey.py +0 -0
  139. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pro/rollout.py +0 -0
  140. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pro/route_health.py +0 -0
  141. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pro/settings_sync.py +0 -0
  142. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pro/status.py +0 -0
  143. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/pro/team_events.py +0 -0
  144. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/quota.py +0 -0
  145. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/repo_cmds.py +0 -0
  146. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/repos.py +0 -0
  147. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/rewind.py +0 -0
  148. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/rule_checks.py +0 -0
  149. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/savings.py +0 -0
  150. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/scratch.py +0 -0
  151. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/secrets.py +0 -0
  152. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/services.py +0 -0
  153. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/sidebar_logo.py +0 -0
  154. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/signin_pause.py +0 -0
  155. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/status_cache.py +0 -0
  156. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/tasks.py +0 -0
  157. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/updates.py +0 -0
  158. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/upgrade.py +0 -0
  159. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/usage.py +0 -0
  160. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/watch_costs.py +0 -0
  161. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/worker_models.py +0 -0
  162. {brindle-0.4.0 → brindle-0.4.2}/src/brindle/workspaces.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brindle
3
- Version: 0.4.0
3
+ Version: 0.4.2
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
@@ -42,13 +42,20 @@ that exact commit and your checks pass. A goal is done only when its check comma
42
42
  exit 0.
43
43
 
44
44
  ```sh
45
- curl -fsSL pawdelta.com/brindle/install | sh
45
+ curl -fsSL pawdelta.com/brindle/install | sh # installs uv and tmux if missing, then brindle
46
+ # or, if you already have uv and tmux:
47
+ uv tool install brindle
48
+
46
49
  brindle demo # watch it finish a practice repo in a few minutes
47
50
  ```
48
51
 
49
- ![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)
52
+ brindle is free to use, including at work, under the [brindle License 1.0](LICENSE): you
53
+ may install and run it, but not modify or redistribute it or use it to build a competing
54
+ service ([details](#license)).
50
55
 
51
- *`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.*
56
+ ![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://raw.githubusercontent.com/PawDelta/brindle/main/assets/brindle-demo.gif)
57
+
58
+ *`brindle demo`, recorded on brindle 0.4.1 (sped up about 2.7×, 20 seconds): two workers in parallel, each branch reviewed and merged, both milestones verified by their check commands.*
52
59
 
53
60
  - **Done means a command passed.** A goal is split into milestones, each with a
54
61
  check command that brindle runs itself. A milestone is verified when its check exits
@@ -94,7 +101,7 @@ uv tool install --editable ~/Projects/brindle # or from a local checkout
94
101
  brindle drives Claude Code, so you need that too (`npm install -g @anthropic-ai/claude-code`).
95
102
 
96
103
  `brindle --version` prints the installed version; the tmux status bar of every brindle
97
- session shows it too (`brindle 0.4.0`). A session started before an upgrade keeps
104
+ session shows it too (`brindle 0.4.2`). A session started before an upgrade keeps
98
105
  running the old code, and shows the old number, until you restart it.
99
106
 
100
107
  ## Quick start
@@ -115,7 +122,8 @@ as `brindle doctor`. Commit the config so your team gets it too.
115
122
  New to brindle? `brindle demo` runs it on a tiny practice repo (under `~/.brindle/demo/`)
116
123
  with two failing test files and a two-milestone goal: in a few minutes you watch two
117
124
  workers fix them in parallel, each branch get reviewed and merged, and both milestones
118
- turn green only once their test command passes. `brindle demo --local` keeps the workers
125
+ turn green only once their test command passes (the demo repo's config sets
126
+ `"delegation": "fast"`, so the supervisor always splits the work). `brindle demo --local` keeps the workers
119
127
  and reviewers on a local model (Ollama).
120
128
 
121
129
  `brindle doctor` checks that everything brindle needs is there (tmux, the agent CLIs,
@@ -347,6 +355,11 @@ or once its goal was replaced from the chat. From a linked worktree, it writes
347
355
  the `goals.md` it loaded (the main checkout's), never another session's. A
348
356
  write that fails never fails the check.
349
357
 
358
+ A new session takes a hand-written `goals.md` over by moving it to its own
359
+ `.brindle/goals/<session id>.md` (git-ignored). If `goals.md` was committed,
360
+ its deletion is left for you to commit or restore; brindle doesn't count it as
361
+ an uncommitted change, so it never blocks a merge or a milestone's check.
362
+
350
363
  `brindle autopilot` shows progress, `brindle autopilot check` runs the checks
351
364
  now, and `brindle autopilot off` (or `on`) hands the wheel back (or takes it
352
365
  again). `brindle --no-autopilot`, or `"autopilot": false` in the repo config,
@@ -399,6 +412,7 @@ your own status line prints, so what you see doesn't change.
399
412
  | `brindle login` | the same as `brindle account login`; when your org's policy has an `agent_setup` block it also signs you in to the Claude and Codex route the company chose (AWS SSO profile, `gcloud`, `az`, `claude auth login`, `codex login`), stores the Claude Code environment for brindle's panes in `~/.brindle/config.json`, and checks the pinned models. `brindle doctor --fix` does the same |
400
413
  | `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) |
401
414
  | `brindle watch [--all] [--once]` | the dashboard on its own (the same view as the sidebar): enter attaches, `p` peeks, `x` closes |
415
+ | `brindle web [--port N] [--no-open]` | this session in your browser (see "Web UI") |
402
416
  | `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 |
403
417
  | `brindle attach / cd / open [WS]` | tmux session (at the agent waiting on you, else the busiest or newest) / path / editor |
404
418
  | `brindle status / diff [--stat] [WS]` | compared with the base branch (committed + uncommitted) |
@@ -421,6 +435,21 @@ your own status line prints, so what you see doesn't change.
421
435
 
422
436
  With no `WS` argument, commands act on the workspace you're in.
423
437
 
438
+ ### Web UI
439
+
440
+ `brindle web` serves the current session to your browser: workspaces, agents
441
+ and their status, and the goal's milestones with each one's last check. Run
442
+ it from a terminal in the session's tmux session to show that session's
443
+ goal; elsewhere it shows the newest autopilot session
444
+ in the repo. The page refreshes itself every couple of seconds.
445
+
446
+ It listens on `127.0.0.1` only, on a free port (or `--port N`), and prints a
447
+ link with a random token, `http://127.0.0.1:PORT/?token=...`, which it also
448
+ opens. The token is new each run and is needed on every request (the first
449
+ one sets a cookie, so the page drops it from the address bar). Requests
450
+ naming another host, or changing anything from another site's page, are
451
+ refused. Ctrl-C stops it.
452
+
424
453
  ### Agent tools
425
454
 
426
455
  Agents launched by brindle get these MCP tools. You don't call them yourself, but
@@ -651,6 +680,7 @@ Autopilot, merge gates and cleanup:
651
680
  | `usage_limit` | `90` | autopilot stops pushing on at this % of your Claude usage limit |
652
681
  | `limit_cooldown_minutes` | `300` for Antigravity | how long a provider that hit its limit counts as unavailable |
653
682
  | `graphify` | if the graph is there | point agents at the repo's [graphify](https://github.com/safishamsi/graphify) code map (`false` turns it off) |
683
+ | `graphify_auto_refresh` | `true` | automatically rebuild untracked code maps; `false` disables rebuilds while keeping queries available |
654
684
  | `stale_after` | `30` | minutes before a worker that reported and sat idle is closed, or `brindle prune` closes a stale paused/exited agent whose workspace is merged or gone (`0`: never) |
655
685
  | `pipeline` | `true` | brindle reviews and merges reported branches itself; the supervisor gets one message per branch |
656
686
  | `review_rounds` | `2` | fix-and-re-review rounds the pipeline runs before handing findings to the supervisor |
@@ -658,7 +688,7 @@ Autopilot, merge gates and cleanup:
658
688
  | `merge_into` | none | branch that worker branches are cut from and merge into, whatever branch the supervisor is on |
659
689
  | `auto_merge_default_branch` | `false` | let the pipeline merge into the repo's default branch (origin HEAD, else `main`/`master`) on its own; by default it sends a "needs you" message instead, and you run `merge_workspace` yourself (manual merges are never gated) |
660
690
  | `permission_policy` | `"off"` | `"on"`: brindle answers Claude Code workers' permission prompts from its rules (see "Permission policy" below); off, the prompts behave as they always did |
661
- | `agy_approvals` | `"prompt"` | `"brindle"`: with the policy on, Antigravity workers run without approval prompts and brindle's hook allows or denies every call (see "Antigravity" under "Permission policy") |
691
+ | `agy_approvals` | unset | `"brindle"`: Antigravity agents run without approval prompts and brindle's hook allows or denies every call, applying the profile's `allowed_tools` (see "Antigravity" under "Permission policy"); `"prompt"`: agy asks as usual. Unset, workers get `"brindle"` and interactive agy sessions `"prompt"` |
662
692
  | `rules` | `[]` | standing rules for the supervisor, e.g. `["Fix review findings without asking", "Validate options before offering them"]`; added to every supervisor's brief. Rules in `~/.brindle/config.json`, the repo's config and `config.local.json` are all kept, so a team's rules and your own add up |
663
693
  | `plan_first` | `false` | workers propose a plan (`submit_plan`) and wait for `approve_plan` before editing |
664
694
  | `overlap` | unset | a task whose `files` overlap a running task's: `"block"` refuses it, `"warn"` starts it with a warning, `"queue"` queues it until that task merges. Unset, it is refused within a session and queued behind another session's task (both sessions' supervisors are told; the other session's work is never cancelled), and the queued task starts from that task's merge commit |
@@ -931,7 +961,8 @@ A session override of `features.hooks=false` reported false. No feature-enable
931
961
  flag is needed on these versions, and brindle leaves explicit disablement alone.
932
962
 
933
963
  **Antigravity.** agy's hook can deny but its "allow" is ignored (an agy bug),
934
- and it has no "no opinion" answer. So the hook denies what the policy denies,
964
+ and it has no "no opinion" answer. So, where agy prompts (an interactive agy
965
+ session, or any agy agent under `"agy_approvals": "prompt"`), the hook denies what the policy denies,
935
966
  answers "allow" for a file inside the workspace (which agy reads and writes
936
967
  without asking anyway) and "ask" for everything else, and brindle copies the
937
968
  rules agy can express exactly into your `~/.gemini/antigravity-cli/settings.json`
@@ -946,8 +977,10 @@ does it by hand. To undo, turn `permission_policy` off and run `sync-agy`: brind
946
977
  removes exactly its own entries. brindle doesn't learn from agy approvals (its
947
978
  hook runs for every tool call, so a tool running doesn't mean you approved it).
948
979
 
949
- With `"agy_approvals": "brindle"` (and the policy on), brindle's hook is the
950
- only gate instead: agy workers start with `--dangerously-skip-permissions`,
980
+ For workers (any agy agent but an interactive one) when `agy_approvals` is
981
+ unset, and for every agy agent under `"agy_approvals": "brindle"`, whether or
982
+ not the policy is on, brindle's hook is the
983
+ only gate instead: they start with `--dangerously-skip-permissions`,
951
984
  under which agy still honors a hook's deny, and the hook runs on every tool,
952
985
  subagents' calls included. It allows what a rule allows (a repo's checks
953
986
  included), the shell commands in the profile's `allowed_tools` (what a Claude
@@ -955,11 +988,20 @@ Code worker on it runs without asking), files inside the workspace given as one
955
988
  absolute path (writes under `.agents/`, `.git/` and
956
989
  `.brindle/` only by a rule, since those hold the hook, git's hooks and the
957
990
  repo's checks), brindle's own tools and agy's tools that act only on the
958
- conversation, and denies everything else, telling the agent to
959
- ask its supervisor; you allow it with `brindle permissions allow`. Any failure
991
+ conversation, and denies everything else. A call no rule covers (not one a
992
+ rule denies) is what a Claude Code worker would wait on you for: brindle
993
+ records it as the worker's pending request and tells its supervisor, once per
994
+ request in a turn, with the `brindle permissions allow ...` commands that allow
995
+ it (one per part of a compound command that no rule covers yet); once you run
996
+ them, the worker's retry goes through. A command no rule can allow as written
997
+ (`$(...)`, a redirection that writes, a heredoc) comes with that instead, so
998
+ the worker can split it into simple commands. A `dontAsk` profile (a
999
+ reviewer) and an agent with no supervisor are only told to ask. Any failure
960
1000
  in the hook is a deny, and a tool brindle doesn't know is denied. Nobody can
961
- approve a prompt in the agent's pane in this mode. The default, `"prompt"`,
962
- leaves agy asking as described above.
1001
+ approve a prompt in the agent's pane in this mode, so a worker never waits at
1002
+ one nobody is watching. `"prompt"` leaves agy asking as described above (and
1003
+ `assign`/`handoff` name the profile's `allowed_tools` agy's own settings don't
1004
+ already allow).
963
1005
 
964
1006
  ## brindle Pro and Team
965
1007
 
@@ -1003,7 +1045,7 @@ brindle account login # opens your browser to sign in (--device: enter a co
1003
1045
  brindle account status # your plan, features, hosted learning on or off, when the entitlement expires
1004
1046
  brindle account savings # what hosted learning's picks gained in this repo, this month and last: estimates, from this machine's records only
1005
1047
  brindle account upgrade # opens the checkout for brindle Pro (and prints its URL)
1006
- brindle account upgrade --enterprise [--trial] --seats N --org ORG # brindle Enterprise for a team org; --trial: free trial, at most 10 seats
1048
+ brindle account upgrade --enterprise # brindle Enterprise is set up by contact (prints support@pawdelta.com); not a checkout
1007
1049
  brindle account portal # opens the billing portal (invoices, payment method, cancellation); --org ORG for a team org
1008
1050
  brindle account seats N # set a Team or Enterprise org's seat count (billing admin); --org ORG; Stripe prorates the change
1009
1051
  brindle account org list # the orgs you belong to; `org use <id>` switches, `org policy` shows the current one
@@ -1454,7 +1496,11 @@ knowledge graph (`graphify-out/graph.json`, built with `/graphify`) and `graphif
1454
1496
  installed, brindle tells the supervisor and every worker to find code with
1455
1497
  `graphify query` before grepping or reading whole files. Those commands are
1456
1498
  pre-approved, and brindle refreshes the graph's code (`graphify update`, no LLM) in
1457
- the background after each merge.
1499
+ the background after each merge. Automatic rebuilds skip checkouts with any
1500
+ git-tracked files under `graphify-out/`, preserving richer semantic graphs and
1501
+ their generated reports. Update tracked graphs explicitly with graphify.
1502
+ Set `"graphify_auto_refresh": false` in `.brindle/config.json` to also disable
1503
+ automatic rebuilds and session seeding for untracked graphs while keeping queries.
1458
1504
 
1459
1505
  When `pool_size` is greater than `0`, a claimed worktree keeps the path and
1460
1506
  port block it was built with -- it's never moved, and its port block is fixed
@@ -1536,6 +1582,7 @@ change files under a directory without prompts.
1536
1582
  | `claude` (Claude Code) | yes (the default) | yes |
1537
1583
  | `codex` | yes | yes |
1538
1584
  | `antigravity` (`agy`) | yes | yes |
1585
+ | `opencode` (OpenCode) | yes | yes (`developer-opencode`, `reviewer-opencode`) |
1539
1586
  | `native` (local models) | no: its loop has only the worker tools (report, message, diff), not `assign` or `handoff` | yes |
1540
1587
  | `subagent` | no: it runs inside a supervisor's own Agent tool | yes, through `assign`/`handoff` |
1541
1588
  | `shell` | only as a stand-in, for testing brindle itself | a plain shell, for dev servers and testing brindle |
@@ -1561,6 +1608,7 @@ it's signed in without a plan that includes it, is reported to its supervisor.
1561
1608
  | `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`) |
1562
1609
  | `codex` | yes: `OPENAI_API_KEY`, `CODEX_API_KEY`. With Codex signed in with ChatGPT (checked on Codex CLI 0.162.0): interactive `codex`, which brindle runs in agent panes, ignores both keys and uses the ChatGPT plan, and only `codex login --with-api-key` switches it to a key; headless `codex exec` (Brindle-CI) bills `CODEX_API_KEY` over the login and ignores `OPENAI_API_KEY`. `brindle doctor` and `brindle keys set` say so, and `auth: api_key` on an interactive Codex profile is refused under a ChatGPT login (run `codex login --with-api-key`, or use `auth: subscription`) |
1563
1610
  | `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 |
1611
+ | `opencode` | whatever the model's provider takes (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, ...), or `opencode auth login`; its free `opencode/...` models need neither |
1564
1612
 
1565
1613
  Workers that run unattended should use key login where the CLI supports it.
1566
1614
  A personal login is one interactive session with one quota shared by every
@@ -1595,7 +1643,39 @@ When running long, unattended autopilot sessions with API keys:
1595
1643
  - **Recovering failed/blocked workers**: When an agent fails on an expired key or autopilot pauses with a credential error, export the fresh key (or update `brindle keys set`), then run `brindle continue`. This unpauses the session and relaunches paused/failed agents with the new credentials. The replacement credentials are automatically synced to the session credential store, dynamically refreshed by the supervisor's launcher on all subsequent worker delegations (`assign`/`handoff`).
1596
1644
  - **Proactive rotation in live sessions**: For Claude keys rotated outside brindle, set Claude Code's `apiKeyHelper` in `~/.claude/settings.json` or the project's `.claude/settings.json`. Claude Code re-runs the helper every 5 minutes (configurable with `CLAUDE_CODE_API_KEY_HELPER_TTL_MS`) and after a 401, so running workers pick up the new key. Don't also export `ANTHROPIC_API_KEY` or set a Claude key with `brindle keys set`, since an environment key takes precedence over the helper. Codex and Antigravity keys still need workers stopped, then `brindle keys set`, then `brindle continue`. Exporting a new key while an active session is healthy does not restart running workers or the supervisor (running `brindle continue` on an active healthy session simply reattaches to tmux without restarting workers). To proactively rotate credentials for the entire session and all running workers: run `brindle stop` -> update credentials in your environment or via `brindle keys set` -> run `brindle continue`.
1597
1645
  - **Precedence**: Newly stored keys (`brindle keys set`) or session credentials updated during recovery take effect dynamically for all new panes, overriding stale launcher environments. For profile-level overrides, an explicit profile key (`env.NAME`) takes precedence over default provider credentials.
1598
- - **Antigravity permissions prerequisite**: Antigravity (`agy`) does not support approving shell commands solely via external hooks. With `GEMINI_API_KEY`, unattended agy workers will still stall on approval prompts unless permissions are pre-populated in `settings.json` (see Permissions) or run with `agy_approvals: "brindle"` under `permission_policy: "on"` (`--dangerously-skip-permissions`).
1646
+ - **Antigravity permissions prerequisite**: Antigravity (`agy`) does not support approving shell commands solely via external hooks. brindle therefore runs agy workers with `--dangerously-skip-permissions` and its own hook as the gate, applying their profile's `allowed_tools` and denying the rest (see Permissions). Under an explicit `agy_approvals: "prompt"` they instead stall on approval prompts for anything `settings.json` doesn't allow, their profile's `allowed_tools` included.
1647
+
1648
+ #### OpenCode
1649
+
1650
+ [OpenCode](https://opencode.ai) (`npm install -g opencode-ai`) runs in an agent
1651
+ pane like the other CLIs; `brindle doctor` says whether it's installed. Run a
1652
+ supervisor on it with `brindle -s opencode`, or workers with the built-in
1653
+ `developer-opencode` and `reviewer-opencode` profiles (or any profile with
1654
+ `provider: opencode`). Checked with OpenCode 1.19.3.
1655
+
1656
+ brindle writes nothing into the worktree. It hands OpenCode its MCP server, a
1657
+ small plugin and the profile's prompt through `OPENCODE_CONFIG_CONTENT`, which
1658
+ OpenCode merges over your own config, so your providers, models and other MCP
1659
+ servers still apply. The plugin reports each turn's start, its end (and any
1660
+ error, such as a rate limit), and permission prompts from OpenCode's own
1661
+ events, so a worker's status never depends on reading its screen.
1662
+
1663
+ | Profile field | OpenCode |
1664
+ |---|---|
1665
+ | `model` | `--model`. Use OpenCode's `provider/model` form (`anthropic/claude-sonnet-4-5`, `opencode/big-pickle`); a bare `claude-…`, `gpt-…` or `gemini-…` name gets its provider added. With no model, OpenCode's own default. |
1666
+ | `allowed_tools` | Each `Bash(cmd:*)` becomes an allowed `bash` pattern; any other command asks. |
1667
+ | `permission_mode` | `bypassPermissions`: everything runs. `dontAsk`: only `allowed_tools` commands run, edits and web fetches are refused. `acceptEdits`/`auto`: edits run. `plan`: OpenCode's `plan` agent. Unset: edits ask too. |
1668
+ | `add_dirs`, `worker_extra_read_dirs` | Allowed as external directories. |
1669
+
1670
+ A `dontAsk` OpenCode worker (the built-in reviewer, for one) can't read outside
1671
+ its worktree or its `add_dirs`: with nobody to ask, anything else is refused.
1672
+
1673
+ `effort` isn't passed (OpenCode's terminal UI has no flag for it). With
1674
+ `permission_policy` on, brindle's plugin also asks brindle before every tool
1675
+ call and stops the ones the policy denies (as for Claude Code and Codex
1676
+ workers); calls the policy allows or asks about are left to the profile's
1677
+ permissions above, so the policy can narrow what an OpenCode worker does but
1678
+ never widens it.
1599
1679
 
1600
1680
  ### Cheap workers
1601
1681
 
@@ -1886,6 +1966,17 @@ No hooks, no screen scraping, and messages sent to the worker arrive between
1886
1966
  its model calls. It works with any OpenAI-compatible chat-completions endpoint
1887
1967
  or Anthropic Messages endpoint.
1888
1968
 
1969
+ The native worker automatically starts language servers found on `PATH` when it
1970
+ first reads or changes a supported file (Python: `pyright-langserver` or `pylsp`;
1971
+ JS/TS: `typescript-language-server`; Go: `gopls`; Rust: `rust-analyzer`; C/C++:
1972
+ `clangd`; Ruby: `ruby-lsp`; Lua: `lua-language-server`). Servers use the worktree
1973
+ as their root and stop with the worker. `Diagnostics`, `Definition`, and
1974
+ `References` provide code information; diagnostics also follow successful Edit
1975
+ and Write calls. Navigation takes 1-based lines and character columns. Explicit
1976
+ LSP tools report missing servers; ordinary file tools stay silent when no server
1977
+ can run. Profiles with a narrowed read scope skip language
1978
+ servers because workspace indexing cannot enforce that boundary.
1979
+
1889
1980
  ```markdown
1890
1981
  ---
1891
1982
  name: developer-local
@@ -1947,6 +2038,14 @@ Other endpoints, same fields:
1947
2038
  | DeepSeek | anthropic | `https://api.deepseek.com/anthropic` | paid; `api_key_env: DEEPSEEK_API_KEY` |
1948
2039
  | Anthropic | anthropic | `https://api.anthropic.com` | `api_key_env: ANTHROPIC_API_KEY` |
1949
2040
 
2041
+ Or name any [Models.dev](https://models.dev) model as `model: <provider>/<model>`
2042
+ (say `nano-gpt/deepseek/deepseek-v3`) and leave out `base_url`: brindle fills in
2043
+ `base_url`, `api`, `api_key_env` and `context_tokens` from the catalog, and
2044
+ anything you set in the profile wins. The catalog is cached in
2045
+ `~/.brindle/models-catalog.json` for a day. It is never fetched in air-gap mode
2046
+ or offline; brindle uses the cached copy, and says so if there isn't one. A profile
2047
+ with its own `base_url` is left alone, so OpenRouter-style ids still work.
2048
+
1950
2049
  What to expect: a 30B-class local model does well on small, well-specified
1951
2050
  tasks (the kind brindle hands out: one change, the test named up front) and on
1952
2051
  reviews of modest diffs, and less well on long multi-step work. The native
@@ -2098,10 +2197,25 @@ you already have them, and won't change one that's committed. Each agent's first
2098
2197
  message is a short warm-up with its instructions, because `agy` connects MCP servers
2099
2198
  only once a conversation has started.
2100
2199
 
2101
- **Permissions.** `agy` doesn't let hooks approve shell commands, so an Antigravity
2102
- agent asks before running anything your own `agy` settings don't already allow, and
2103
- the sidebar shows it as needing you. To let agents run tests and commit without asking,
2104
- add rules to `~/.gemini/antigravity-cli/settings.json`, for example:
2200
+ **Permissions.** `agy` doesn't let hooks approve shell commands, so by default
2201
+ brindle runs Antigravity workers with `--dangerously-skip-permissions` and its
2202
+ own hook as the only gate (see "Antigravity" under "Permission policy"): a
2203
+ profile's `allowed_tools` shell entries and brindle's rules apply as they do
2204
+ for a Claude Code worker, and a call nothing allows is denied, with its
2205
+ supervisor told how you can allow it (a `brindle permissions allow` you run),
2206
+ never left at a prompt nobody is watching. A profile's `permission_mode` maps to agy's modes
2207
+ (`auto` to agy's Auto mode, `acceptEdits` to accept-edits, `plan` to plan;
2208
+ `dontAsk` runs brindle's hook as the only gate even in an interactive session).
2209
+
2210
+ An interactive agy session, or any agy agent under `"agy_approvals": "prompt"`,
2211
+ asks before running anything your own `agy` settings don't already allow, and
2212
+ the sidebar shows it as needing you. There a profile's `allowed_tools` don't
2213
+ apply by themselves: agy reads no hook's "allow", and its only allow list,
2214
+ `~/.gemini/antigravity-cli/settings.json`, is shared by every agy session, so brindle
2215
+ doesn't add one worker's rules there (another worker, or you, would get them too).
2216
+ `assign` and `handoff` name the `allowed_tools` that settings file doesn't already
2217
+ allow. To let such agents run tests and commit without asking,
2218
+ add rules to that file yourself, for example:
2105
2219
 
2106
2220
  ```json
2107
2221
  { "permissions": { "allow": ["command(uv run pytest)", "command(git status)",
@@ -2117,14 +2231,20 @@ add rules to `~/.gemini/antigravity-cli/settings.json`, for example:
2117
2231
  and every agent's MCP server share it. Worktrees live in
2118
2232
  `~/.brindle/worktrees/<repo>/<branch>`, and the base branch is recorded in git
2119
2233
  config as `branch.<b>.brindle-base`.
2120
- - **Messages arrive through Claude Code's own inbox.** Every Claude Code session
2121
- listens on a socket for messages from other sessions, and tells its hooks where
2122
- it is; brindle's SessionStart hook records it, and from then on messages go there
2123
- instead of being typed into the pane. An idle agent starts a new turn with the
2124
- message; a busy one gets it between tool calls. Typing into the pane is the
2125
- fallback when there's no socket (Codex, Antigravity, older Claude Code). A
2126
- `crossSessionInbound` of `hold` or `refuse` in your Claude settings would hold or
2127
- drop them.
2234
+ - **Messages wake a supervisor through brindle's own hook.** Your interactive
2235
+ Claude Code chat runs a small background waiter (an `asyncRewake` hook started
2236
+ at `SessionStart` and at every `Stop`). When a worker reports, or brindle has a
2237
+ "new messages" notice or an autopilot nudge, the waiter hands it over and Claude
2238
+ Code wakes the model with it: an idle chat starts a new turn, a busy one gets it
2239
+ between steps, and the chat shows one "Stop hook feedback" line. Each message
2240
+ is wrapped as `<brindle-msg nonce="..." from="developer agent w1">...</brindle-msg>`
2241
+ with a fresh random nonce every hand-over, and `from` comes from who really sent
2242
+ it, never from its text: a worker can't make its words read as brindle's or yours.
2243
+ A session paused on an expired cloud login gets its mail once the login is back.
2244
+ Workers and older sessions get messages through Claude Code's own inbox
2245
+ (another-session messages; a `crossSessionInbound` of `hold` or `refuse` in your
2246
+ Claude settings would hold or drop them). Typing into the pane is the fallback
2247
+ when neither is there (Codex, Antigravity, older Claude Code).
2128
2248
  - **Agent status comes from hooks, not screen-scraping.** Guessing an agent's
2129
2249
  state by pattern-matching terminal output breaks whenever a CLI redesigns its
2130
2250
  interface. brindle launches Claude Code with `--settings` hooks