brindle 0.0.6__tar.gz → 0.2.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 (114) hide show
  1. {brindle-0.0.6 → brindle-0.2.0}/PKG-INFO +343 -15
  2. {brindle-0.0.6 → brindle-0.2.0}/README.md +342 -14
  3. {brindle-0.0.6 → brindle-0.2.0}/pyproject.toml +7 -2
  4. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/agents.py +199 -27
  5. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/autopilot.py +63 -9
  6. brindle-0.2.0/src/brindle/budget.py +374 -0
  7. brindle-0.2.0/src/brindle/builtin_agents/backend-auditor.md +12 -0
  8. brindle-0.2.0/src/brindle/builtin_agents/qa.md +9 -0
  9. brindle-0.2.0/src/brindle/builtin_rules/__init__.py +1 -0
  10. brindle-0.2.0/src/brindle/builtin_rules/security/backend.md +24 -0
  11. brindle-0.2.0/src/brindle/builtin_rules/style/minimal-diff.md +10 -0
  12. brindle-0.2.0/src/brindle/builtin_rules/tests/only.md +12 -0
  13. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/ci_adapters.py +3 -2
  14. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/ci_client.py +152 -28
  15. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/ci_federation.py +5 -0
  16. brindle-0.2.0/src/brindle/ci_hosts.py +313 -0
  17. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/cli.py +601 -23
  18. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/config.py +32 -6
  19. brindle-0.2.0/src/brindle/conflicts.py +211 -0
  20. brindle-0.2.0/src/brindle/cost.py +422 -0
  21. brindle-0.2.0/src/brindle/cost_estimate.py +432 -0
  22. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/cull.py +24 -2
  23. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/db.py +164 -4
  24. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/doctor.py +17 -0
  25. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/events.py +62 -0
  26. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/gates.py +28 -6
  27. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/git.py +18 -2
  28. brindle-0.2.0/src/brindle/guardrails.py +244 -0
  29. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/history.py +3 -2
  30. brindle-0.2.0/src/brindle/learned_rules.py +614 -0
  31. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/mcp_server.py +135 -10
  32. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/native/runner.py +3 -1
  33. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/native/tools.py +37 -5
  34. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/pipeline.py +289 -45
  35. brindle-0.2.0/src/brindle/pricing.py +230 -0
  36. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/pro/account.py +102 -8
  37. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/pro/audit_chain.py +100 -15
  38. brindle-0.2.0/src/brindle/pro/audit_export.py +822 -0
  39. brindle-0.2.0/src/brindle/pro/cost_centers.py +225 -0
  40. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/pro/learning.py +21 -4
  41. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/pro/license.py +9 -3
  42. brindle-0.2.0/src/brindle/pro/managed_models.py +218 -0
  43. brindle-0.2.0/src/brindle/pro/org_profiles.py +350 -0
  44. brindle-0.2.0/src/brindle/pro/rollout.py +227 -0
  45. brindle-0.2.0/src/brindle/pro/status.py +591 -0
  46. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/pro/team_events.py +73 -8
  47. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/pro/team_policy.py +245 -10
  48. brindle-0.2.0/src/brindle/profiles.py +625 -0
  49. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/providers.py +19 -1
  50. brindle-0.2.0/src/brindle/rewind.py +447 -0
  51. brindle-0.2.0/src/brindle/rule_checks.py +502 -0
  52. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/savings.py +139 -20
  53. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/scratch.py +34 -2
  54. brindle-0.2.0/src/brindle/secrets.py +191 -0
  55. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/tasks.py +161 -20
  56. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/tmux.py +92 -13
  57. brindle-0.2.0/src/brindle/usage.py +394 -0
  58. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/watch.py +83 -9
  59. brindle-0.2.0/src/brindle/watch_costs.py +298 -0
  60. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/workspaces.py +61 -0
  61. brindle-0.0.6/src/brindle/profiles.py +0 -248
  62. brindle-0.0.6/src/brindle/usage.py +0 -205
  63. {brindle-0.0.6 → brindle-0.2.0}/.gitignore +0 -0
  64. {brindle-0.0.6 → brindle-0.2.0}/LICENSE +0 -0
  65. {brindle-0.0.6 → brindle-0.2.0}/SCHEDULE-A +0 -0
  66. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/__init__.py +0 -0
  67. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/__main__.py +0 -0
  68. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/account.py +0 -0
  69. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/airgap.py +0 -0
  70. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/antigravity.py +0 -0
  71. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/builtin_agents/__init__.py +0 -0
  72. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/builtin_agents/developer-codex.md +0 -0
  73. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/builtin_agents/developer-heavy.md +0 -0
  74. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/builtin_agents/developer-local.md +0 -0
  75. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/builtin_agents/developer.md +0 -0
  76. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/builtin_agents/reviewer-codex.md +0 -0
  77. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/builtin_agents/reviewer-local.md +0 -0
  78. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/builtin_agents/reviewer.md +0 -0
  79. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/builtin_agents/subagent.md +0 -0
  80. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/builtin_agents/supervisor.md +0 -0
  81. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/codemap.py +0 -0
  82. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/codex_hook.py +0 -0
  83. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/demo.py +0 -0
  84. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/detect.py +0 -0
  85. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/inbox.py +0 -0
  86. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/learning.py +0 -0
  87. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/legacy.py +0 -0
  88. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/native/__init__.py +0 -0
  89. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/native/client.py +0 -0
  90. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/native/loop.py +0 -0
  91. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/native/permissions.py +0 -0
  92. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/native/serve.py +0 -0
  93. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/permissions.py +0 -0
  94. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/plugins.py +0 -0
  95. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/policy.py +0 -0
  96. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/pool.py +0 -0
  97. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/pro/__init__.py +0 -0
  98. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/pro/_files.py +0 -0
  99. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/pro/auth.py +0 -0
  100. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/pro/credentials.py +0 -0
  101. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/pro/features.py +0 -0
  102. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/pro/keys.py +0 -0
  103. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/pro/loopback.py +0 -0
  104. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/pro/orgkey.py +0 -0
  105. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/pro/settings_sync.py +0 -0
  106. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/procs.py +0 -0
  107. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/quota.py +0 -0
  108. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/repo_cmds.py +0 -0
  109. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/repos.py +0 -0
  110. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/services.py +0 -0
  111. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/sessions.py +0 -0
  112. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/sidebar_follow.py +0 -0
  113. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/status_cache.py +0 -0
  114. {brindle-0.0.6 → brindle-0.2.0}/src/brindle/view.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brindle
3
- Version: 0.0.6
3
+ Version: 0.2.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.0.6`). A session started before an upgrade keeps
92
+ session shows it too (`brindle 0.2.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
@@ -257,8 +257,8 @@ or put the goal in `.brindle/goals.md`, and it works like a project manager:
257
257
  Claude workers run the task as a Claude Code `/goal` with a finish line, so
258
258
  they keep going until it's met. `assign`/`handoff` take `files` (the
259
259
  paths/globs a task expects to touch, checked for overlap against other
260
- active workers' declared and actually-changed files — a warning, not a
261
- block) and `depends_on` (earlier tasks, by agent id or branch, that must
260
+ active workers' declared and actually-changed files; refused by default,
261
+ a warning with `"overlap": "warn"`) and `depends_on` (earlier tasks, by agent id or branch, that must
262
262
  merge first): a task with unmet dependencies is queued instead of started,
263
263
  and starts automatically, cut from the updated base, once
264
264
  `merge_workspace` resolves them. `list_tasks` shows what's queued.
@@ -351,12 +351,17 @@ your own status line prints, so what you see doesn't change.
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
353
  | `brindle history [--limit N] [--kind K] [--all]` | durable log of worker results, reviews, merges and milestone checks |
354
+ | `brindle cost [--days N] [--all]` | dollar spend at list prices over the last 30 days, by model |
355
+ | `brindle cost report [--days N] [--all]` | Pro: spend by day, profile and goal, cost per merged branch, review pass rate per worker profile |
354
356
  | `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 |
355
357
  | `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") |
356
358
  | `brindle permissions install-codex-hook [--yes]` / `brindle permissions sync-agy` | trust brindle's Codex permission hook now (brindle does it itself when needed); copy your rules into Antigravity's settings (see "Permission policy") |
359
+ | `brindle cost request --usd N --reason "..." [--goal TEXT]` | Enterprise (cost centers): ask an admin to approve spend over your budget. A budget refusal points here. Once approved, this month's limit (or the named goal's) goes up by the approved amount, for the month it was approved in |
360
+ | `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
+ | `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) |
357
362
  | `brindle learning` | whether brindle Pro's hosted learning is on for this repo, and if not, why (nothing is learned on your machine) |
358
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) |
359
- | `brindle audit verify\|export\|pubkey` | the local tamper-evident audit log (brindle Enterprise; see "Audit log" below) |
364
+ | `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) |
360
365
  | `brindle watch [--all] [--once]` | the dashboard on its own (the same view as the sidebar): enter attaches, `p` peeks, `x` closes |
361
366
  | `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 |
362
367
  | `brindle attach / cd / open [WS]` | tmux session (at the agent waiting on you, else the busiest or newest) / path / editor |
@@ -369,6 +374,9 @@ your own status line prints, so what you see doesn't change.
369
374
  | `brindle close AGENT` / `brindle close --exited` | hide an agent (or every stopped one) from the dashboard, stopping it if it's running; its worktree and branch stay |
370
375
  | `brindle send AGENT MSG` | message an agent; waits in its inbox until it's idle |
371
376
  | `brindle agent spawn/kill/peek/profiles` | manage agents |
377
+ | `brindle profile new/lint/show` | write a profile file, check every profile (extends chains, rule packs, patterns), or print one as brindle resolves it |
378
+ | `brindle rules suggest/accept/reject` | (brindle Pro) rules suggested from review findings that keep recurring; accept one into `.brindle/rules/learned.md`, or reject it for good |
379
+ | `brindle agent turns AGENT` / `brindle agent rewind AGENT --to N [--profile P] [--note "..."]` | a worker's worktree is snapshotted after every turn that changed files; rewind puts it back as it was after turn N and starts a fresh session there, briefed on the task, turns 1..N and your note (any provider, Codex included). Supervisors have the `agent_turns` and `rewind_agent` tools |
372
380
  | `brindle setup [WS]` | re-run the repo's setup commands in a workspace |
373
381
  | `brindle mcp` | the MCP server agents talk to (launched for them; you don't run it) |
374
382
 
@@ -381,12 +389,13 @@ knowing them helps when you tell the supervisor how to work.
381
389
 
382
390
  | Tool | Used by | |
383
391
  |---|---|---|
384
- | `assign` / `handoff` / `wait_for_worker` | supervisor | start a worker (return now / wait for its result / keep waiting) |
392
+ | `assign` / `handoff` / `wait_for_worker` | supervisor | start a worker (return now / wait for its result / keep waiting); `dry_run=true` on `assign`/`handoff` starts nothing and reports the profile, what each `files` glob matches, overlaps and whether it would queue |
385
393
  | `send_message` | any agent | message another agent; delivered when it's idle |
386
394
  | `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 |
387
- | `list_agents` / `list_tasks` / `list_agent_profiles` | supervisor | who's running, what's queued, which profiles exist |
395
+ | `list_agents` / `list_tasks` / `list_agent_profiles` | supervisor | who's running, what's queued, which profiles exist; `list_tasks(full=true)` also shows each task's brief and `done_when` |
388
396
  | `list_repos` | supervisor | the repos attached to the session (brindle Pro); `repo=<alias>` on `assign`/`handoff` puts a worker there |
389
397
  | `cancel_task` | supervisor | cancel a queued task (and its dependents) to re-plan |
398
+ | `requeue` | supervisor | queue a cancelled task again with the same brief, files and profile; `depends_on` replaces its dependencies |
390
399
  | `workspace_diff` | supervisor | a worker branch's changes against its base |
391
400
  | `request_review` / `submit_review` | supervisor / reviewer | start a reviewer on a branch / record its verdict |
392
401
  | `merge_workspace` / `remove_workspace` | supervisor | merge through the gates / delete the worktree |
@@ -420,6 +429,51 @@ capped at 5000 rows per repo, oldest dropped first. Recording usage or
420
429
  history never blocks a report, merge or check: a failure there is logged and
421
430
  skipped.
422
431
 
432
+ Codex agents' usage comes from Codex's own session rollouts under
433
+ `~/.codex/sessions/`; agents on other providers (Antigravity) have none, and
434
+ count as unknown.
435
+
436
+ ### Spend in dollars
437
+
438
+ `brindle cost` prices the last 30 days of history at each model's list price
439
+ (per million tokens: input, output, cache write, cache read), copied from
440
+ Anthropic's and OpenAI's pricing pages and dated; brindle warns when they're
441
+ more than 90 days old. Local models cost $0, and usage on a model known to be
442
+ free reads "Free" (its tokens still count; a mix reads "$1.20 (+40k tokens
443
+ free)"). A model brindle has no price for is shown as unpriced tokens, never
444
+ guessed. Add or correct prices under
445
+ `pricing` in `~/.brindle/config.json` or `.brindle/config.json`:
446
+
447
+ ```json
448
+ "pricing": {"my-model": {"input": 1, "output": 5, "cache_write": 1.25, "cache_read": 0.1}}
449
+ ```
450
+
451
+ These are list prices: a subscription or a discount makes the real bill
452
+ lower. `brindle cost report` (Pro) breaks spend down by day, profile and goal,
453
+ with the cost per merged branch and each worker profile's review pass rate,
454
+ and a savings section: the cache-hit rate, what routing to cheaper profiles
455
+ saved against running the same tokens on the most expensive profile in your
456
+ routing, and the spend budgets avoided.
457
+ Forwarded results add the dollar figure to their token line, e.g.
458
+ `tokens: 182k in (160k cached, 20k written) · 9k out · sonnet · ~$0.18`.
459
+
460
+ ### Budgets (Pro)
461
+
462
+ ```json
463
+ "budget": {"task_usd": 5, "goal_usd": 40, "month_usd": 200, "stop": false}
464
+ ```
465
+
466
+ Set in `~/.brindle/config.json`, `.brindle/config.json` or `config.local.json`
467
+ (merged per key, the local file last). When a task routed by `weight` would
468
+ take the task, its goal or the month over a limit (a task's cost is estimated
469
+ from this repo's finished tasks on that profile, else a typical token mix at
470
+ list prices), brindle skips that candidate and uses the next cheaper one. If
471
+ none fits it refuses the assign, says why and, in autopilot, asks you. A
472
+ profile you name yourself is never second-guessed. Running workers are
473
+ reported to the supervisor at 80% of `task_usd` and again at 100%; set
474
+ `"stop": true` to close a worker at 100% instead of only warning. Without the
475
+ Pro `cost` feature budgets do nothing.
476
+
423
477
  ## Provider quota
424
478
 
425
479
  brindle keeps one place (`~/.brindle/quota.json`) that knows how close each
@@ -484,9 +538,10 @@ Autopilot, merge gates and cleanup:
484
538
  | `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 |
485
539
  | `plan_first` | `false` | workers propose a plan (`submit_plan`) and wait for `approve_plan` before editing |
486
540
  | `overlap` | `"block"` | a task whose `files` overlap a running task's is refused (`"warn"` starts it with a warning) |
541
+ | `protected_paths` | `[]` | paths or globs (`"migrations/"`, `"*.lock"`) whose merge conflicts brindle never hands to a worker to resolve: the branch waits for you instead (see "Conflict-aware merging") |
487
542
  | `pool_size` | `1` if `setup` is set, else `0` | pre-built worktrees (checked out, files copied, setup run) kept ready so a new worker doesn't wait on `setup`; `0` disables it |
488
543
  | `add_dirs` | `[]` | directories outside the worktree that Claude Code agents may use (`--add-dir`; full tool access, see "Directories outside the workspace") |
489
- | `local_models` | `false` | `true`: when a native profile points at Ollama on this machine and it isn't running, `brindle` starts `ollama serve` in the background (with the context length the profiles need) and loads their models. Off, brindle uses a server that's already running and never starts or preloads one |
544
+ | `local_models` | `true` | When a native profile points at Ollama on this machine and it isn't running, `brindle` starts `ollama serve` in the background (with the context length the profiles need) and loads their models, then stops it when the last session closes. `false`: brindle uses a server that's already running and never starts or preloads one |
490
545
  | `sidebar` | `"left"` | where the dashboard sits in each window: `"left"` of the chat, or `"bottom"` (full-width rows under it) |
491
546
  | `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 |
492
547
  | `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) |
@@ -494,6 +549,8 @@ Autopilot, merge gates and cleanup:
494
549
  | `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 |
495
550
  | `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 |
496
551
  | `learning_candidates` | `[]` | the profile names the hosted learner may pick from |
552
+ | `learned_rules_repeats` | `3` | learned rules (brindle Pro): how many tasks of one profile's work must get the same review finding before a rule is suggested |
553
+ | `learned_rules_profile` | none | the profile that groups review findings for learned rules (a `native` or `claude` one); unset, a local model that answers, else Claude's cheapest model |
497
554
  | `plugins` | `{}` | which installed plugin to use per group, e.g. `{"events": "<name>", "policy": "off"}`; unset, a group uses the only plugin installed in it, except `events`, which uses every installed one (several names: `"pro, audit"`; see "Plugins" below) |
498
555
  | `routing` | see below | for each task weight (`light`, `medium`, `heavy`), the profiles `assign`/`handoff` try in order |
499
556
  | `services` | `[]` | per-worktree Docker services (brindle Pro; see "Per-worktree services" below) |
@@ -679,9 +736,24 @@ how to use them, and the next step to get the rest:
679
736
  | hosted learning | Pro | on by itself; `brindle learning` |
680
737
  | per-worktree services | Pro | `"services"` in `.brindle/config.json` |
681
738
  | several repos in one session | Pro | `brindle repo add <path>` |
739
+ | settings sync across machines | Pro | `brindle account sync` |
740
+ | Brindle-CI build fixing (the fix workflow) | Pro | `brindle ci init` |
741
+ | learned rules from repeated review findings | Pro | `brindle rules suggest` |
742
+ | cost (estimates, budgets, reports) | Pro | `brindle cost report` |
743
+ | guardrails (write scope, read scope, env allowlist per profile) | Pro | `write_scope`, `read_scope`, `env_allow` in a profile |
682
744
  | org policies + audit feed | Team | `brindle account org policy` |
683
- | Brindle-CI | Team | `brindle ci init` |
745
+ | Brindle-CI (fixes builds, issues into verified pull requests, validation) | Team | `brindle ci init` |
746
+ | org profile and rule-pack library, pinned so a repo can't loosen it | Team | `brindle account org profiles` |
747
+ | org budgets and protected paths | Team | `budget`, `protected_paths` in your org policy (`brindle account org policy`) |
748
+ | per-member budgets, pause, and admin messages (sidebar "Messages", the bottom bar, `brindle org messages`) | Team | `brindle org messages [--keep]` lists and marks read the notices your admins sent you; a pause refuses new workers and stops running ones |
684
749
  | audit log, air-gap | Enterprise | `brindle audit verify`, `"airgap": true` |
750
+ | audit export | Enterprise | `"audit_export"` in `~/.brindle/config.json`, `brindle audit ship` |
751
+ | managed models | Enterprise | `provider_config`, `deny_personal_keys` in your org policy (see "Managed models" below) |
752
+ | wider CI, cost centers, managed rollout | Enterprise | `brindle ci init --host gitlab`, `brindle cost request`, and `min_version`, required packs and the kill switch in your org policy; admins can also shut down or throttle a member, role or org remotely (it arrives within about a minute) |
753
+
754
+ `brindle ci init` checks your plan before it changes anything: it refuses
755
+ without Brindle-CI on the org you're using, and on Pro it sets up only the fix
756
+ workflow.
685
757
 
686
758
  ```sh
687
759
  brindle account # paid features: what you have, how to use them, how to get the rest
@@ -692,6 +764,9 @@ brindle account upgrade # opens the checkout for brindle Pro (and prints its
692
764
  brindle account portal # opens the billing portal (invoices, seats, cancellation); --org ORG for a team org
693
765
  brindle account org list # the orgs you belong to; `org use <id>` switches, `org policy` shows the current one
694
766
  brindle account org policy # the org's policy, its per-role overrides, and the policy that applies to you
767
+ brindle account org profiles # the org's shared profiles and rule packs, and which are pinned
768
+ brindle account org profiles push <file> [--pack] [--pinned] [--name NAME] # publish a profile (--pack: a rule pack) to the org (admin)
769
+ brindle account org profiles rm <name> [--pack] # take one out of the org's library (admin)
695
770
  brindle account org member policy-role <member> <role|none> # give a member a policy role (admin; Enterprise)
696
771
  brindle account org company [link <org_id> | unlink] # link orgs you own into one company; learning is pooled only within it
697
772
  ```
@@ -733,6 +808,35 @@ key) that the server can't reverse. Never the task text, prompts, diffs,
733
808
  file names, paths or branch names. See `src/brindle/pro/learning.py` and
734
809
  `src/brindle/pro/team_events.py` for the exact payloads.
735
810
 
811
+ ### Managed models (brindle Enterprise)
812
+
813
+ An org policy can pick the provider its workers run on and keep people's own
814
+ model keys out of their panes. Both fields are ignored without the Enterprise
815
+ entitlement.
816
+
817
+ * `provider_config`: `provider` (`bedrock`, `vertex`, `azure` or
818
+ `openai-compatible`), plus `region`, `model_ids` and `endpoint` as the
819
+ provider needs them. For `vertex` set `project` to the GCP project id
820
+ (brindle passes it to Claude Code as `ANTHROPIC_VERTEX_PROJECT_ID`; an older
821
+ policy that put the project id in `endpoint` still works). Every agent is
822
+ launched on that provider, and a profile that points somewhere else is
823
+ refused with a message saying why.
824
+ * `deny_personal_keys`: the personal model keys (`ANTHROPIC_API_KEY`,
825
+ `OPENAI_API_KEY`, ...) are taken out of every agent pane, and a profile that
826
+ sets one is refused.
827
+
828
+ Two things to know when you turn on `deny_personal_keys`:
829
+
830
+ * With an `openai-compatible` or `azure` provider, `OPENAI_API_KEY` is
831
+ stripped like the rest, so give the profile a non-personal key to use: set
832
+ `api_key_env` to the name of a variable that holds the org's key (not
833
+ `OPENAI_API_KEY`).
834
+ * It can't block a Claude subscription or keychain login by itself: Claude
835
+ Code signs in on its own, outside the variables brindle removes. Only a
836
+ `provider_config` that routes Claude Code to Bedrock, Vertex or Foundry
837
+ (`azure`) keeps it off that login. Always pair `deny_personal_keys` with a
838
+ `provider_config`.
839
+
736
840
  ### Air-gapped mode (brindle Enterprise)
737
841
 
738
842
  For machines that must not talk to the internet at all, air-gap mode turns
@@ -815,9 +919,56 @@ one altered in place (hash or signature), one removed, inserted or reordered
815
919
  (seq and prev_hash), or a truncated tail. Verifying and exporting never need
816
920
  the entitlement, so a log keeps its value after a plan lapses.
817
921
 
922
+ #### Audit export (brindle Enterprise)
923
+
924
+ With a plan that includes `audit_export`, brindle ships the audit records to your
925
+ own logging stack and can prune old ones locally. Configure it under
926
+ `audit_export` in `~/.brindle/config.json`; secrets are never in the config, each
927
+ `*_env` names an environment variable that is read when sending:
928
+
929
+ ```json
930
+ {"audit_export": {
931
+ "retention_days": 90,
932
+ "sinks": [
933
+ {"type": "webhook", "url": "https://siem.example.com/brindle", "secret_env": "BRINDLE_WEBHOOK_SECRET"},
934
+ {"type": "splunk", "url": "https://splunk.example.com:8088", "token_env": "SPLUNK_HEC_TOKEN", "index": "audit"},
935
+ {"type": "datadog", "site": "datadoghq.com", "api_key_env": "DD_API_KEY", "tags": ["env:prod"]},
936
+ {"type": "s3", "bucket": "acme-audit", "prefix": "brindle/", "region": "eu-west-1"}]}}
937
+ ```
938
+
939
+ - `webhook`: an HTTPS `POST` of `{"version", "source", "sent_at", "records"}` with
940
+ `X-Brindle-Signature: sha256=<HMAC-SHA256 of the raw body under the secret>`.
941
+ - `splunk`: the HTTP Event Collector (`Authorization: Splunk <token>`); the path
942
+ defaults to `/services/collector/event`; `index`, `source`, `sourcetype` are optional.
943
+ - `datadog`: the Logs intake for your `site`, one log per record.
944
+ - `s3`: one JSONL object per batch under `<prefix>YYYY/MM/DD/`. Needs `boto3`
945
+ (not installed with brindle; without it the sink reports it isn't configured).
946
+ Credentials are boto3's usual chain, or `access_key_env` / `secret_key_env`.
947
+
948
+ Two sources are shipped: every record of the local audit chain, as written
949
+ (`seq`, `ts`, the event, `prev_hash`, `hash`, `sig`, plus the log's name, so the
950
+ receiver can re-check the chain against `brindle audit pubkey`), and the Team
951
+ feed's events (the same HMAC refs the Team backend gets, never raw names, each
952
+ with its content `hash` and the `chain_hash` of the matching chain record). Each
953
+ sink has its own cursor, so a slow or failing sink never holds the others back.
954
+ Records go out in batches, in order, at least once; a failed send is retried with
955
+ exponential backoff from what is on disk, so it survives offline periods and
956
+ restarts. A sink that can't be set up (an unset variable, a non-HTTPS URL) is
957
+ reported and skipped. Nothing is sent without the entitlement or in air-gap mode.
958
+
959
+ `retention_days` prunes local audit records older than that, but only once every
960
+ sink has received them. Pruning replaces the dropped records with one signed
961
+ checkpoint (the last dropped seq and its hash), so `brindle audit verify` still
962
+ passes; the newest record is always kept.
963
+
964
+ ```sh
965
+ brindle audit ship [--force] # send now; exit 1 if a sink failed
966
+ brindle audit prune [--repo PATH] [--days N] # prune now (default: retention_days)
967
+ ```
968
+
818
969
  ### Brindle-CI
819
970
 
820
- Brindle-CI (brindle Team) brings brindle into your CI: it fixes broken builds,
971
+ Brindle-CI (brindle Team; build fixing alone is on Pro) brings brindle into your CI: it fixes broken builds,
821
972
  turns issues into pull requests whose checks it has verified, and reviews pull
822
973
  requests with evidence. It runs on your own CI with your own model keys; the
823
974
  hosted control plane decides what each run does and what it reports. The
@@ -931,6 +1082,20 @@ conflict, a failing check, no reviewer) arrives as "needs you" with the details.
931
1082
  `"pipeline": false` restores the manual flow. Supervisors are also told to keep task
932
1083
  briefs short: writing a long brief holds up every worker waiting on it.
933
1084
 
1085
+ **Conflict-aware merging.** Declared `files` are a guess; what a branch actually
1086
+ changed is the truth. So at the end of each worker turn brindle compares the files
1087
+ each running branch has changed (committed or not) and tells the supervisor as soon
1088
+ as two branches touch the same file, once per pair, while there's still time to have
1089
+ one wait for the other. When several approved branches are ready at once, brindle
1090
+ merges the one whose hunks overlap the others' least first, then syncs each of the
1091
+ rest onto the new tip (merging the base into it, so its approval carries over) and
1092
+ merges it in turn; a conflict then belongs to one branch, not to the pile. That
1093
+ branch goes back to its own worker with the conflicting files to resolve, or to a
1094
+ light worker started in its worktree if the original has finished; its next report
1095
+ starts a fresh review, and the checks run again on the resolved commit before it
1096
+ merges. Conflicts in `protected_paths` (migrations, lockfiles, generated code) are
1097
+ never handed to a worker: the supervisor gets them as "needs you".
1098
+
934
1099
  **The completion audit.** A milestone's check can pass without the milestone being
935
1100
  done: the test asserts too little, or checks the wrong thing. So when every milestone
936
1101
  passes, autopilot doesn't call the goal reached yet. A read-only reviewer audits everything since the goal was set
@@ -993,7 +1158,8 @@ servers don't collide. Agents also get `BRINDLE_AGENT_ID`.
993
1158
  ## Agent profiles
994
1159
 
995
1160
  Markdown files with frontmatter. brindle looks in `.brindle/agents/`, then
996
- `~/.brindle/agents/`, then its built-ins (`supervisor`, `developer`, `reviewer`,
1161
+ `~/.brindle/agents/`, then your org's library (brindle Team, below), then its
1162
+ built-ins (`supervisor`, `developer`, `reviewer`,
997
1163
  `reviewer-codex`, `developer-local`, `reviewer-local`, `subagent`):
998
1164
 
999
1165
  ```markdown
@@ -1120,7 +1286,169 @@ reports the error. Differences from an interactive worker:
1120
1286
 
1121
1287
  Blank values and anything after ` #` are ignored, so frontmatter can carry comments.
1122
1288
  `env.NAME: value` lines set environment variables for the agent's process (see
1123
- "Open-weight models" for what that's for).
1289
+ "Open-weight models" for what that's for). A list field takes `a, b`, `[a, b]`,
1290
+ or a block list (`- item` lines under the key), which is the form for items that
1291
+ contain commas.
1292
+
1293
+ ### Building on a profile: `extends` and rule packs
1294
+
1295
+ A profile can start from another one instead of copying it:
1296
+
1297
+ ```markdown
1298
+ ---
1299
+ name: backend-auditor
1300
+ description: Reviews a branch as a backend security audit
1301
+ extends: reviewer
1302
+ rules: security/backend
1303
+ ---
1304
+ Audit the change the way a security reviewer would...
1305
+ ```
1306
+
1307
+ `extends` names one parent, found by the same lookup order (the repo's
1308
+ `.brindle/agents/`, then `~/.brindle/agents/`, then the built-ins). The child's
1309
+ fields override the parent's, field by field; a field the child leaves out is
1310
+ the parent's. The child's prompt is appended to the parent's, so the child adds
1311
+ to the role rather than restating it. Chains work (`a` extends `b` extends
1312
+ `developer`); a cycle is an error that names the loop. `brindle profile show
1313
+ <name>` prints the result.
1314
+
1315
+ `rules` names **rule packs**: markdown files that give an agent a standing set
1316
+ of rules, and brindle a few it can check itself. A pack lives in
1317
+ `.brindle/rules/<name>.md` (the repo's), `~/.brindle/rules/<name>.md` (yours),
1318
+ or ships with brindle, in that order of precedence; `security/backend` is
1319
+ `security/backend.md`. A child's packs are added after its parent's.
1320
+
1321
+ ```markdown
1322
+ ---
1323
+ name: security/backend
1324
+ description: Backend code handles untrusted input, secrets and the shell carefully
1325
+ deny_deps: [pickle] # names that may not be imported or added to a manifest
1326
+ require_tests_for: [src/**/*.py] # a change here must come with a test file in the diff
1327
+ deny_patterns: # regexes no added line may match
1328
+ - shell=True
1329
+ - \beval\(
1330
+ ---
1331
+ Treat every input that crosses a trust boundary as hostile until validated...
1332
+ ```
1333
+
1334
+ The pack's text joins the agent's prompt, for every provider, with the
1335
+ mechanical rules spelled out. Those three are checked against the branch's
1336
+ diff (what it commits on top of its base) in the same step that runs the repo's
1337
+ `checks`: the reviewer's check summary lists each pack as `PASS`/`FAIL` with
1338
+ the offending `file:line`, and `merge_workspace` refuses the branch while a
1339
+ pack fails, with the violations to send back to the worker, like a failed
1340
+ check. `deny_deps` looks at added `import`/`from`/`require` lines and at
1341
+ dependency manifests (`pyproject.toml`, `requirements*.txt`, `package.json`,
1342
+ `go.mod`, `Cargo.toml` and the like); `require_tests_for` is satisfied by any
1343
+ added or changed test file (`tests/`, `test_*`, `*_test.*`, `*.test.*`,
1344
+ `*.spec.*`, `__tests__/`). A pack with only prose is a prompt and nothing to
1345
+ check. A pack a profile names but that doesn't exist stops the launch, so a
1346
+ rule never silently stops applying. These are pattern checks on the diff's
1347
+ text: they catch the honest mistake and the obvious shortcut and name the
1348
+ line, and a dependency loaded through a name built at run time, or a test
1349
+ that tests nothing, is the reviewer's to catch. What they read can't be
1350
+ hidden: the diff is taken with git's attributes, drivers and prefixes
1351
+ overridden, and a worker whose profile no longer loads fails the gate rather
1352
+ than passing with no rules.
1353
+
1354
+ Built-in packs: `security/backend` (no shell-outs from input, no `eval`, no
1355
+ unsafe deserializing, no secrets in code), `tests/only` (change tests, not the
1356
+ code under test: every diff must touch a test file), `style/minimal-diff` (the
1357
+ smallest diff that does the job). Built-in profiles that use them:
1358
+ `backend-auditor` (`reviewer` plus `security/backend`) and `qa` (`developer`
1359
+ plus `tests/only`).
1360
+
1361
+ `brindle profile new <name> --extends developer --rules security/backend` writes
1362
+ the file; `brindle profile lint` checks every profile it can see (a missing
1363
+ parent, a cycle, an unknown pack, a pattern that doesn't compile, an unknown
1364
+ provider) and exits 1 if any fails.
1365
+
1366
+ ### Guardrails: write scope, read scope, env allowlist (brindle Pro)
1367
+
1368
+ ```markdown
1369
+ ---
1370
+ name: api-dev
1371
+ extends: developer
1372
+ write_scope: [src/api/**, tests/api/**]
1373
+ read_scope: [src/**, tests/**, docs/**]
1374
+ env_allow: [NPM_TOKEN, AWS_*]
1375
+ ---
1376
+ ```
1377
+
1378
+ Globs are relative to the worktree; a trailing `/` or a bare directory name
1379
+ means everything under it, `*` crosses `/`, and case counts.
1380
+
1381
+ - **`write_scope`** is enforced on the branch: the same step that checks rule
1382
+ packs lists every file the branch changes (deletions, renames and mode
1383
+ changes included) and fails the review and merge gates on any outside the
1384
+ scope. On top of that, best effort: Claude Code's PreToolUse hook denies an
1385
+ Edit or Write outside it, the native runner's file tools refuse one, and the
1386
+ worker is told its scope in its prompt. A shell command can still write
1387
+ anywhere, and Codex and agy get only the prompt, so the diff check is what
1388
+ holds. An empty `write_scope: []` means no limit, not "deny everything".
1389
+ - **`read_scope`** is best effort only. The native runner's Read refuses a
1390
+ file outside it and Glob and Grep leave such files out; Claude Code's hook
1391
+ denies a Read outside it, and a Glob or Grep unless the directory searched
1392
+ is wholly in scope (a scope of `src/api/**` can search `src/api/`, not
1393
+ `src/`). A shell command can read anything, other providers only get the
1394
+ prompt, and a read leaves no diff to check afterwards.
1395
+ - **`env_allow`** (names or globs): the worker's process starts with only
1396
+ these variables, brindle's own (`BRINDLE_*`), the basics a process needs
1397
+ (`PATH`, `HOME`, `LANG`, `LC_*`, `TERM`, ...), the profile's own `env.NAME`
1398
+ lines and the provider's sign-in (Claude Code's keys and cloud backends,
1399
+ Codex's keys, a native profile's `api_key_env`). brindle's own secrets (the
1400
+ Pro token, GitHub's tokens) never reach a worker, listed or not. Anything
1401
+ else is dropped, so a tool that needs `GIT_*`, the proxy variables
1402
+ (`HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`) or `NO_COLOR` must have them listed.
1403
+
1404
+ Without the `guardrails` feature these keys are ignored, with one warning per
1405
+ profile, and the profile runs as if they weren't there.
1406
+
1407
+ ### Learned rules (brindle Pro)
1408
+
1409
+ When reviewers keep asking for the same change, brindle suggests a rule for
1410
+ it. Each review that requests changes is a finding, filed under the repo and
1411
+ the profile of the worker whose branch it was. After a review like that,
1412
+ brindle groups the new findings with one small model call (the repo's
1413
+ `learned_rules_profile`, else a local model that answers, else Claude's
1414
+ cheapest model; its tokens show up in `brindle history` like any other).
1415
+ Once the same finding has come up in `learned_rules_repeats` tasks (3 by
1416
+ default), the supervisor's `get_progress` mentions the suggestion and
1417
+ `brindle rules suggest` lists it. Nothing changes until you decide:
1418
+
1419
+ ```sh
1420
+ brindle rules suggest # group new findings, then list the suggestions (--no-refresh: just list)
1421
+ brindle rules accept 3f9c2a1b # add it to .brindle/rules/learned.md
1422
+ brindle rules reject 3f9c2a1b # never suggest it again
1423
+ ```
1424
+
1425
+ `.brindle/rules/learned.md` is an ordinary rule pack that every profile in the
1426
+ repo picks up: each accepted rule is a line of its prose, and when the finding
1427
+ is a mechanical one (a pattern no added line may match, a dependency not to
1428
+ add, paths whose changes need a test), it lands in the pack's `deny_patterns`,
1429
+ `deny_deps` or `require_tests_for` and is checked against every branch's diff.
1430
+ Commit it so the rule is reviewed and shared; edit or delete lines as with any
1431
+ pack. Rejections are remembered on this machine: findings that fall under a
1432
+ rejected rule keep it rejected.
1433
+
1434
+ ### The org library (brindle Team)
1435
+
1436
+ With a Team plan, an org's admins can share profiles and rule packs with every
1437
+ member. An admin publishes a file with `brindle account org profiles push
1438
+ reviewer.md` (a rule pack: `push --pack security.md`, named like
1439
+ `security/backend` with `--name`), and `brindle account org profiles rm <name>`
1440
+ removes one. `brindle account org profiles` lists what the org shares.
1441
+
1442
+ Members get them in the lookup order repo, user, org, built-in (for profiles
1443
+ and rule packs alike), so a repo or user file of the same name still wins.
1444
+ `push --pinned` changes that for an item: a pinned org profile or pack is used
1445
+ over any repo or user file of that name, which is ignored with a warning, so a
1446
+ repo can't loosen what the org requires.
1447
+
1448
+ The library is signed by the backend and verified before use. A copy is kept
1449
+ in `~/.brindle/pro/profiles-<org>/` and used while the backend can't be
1450
+ reached (for a week past its signature); with no usable copy brindle uses
1451
+ only what it has locally, and says so. Air-gap mode never fetches.
1124
1452
 
1125
1453
  ### Directories outside the workspace
1126
1454
 
@@ -1224,9 +1552,9 @@ ollama pull qwen3-coder:30b
1224
1552
  brindle doctor # "model qwen3-coder:30b ... is available"
1225
1553
  ```
1226
1554
 
1227
- Run `ollama serve` yourself, or let brindle do it: a 30B model holds about 20 GB
1228
- of GPU memory, so brindle doesn't start or preload one unless you set
1229
- `"local_models": true`. Then, when `brindle` (or `brindle continue`)
1555
+ Run `ollama serve` yourself, or let brindle do it, with no setup. A 30B model holds
1556
+ about 20 GB of GPU memory, so set `"local_models": false` if you don't want brindle
1557
+ to start or preload one. When `brindle` (or `brindle continue`)
1230
1558
  starts and a native profile points at Ollama on this machine that isn't answering,
1231
1559
  brindle starts it in the background with `OLLAMA_CONTEXT_LENGTH` set to the largest
1232
1560
  `context_tokens` any profile asks of it plus room for the reply (40960 for the