brindle 0.4.7__tar.gz → 0.5.1__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 (205) hide show
  1. {brindle-0.4.7 → brindle-0.5.1}/PKG-INFO +68 -74
  2. {brindle-0.4.7 → brindle-0.5.1}/README.md +67 -73
  3. {brindle-0.4.7 → brindle-0.5.1}/pyproject.toml +2 -2
  4. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/__main__.py +2 -2
  5. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/agents.py +304 -169
  6. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/antigravity.py +12 -15
  7. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/autopilot.py +11 -9
  8. brindle-0.5.1/src/brindle/builtin_agents/developer-codex.md +9 -0
  9. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/builtin_agents/reviewer-codex.md +3 -1
  10. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/builtin_agents/reviewer.md +1 -1
  11. brindle-0.5.1/src/brindle/bus.py +149 -0
  12. brindle-0.5.1/src/brindle/check_scope.py +313 -0
  13. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/cli.py +39 -7
  14. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/codemap.py +99 -58
  15. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/codex_hook.py +4 -7
  16. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/config.py +15 -8
  17. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/cull.py +13 -12
  18. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/daemon/api-schema.json +2804 -2456
  19. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/daemon/app.py +1 -4
  20. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/daemon/events.py +4 -0
  21. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/daemon/holds.py +150 -17
  22. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/daemon/routes_actions.py +232 -26
  23. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/daemon/routes_read.py +41 -12
  24. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/db.py +78 -4
  25. brindle-0.5.1/src/brindle/entry.py +25 -0
  26. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/gates.py +48 -15
  27. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/mcp_server.py +12 -0
  28. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/native/permissions.py +2 -1
  29. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/native/runner.py +11 -9
  30. brindle-0.5.1/src/brindle/permission_asks.py +184 -0
  31. brindle-0.5.1/src/brindle/permission_grants.py +803 -0
  32. brindle-0.5.1/src/brindle/permission_groups.py +458 -0
  33. brindle-0.5.1/src/brindle/permission_routing.py +167 -0
  34. brindle-0.5.1/src/brindle/permissions.py +4069 -0
  35. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pipeline.py +33 -18
  36. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pro/_files.py +11 -6
  37. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pro/license.py +148 -6
  38. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/profiles.py +18 -0
  39. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/prompt_answer.py +22 -0
  40. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/providers.py +31 -5
  41. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/quota.py +6 -2
  42. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/service/dto.py +191 -117
  43. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/service/inbox.py +17 -137
  44. brindle-0.5.1/src/brindle/service/permissions.py +138 -0
  45. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/watch.py +8 -1
  46. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/workspaces.py +9 -5
  47. brindle-0.4.7/src/brindle/builtin_agents/developer-codex.md +0 -6
  48. brindle-0.4.7/src/brindle/bus.py +0 -75
  49. brindle-0.4.7/src/brindle/daemon/grants.py +0 -320
  50. brindle-0.4.7/src/brindle/daemon/routes_permissions.py +0 -37
  51. brindle-0.4.7/src/brindle/permission_editor.py +0 -385
  52. brindle-0.4.7/src/brindle/permission_grants.py +0 -646
  53. brindle-0.4.7/src/brindle/permission_presets.json +0 -44
  54. brindle-0.4.7/src/brindle/permissions.py +0 -2182
  55. {brindle-0.4.7 → brindle-0.5.1}/.gitignore +0 -0
  56. {brindle-0.4.7 → brindle-0.5.1}/LICENSE +0 -0
  57. {brindle-0.4.7 → brindle-0.5.1}/SCHEDULE-A +0 -0
  58. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/__init__.py +0 -0
  59. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/account.py +0 -0
  60. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/airgap.py +0 -0
  61. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/assembler.py +0 -0
  62. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/assets/braid_dark.png +0 -0
  63. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/assets/braid_light.png +0 -0
  64. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/budget.py +0 -0
  65. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/builtin_agents/__init__.py +0 -0
  66. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/builtin_agents/backend-auditor.md +0 -0
  67. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/builtin_agents/coordinator.md +0 -0
  68. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/builtin_agents/developer-fable.md +0 -0
  69. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/builtin_agents/developer-gemini.md +0 -0
  70. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/builtin_agents/developer-heavy.md +0 -0
  71. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/builtin_agents/developer-light.md +0 -0
  72. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/builtin_agents/developer-local.md +0 -0
  73. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/builtin_agents/developer-opencode.md +0 -0
  74. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/builtin_agents/developer-sonnet.md +0 -0
  75. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/builtin_agents/developer.md +0 -0
  76. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/builtin_agents/qa.md +0 -0
  77. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/builtin_agents/reviewer-gemini.md +0 -0
  78. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/builtin_agents/reviewer-local.md +0 -0
  79. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/builtin_agents/reviewer-opencode.md +0 -0
  80. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/builtin_agents/subagent.md +0 -0
  81. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/builtin_agents/supervisor.md +0 -0
  82. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/builtin_rules/__init__.py +0 -0
  83. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/builtin_rules/security/backend.md +0 -0
  84. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/builtin_rules/style/minimal-diff.md +0 -0
  85. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/builtin_rules/tests/only.md +0 -0
  86. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/ci_adapters.py +0 -0
  87. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/ci_client.py +0 -0
  88. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/ci_federation.py +0 -0
  89. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/ci_hosts.py +0 -0
  90. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/clone.py +0 -0
  91. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/cloud_credential.py +0 -0
  92. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/cloud_login.py +0 -0
  93. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/codemap_docs.py +0 -0
  94. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/company_identity.py +0 -0
  95. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/company_login.py +0 -0
  96. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/conflicts.py +0 -0
  97. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/coordinator.py +0 -0
  98. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/cost.py +0 -0
  99. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/cost_estimate.py +0 -0
  100. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/daemon/__init__.py +0 -0
  101. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/daemon/auth.py +0 -0
  102. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/daemon/lifecycle.py +0 -0
  103. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/daemon/peer.py +0 -0
  104. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/daemon/routes_org_permissions.py +0 -0
  105. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/daemon/schema.py +0 -0
  106. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/daemon/session_review.py +0 -0
  107. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/daemon/terminal.py +0 -0
  108. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/debuglog.py +0 -0
  109. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/demo.py +0 -0
  110. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/detect.py +0 -0
  111. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/doctor.py +0 -0
  112. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/escapes.py +0 -0
  113. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/events.py +0 -0
  114. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/git.py +0 -0
  115. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/guardrails.py +0 -0
  116. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/history.py +0 -0
  117. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/identity_lock.py +0 -0
  118. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/inbox.py +0 -0
  119. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/kb.py +0 -0
  120. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/key_check.py +0 -0
  121. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/keys_cmds.py +0 -0
  122. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/keystore.py +0 -0
  123. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/knowledge.py +0 -0
  124. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/launch_health.py +0 -0
  125. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/learned_rules.py +0 -0
  126. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/learning.py +0 -0
  127. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/legacy.py +0 -0
  128. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/local_setup.py +0 -0
  129. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/login_pause.py +0 -0
  130. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/model_access.py +0 -0
  131. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/model_availability.py +0 -0
  132. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/native/__init__.py +0 -0
  133. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/native/catalog.py +0 -0
  134. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/native/client.py +0 -0
  135. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/native/loop.py +0 -0
  136. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/native/lsp.py +0 -0
  137. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/native/serve.py +0 -0
  138. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/native/tools.py +0 -0
  139. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/opencode.py +0 -0
  140. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/org_permissions.py +0 -0
  141. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pane_auth.py +0 -0
  142. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/picker.py +0 -0
  143. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/plugins.py +0 -0
  144. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/policy.py +0 -0
  145. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pool.py +0 -0
  146. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/preflight.py +0 -0
  147. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pricing.py +0 -0
  148. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pro/__init__.py +0 -0
  149. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pro/account.py +0 -0
  150. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pro/audit_chain.py +0 -0
  151. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pro/audit_export.py +0 -0
  152. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pro/auth.py +0 -0
  153. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pro/brief_learning.py +0 -0
  154. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pro/cost_centers.py +0 -0
  155. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pro/credentials.py +0 -0
  156. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pro/features.py +0 -0
  157. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pro/keys.py +0 -0
  158. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pro/learning.py +0 -0
  159. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pro/loopback.py +0 -0
  160. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pro/managed_models.py +0 -0
  161. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pro/org_profiles.py +0 -0
  162. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pro/orgkey.py +0 -0
  163. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pro/rollout.py +0 -0
  164. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pro/route_health.py +0 -0
  165. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pro/settings_sync.py +0 -0
  166. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pro/status.py +0 -0
  167. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pro/team_events.py +0 -0
  168. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/pro/team_policy.py +0 -0
  169. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/procs.py +0 -0
  170. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/provider_homes.py +0 -0
  171. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/providers_install.py +0 -0
  172. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/repo_cmds.py +0 -0
  173. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/repos.py +0 -0
  174. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/rewind.py +0 -0
  175. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/rule_checks.py +0 -0
  176. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/savings.py +0 -0
  177. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/scratch.py +0 -0
  178. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/secrets.py +0 -0
  179. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/self_serve.py +0 -0
  180. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/service/__init__.py +0 -0
  181. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/service/errors.py +0 -0
  182. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/service/routing.py +0 -0
  183. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/service/sessions.py +0 -0
  184. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/services.py +0 -0
  185. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/sessions.py +0 -0
  186. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/sidebar_follow.py +0 -0
  187. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/sidebar_logo.py +0 -0
  188. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/signin_pause.py +0 -0
  189. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/status_cache.py +0 -0
  190. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/tasks.py +0 -0
  191. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/tmux.py +0 -0
  192. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/updates.py +0 -0
  193. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/upgrade.py +0 -0
  194. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/usage.py +0 -0
  195. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/view.py +0 -0
  196. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/wake.py +0 -0
  197. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/watch_costs.py +0 -0
  198. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/web/__init__.py +0 -0
  199. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/web/chat.py +0 -0
  200. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/web/dashboard.py +0 -0
  201. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/web/diff.py +0 -0
  202. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/web/server.py +0 -0
  203. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/web/static/app.js +0 -0
  204. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/web/static/index.html +0 -0
  205. {brindle-0.4.7 → brindle-0.5.1}/src/brindle/worker_models.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brindle
3
- Version: 0.4.7
3
+ Version: 0.5.1
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
@@ -404,7 +404,7 @@ your own status line prints, so what you see doesn't change.
404
404
  | `brindle cost [--days N] [--all]` | dollar spend at list prices over the last 30 days, by model |
405
405
  | `brindle cost report [--days N] [--all]` | Pro: spend by day, profile and goal, cost per merged branch, review pass rate per worker profile |
406
406
  | `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 |
407
- | `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") |
407
+ | `brindle permissions list / check / preset / suggestions / accept / allow / deny / forget / reset` | the rules brindle answers workers' permission requests with, the preset they started from, and what it suggests from your approvals (see "Permission policy"; saved actions are managed in the app for now, [#285](https://github.com/PawDelta/brindle/issues/285)) |
408
408
  | `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") |
409
409
  | `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 |
410
410
  | `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 |
@@ -473,6 +473,7 @@ knowing them helps when you tell the supervisor how to work.
473
473
  | `report_result` | worker | finish a task and hand back the result |
474
474
  | `submit_plan` | worker | a `plan_first` worker proposes its plan and waits for approval before editing |
475
475
  | `approve_plan` | supervisor | approve a worker's plan, or send it back with feedback (`approved=false`) |
476
+ | `answer_permission` | supervisor | allow, deny or escalate (with a reason) a worker's permission ask before the person sees it |
476
477
  | `complete_subagent` | supervisor | record the result of a `subagent`-profile task |
477
478
  | `set_goal` / `get_progress` / `check_milestone` | supervisor | autopilot's goal, its progress, and running the checks |
478
479
  | `record_audit_decision` | supervisor | record acceptance of an audit finding with rationale so subsequent audits respect it |
@@ -672,7 +673,8 @@ Autopilot, merge gates and cleanup:
672
673
  | `autopilot` | `true` | start the supervisor with autopilot on |
673
674
  | `claude_permission_mode` | `"default"` | the `--permission-mode` for Claude Code sessions whose profile sets no `permission_mode`; the default holds permission requests for the brindle app, set `"auto"` to opt into auto mode. Only read from `~/.brindle/config.json` |
674
675
  | `auto_update` | `false` | run `uv tool upgrade brindle` in the background at session start when PyPI has a newer version; restart to use it. Can also be set in `~/.brindle/config.json` for every repo |
675
- | `checks` | `[]` | commands that must pass in a worker's branch before it merges |
676
+ | `scoped_checks` | unset (acts as `"off"`) | `"infer"` narrows a bare pytest check (`pytest ...`, `python -m pytest ...`, `uv run pytest ...`, options only) on a worker's branch to the tests `graphify affected` reaches plus the touched test files, when the repo has a stored graph; it runs in full when the mapping fails or `conftest.py`, `pyproject.toml`, `setup.cfg`, `pytest.ini`, `tox.ini` or a lockfile changed. A pytest command that already names test paths (`pytest tests/`) and every other command always run in full. `"off"` narrows nothing. It becomes the default next release; until you set it, brindle tells you once |
677
+ | `checks` | `[]` | commands (plain strings) that must pass in a worker's branch before it merges. On a worker's branch every check also gets `BRINDLE_CHANGED_FILES` (the files changed since the branch left its base, one repo-relative path per line) so a script can narrow itself; the supervisor's checkout always runs every check in full (see `scoped_checks` and `brindle/check_scope.py`) |
676
678
  | `review` | only under autopilot | require a reviewer's approval before merging |
677
679
  | `reviewer` | `"reviewer"` | the agent profile that reviews (a Codex profile gives a second model's view) |
678
680
  | `review_profile` | none | force `request_review`'s profile, skipping its automatic cross-model pick (see below) |
@@ -691,7 +693,8 @@ Autopilot, merge gates and cleanup:
691
693
  | `goal_audit` | `true` | once every milestone passes, an adversarial reviewer checks the work against the original goal before the goal counts as reached |
692
694
  | `merge_into` | none | branch that worker branches are cut from and merge into, whatever branch the supervisor is on |
693
695
  | `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) |
694
- | `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 |
696
+ | `permission_policy` | `"on"` with a Pro license, else `"off"` | `"on"`: brindle answers workers' permission requests from its rules (see "Permission policy" below); `"off"`: the provider's own prompts and permission mode decide, except Antigravity workers under `agy_approvals: "brindle"`, whose hook still enforces the rules. The only permission switch: a leftover `permission_essentials` key is ignored |
697
+ | `permission_supervisor_hold_seconds` | `120` | with `permission_policy` on, how long a Claude Code or Codex worker's ask waits on its supervisor (`answer_permission`) before you see it; asks a person-only rule makes (pushes into the default or a release branch) come straight to you |
695
698
  | `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"` |
696
699
  | `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 |
697
700
  | `plan_first` | `false` | workers propose a plan (`submit_plan`) and wait for `approve_plan` before editing |
@@ -879,26 +882,43 @@ command, path or URL; brindle never reads the screen) and brindle answers
879
882
  **allow**, **deny**, or **ask**, which leaves the prompt to you as usual. Any
880
883
  error is ask.
881
884
 
882
- The starting rules:
883
-
884
- - **allow** reading a file git tracks in the worktree or repo root (exact
885
- path, symlinks resolved and kept inside the root; never `.git/`, ignored or
886
- untracked files);
887
- - **allow** a Bash command that is exactly one of the repo's `checks`, and
888
- `git status` / `git diff` / `git log` / `git show`, but only as a single
889
- simple command: anything with `;`, `|`, `&`, `$`, backticks, quotes,
890
- redirections or globs is never auto-allowed;
891
- - **deny** `git push`, git force flags, and reading `~/.ssh`, `~/.aws`,
892
- `~/.gnupg` or `.env*`;
893
- - **ask** for everything else.
894
-
895
- A deny beats any allow; nothing else is ranked. Add your own with
896
- `brindle permissions allow|deny KIND MATCH` (kinds: `read`, `write`, `edit`,
897
- `bash`, `fetch`, `mcp`, `other`; the match is exact unless `--prefix` or
898
- `--glob`); they're kept in `~/.brindle/permissions.json`. A repo's
885
+ Nothing is built in. Every rule lives in `~/.brindle/permissions.json`, and
886
+ you can edit or remove any of them, including the ones a **preset** seeded.
887
+ Pick a preset in Settings → Permissions or at setup:
888
+
889
+ - **Autonomous** (the default): everything runs, except pushes into the
890
+ repo's default branch (ask) and destructive actions (deny: git force flags,
891
+ deleting a branch by push, a recursive `rm` of `/`, a top-level folder or
892
+ home);
893
+ - **Permissive**: edits in the worktree and everyday commands run, the rest asks;
894
+ - **Standard**: routine reads and everyday commands run, changes ask;
895
+ - **Secure**: short approvals, and a deny for anything no rule covers.
896
+
897
+ Every preset also denies reading `~/.ssh`, `~/.aws`, `~/.gnupg`, `.env*` and
898
+ the daemon's token, and asks before every push into a `release` or
899
+ `release/*` branch, which starts a signed build (an approval there is never
900
+ saved). It allows reading files git tracks, the repo's `checks`, read-only
901
+ git commands and read-only pipelines (grep, rg, ls, cat, head, `sed -n` ...
902
+ joined by pipes or `&&`, quoting read as the shell reads it). A pipeline
903
+ stops being read-only when it writes a file, substitutes a command, sets a
904
+ variable, recurses into a tree with a file a read deny covers, or reads such a
905
+ file. Precedence: a deny wins, then an ask, then an allow, then the preset's
906
+ setting for what no rule covers (ask, allow or deny). A deny or ask also
907
+ catches a part of a compound command however it's spelled (spacing, quotes,
908
+ assignments, redirects, `$(...)`, patterns the shell expands); when brindle
909
+ can't read the syntax and a deny or ask could match it, it asks.
910
+
911
+ Add your own with `brindle permissions allow|deny KIND MATCH` (kinds: `read`,
912
+ `write`, `edit`, `bash`, `fetch`, `mcp`, `other`; the match is exact unless
913
+ `--prefix` or `--glob`) or in the app, which can also make ask rules with
914
+ limits on saved approvals, timed rules and exceptions. A repo's
899
915
  `.brindle/permissions.json` (`{"rules": [...]}`, same format) can only add
900
916
  denies. `brindle permissions list` shows every rule and where it came from,
901
- `forget ID` removes one of yours, `reset` goes back to the starting rules.
917
+ `forget ID` removes one, `reset` goes back to the preset's rules. A store
918
+ written by brindle 0.4.7 is migrated on first load: its permission editor's
919
+ preset becomes Permissive, Standard or Secure (Autonomous when it chose
920
+ none), its own rules stay, and its remembered approvals become saved actions;
921
+ its old rows stay in the file.
902
922
  Profiles can add narrower, profile-local denies with a JSON array in frontmatter,
903
923
  for example `permission_denies: ["read glob ~/.private*", "bash prefix deploy"]`.
904
924
  Built-in profiles ship with no profile-specific denies by default. These denies
@@ -919,63 +939,37 @@ on without the tool running counts for nothing. Every decision is in
919
939
  `brindle history --kind permission`, and when a worker sits on a prompt, its
920
940
  supervisor is told exactly which request is pending (only you can answer it).
921
941
 
922
- **Grants.** When the app holds a Claude Code or Codex worker's request in the
923
- Inbox, you can answer **Allow for N min**. That lets this agent repeat exactly
924
- this request (the whole command, the resolved path, the https URL or the tool)
925
- in this repo until the time or the uses run out, or until you revoke it under
926
- Settings → Permission grants. A grant is spent when the hook allows the call,
927
- not when the tool succeeds (a hook can't see the result). Grants live in the
928
- daemon's memory, so a restart ends them all.
929
-
930
- Two built-in denies can also be lifted by an explicit approval. When nothing else
931
- denies the request, it is still denied at once, never held. A held request
932
- shows the CLI's own prompt, and an answer typed there would run it. The worker
933
- is told it was refused and that it asked you, and the Inbox shows the exact
934
- request with **Allow once**, **Deny**, and, when policy permits, **Always allow in this repository**. Allow it once, and the worker's next
935
- try of exactly that command uses your one-time grant. What it covers and what
936
- the Inbox shows are worked out by the daemon from the agent's own workspace,
937
- never taken from the agent. The grant expires after 15 minutes, or sooner if
938
- your org's `max_minutes` is shorter, even when the org shortens it later. It is
939
- used up even if the push then fails. If nobody answers, or the
940
- daemon isn't running, it stays denied.
941
-
942
- **Remembered approvals** are saved in your user permission preferences, across
943
- sessions and daemon restarts, until you revoke them in Settings → Permission
944
- grants. A remembered push covers the same repository, remote URL and destination
945
- branch across future commits; a file approval covers that specific file.
946
- Every use still checks the current policy, explicit denies and adapter protections.
947
- Temporary time/use limits apply to temporary grants; persistent approvals require
948
- their own policy permission. Turning that permission off prevents their use.
949
- - `git push` qualifies only as `git push <remote> <full commit sha>:refs/heads/<branch>`, run in the agent's own worktree (not another clone or a nested repository), and only when:
950
- - the remote resolves (insteadOf applied) to exactly one https, ssh or `user@host:path` URL;
951
- - none of `core.sshCommand`, `core.hooksPath`, the remote's receivepack/vcs/mirror, `push.followTags` or `push.recurseSubmodules` is set;
952
- - there is no pre-push or reference-transaction hook;
953
- - neither the repo nor your org sets protected paths (a push skips the merge check that enforces them).
954
- - `.env*` qualifies for one file inside the worktree.
955
-
956
- After your answer, brindle checks the policy, every rule and that scope again
957
- before allowing. Anything that changed meanwhile refuses it. The command then
958
- runs outside brindle.
959
-
960
- Never lifted: your own, the repo's, a profile's or your org's denies, git force
961
- flags, and reads of `~/.ssh`, `~/.aws`, `~/.gnupg` or the daemon's token.
962
- Antigravity and OpenCode workers get no grants: agy ignores a hook's allow, and
963
- OpenCode's plugin has no hold.
964
-
965
- In a Team org, only the org policy's `permission_grants` block decides what may
966
- be granted. It is read again on every request:
942
+ **Saved actions.** The app's Inbox groups pending asks by kind and general
943
+ form (a push by its destination, a file by its top folder, a command by its
944
+ first two words, an MCP tool or its server). One decision answers the group:
945
+ once, for the session, for a time, until revoked, deny, or always deny. Anything
946
+ kept is a saved action, listed and editable under Settings → Permissions. A
947
+ saved deny stops every ask it covers. A saved allow settles an ask only within
948
+ the limits of the ask rule that asked (its scope and how long approvals may
949
+ last), never past a deny rule, and never once it has expired. Every use reads
950
+ the current rules again.
951
+
952
+ Saved actions and the grouped Inbox are in the app only for now. From the
953
+ CLI, saved actions still apply to every worker, but you can't list, edit or
954
+ delete one (`brindle permissions reset` removes them all, with your own and
955
+ learned rules), and without the app a held request falls back to the worker
956
+ CLI's own prompt in its pane, one exact request at a time. CLI commands for
957
+ both are tracked in
958
+ [#285](https://github.com/PawDelta/brindle/issues/285).
959
+
960
+ In a Team org, the org policy's `permission_grants` block bounds what a saved
961
+ allow may do. It is read again on every request:
967
962
 
968
963
  ```json
969
- "permission_grants": {"allow": true, "kinds": ["bash", "fetch"], "overridable": ["git-push"],
970
- "max_minutes": 30, "max_uses": 5, "allow_persistent": true,
971
- "deny": ["bash prefix terraform apply"]}
964
+ "permission_grants": {"allow": true, "kinds": ["bash", "fetch"], "max_minutes": 30,
965
+ "allow_persistent": true, "lifetimes": ["session", "time", "forever"],
966
+ "scopes": ["branch", "remote"], "deny": ["bash prefix terraform apply"]}
972
967
  ```
973
968
 
974
969
  - `deny` entries are enforced for every provider and can't be lifted.
975
- - `allow_persistent` permits remembered overrides of these two built-in denies; omitted or false allows only temporary overrides. This setting does not control ordinary saved allow rules. The admin policy editor is not delivered by this local engine change.
976
- - A policy without the block, an unknown or malformed field (which makes the whole policy unusable), a policy that can't be read, or a paused or shut-down member: no grants.
977
- - Without a Team org, any kind can be granted, for up to 60 min and 20 uses. Both built-in denies support one-time or remembered approvals.
978
- - The org backend that serves this block isn't part of this repository. Until it serves one, Team orgs get no grants. It is enforced from the cached policy, or in air-gap mode from `.brindle/policy.json`.
970
+ - A saved allow applies only for the listed `kinds`, `lifetimes` and `scopes`, with timed ones capped at `max_minutes`; one that lasts until revoked also needs `allow_persistent`.
971
+ - A policy without the block, one that can't be read, or a paused or shut-down member: no saved allow applies.
972
+ - Org admins edit the block in Settings → Organization approvals. It is enforced from the cached policy, or in air-gap mode from `.brindle/policy.json`.
979
973
 
980
974
  **Codex.** Codex runs a hook only once it's trusted, so with
981
975
  `permission_policy` on, brindle trusts its own hook the first time a Codex worker
@@ -372,7 +372,7 @@ your own status line prints, so what you see doesn't change.
372
372
  | `brindle cost [--days N] [--all]` | dollar spend at list prices over the last 30 days, by model |
373
373
  | `brindle cost report [--days N] [--all]` | Pro: spend by day, profile and goal, cost per merged branch, review pass rate per worker profile |
374
374
  | `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 |
375
- | `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") |
375
+ | `brindle permissions list / check / preset / suggestions / accept / allow / deny / forget / reset` | the rules brindle answers workers' permission requests with, the preset they started from, and what it suggests from your approvals (see "Permission policy"; saved actions are managed in the app for now, [#285](https://github.com/PawDelta/brindle/issues/285)) |
376
376
  | `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") |
377
377
  | `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 |
378
378
  | `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 |
@@ -441,6 +441,7 @@ knowing them helps when you tell the supervisor how to work.
441
441
  | `report_result` | worker | finish a task and hand back the result |
442
442
  | `submit_plan` | worker | a `plan_first` worker proposes its plan and waits for approval before editing |
443
443
  | `approve_plan` | supervisor | approve a worker's plan, or send it back with feedback (`approved=false`) |
444
+ | `answer_permission` | supervisor | allow, deny or escalate (with a reason) a worker's permission ask before the person sees it |
444
445
  | `complete_subagent` | supervisor | record the result of a `subagent`-profile task |
445
446
  | `set_goal` / `get_progress` / `check_milestone` | supervisor | autopilot's goal, its progress, and running the checks |
446
447
  | `record_audit_decision` | supervisor | record acceptance of an audit finding with rationale so subsequent audits respect it |
@@ -640,7 +641,8 @@ Autopilot, merge gates and cleanup:
640
641
  | `autopilot` | `true` | start the supervisor with autopilot on |
641
642
  | `claude_permission_mode` | `"default"` | the `--permission-mode` for Claude Code sessions whose profile sets no `permission_mode`; the default holds permission requests for the brindle app, set `"auto"` to opt into auto mode. Only read from `~/.brindle/config.json` |
642
643
  | `auto_update` | `false` | run `uv tool upgrade brindle` in the background at session start when PyPI has a newer version; restart to use it. Can also be set in `~/.brindle/config.json` for every repo |
643
- | `checks` | `[]` | commands that must pass in a worker's branch before it merges |
644
+ | `scoped_checks` | unset (acts as `"off"`) | `"infer"` narrows a bare pytest check (`pytest ...`, `python -m pytest ...`, `uv run pytest ...`, options only) on a worker's branch to the tests `graphify affected` reaches plus the touched test files, when the repo has a stored graph; it runs in full when the mapping fails or `conftest.py`, `pyproject.toml`, `setup.cfg`, `pytest.ini`, `tox.ini` or a lockfile changed. A pytest command that already names test paths (`pytest tests/`) and every other command always run in full. `"off"` narrows nothing. It becomes the default next release; until you set it, brindle tells you once |
645
+ | `checks` | `[]` | commands (plain strings) that must pass in a worker's branch before it merges. On a worker's branch every check also gets `BRINDLE_CHANGED_FILES` (the files changed since the branch left its base, one repo-relative path per line) so a script can narrow itself; the supervisor's checkout always runs every check in full (see `scoped_checks` and `brindle/check_scope.py`) |
644
646
  | `review` | only under autopilot | require a reviewer's approval before merging |
645
647
  | `reviewer` | `"reviewer"` | the agent profile that reviews (a Codex profile gives a second model's view) |
646
648
  | `review_profile` | none | force `request_review`'s profile, skipping its automatic cross-model pick (see below) |
@@ -659,7 +661,8 @@ Autopilot, merge gates and cleanup:
659
661
  | `goal_audit` | `true` | once every milestone passes, an adversarial reviewer checks the work against the original goal before the goal counts as reached |
660
662
  | `merge_into` | none | branch that worker branches are cut from and merge into, whatever branch the supervisor is on |
661
663
  | `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) |
662
- | `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 |
664
+ | `permission_policy` | `"on"` with a Pro license, else `"off"` | `"on"`: brindle answers workers' permission requests from its rules (see "Permission policy" below); `"off"`: the provider's own prompts and permission mode decide, except Antigravity workers under `agy_approvals: "brindle"`, whose hook still enforces the rules. The only permission switch: a leftover `permission_essentials` key is ignored |
665
+ | `permission_supervisor_hold_seconds` | `120` | with `permission_policy` on, how long a Claude Code or Codex worker's ask waits on its supervisor (`answer_permission`) before you see it; asks a person-only rule makes (pushes into the default or a release branch) come straight to you |
663
666
  | `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"` |
664
667
  | `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 |
665
668
  | `plan_first` | `false` | workers propose a plan (`submit_plan`) and wait for `approve_plan` before editing |
@@ -847,26 +850,43 @@ command, path or URL; brindle never reads the screen) and brindle answers
847
850
  **allow**, **deny**, or **ask**, which leaves the prompt to you as usual. Any
848
851
  error is ask.
849
852
 
850
- The starting rules:
851
-
852
- - **allow** reading a file git tracks in the worktree or repo root (exact
853
- path, symlinks resolved and kept inside the root; never `.git/`, ignored or
854
- untracked files);
855
- - **allow** a Bash command that is exactly one of the repo's `checks`, and
856
- `git status` / `git diff` / `git log` / `git show`, but only as a single
857
- simple command: anything with `;`, `|`, `&`, `$`, backticks, quotes,
858
- redirections or globs is never auto-allowed;
859
- - **deny** `git push`, git force flags, and reading `~/.ssh`, `~/.aws`,
860
- `~/.gnupg` or `.env*`;
861
- - **ask** for everything else.
862
-
863
- A deny beats any allow; nothing else is ranked. Add your own with
864
- `brindle permissions allow|deny KIND MATCH` (kinds: `read`, `write`, `edit`,
865
- `bash`, `fetch`, `mcp`, `other`; the match is exact unless `--prefix` or
866
- `--glob`); they're kept in `~/.brindle/permissions.json`. A repo's
853
+ Nothing is built in. Every rule lives in `~/.brindle/permissions.json`, and
854
+ you can edit or remove any of them, including the ones a **preset** seeded.
855
+ Pick a preset in Settings → Permissions or at setup:
856
+
857
+ - **Autonomous** (the default): everything runs, except pushes into the
858
+ repo's default branch (ask) and destructive actions (deny: git force flags,
859
+ deleting a branch by push, a recursive `rm` of `/`, a top-level folder or
860
+ home);
861
+ - **Permissive**: edits in the worktree and everyday commands run, the rest asks;
862
+ - **Standard**: routine reads and everyday commands run, changes ask;
863
+ - **Secure**: short approvals, and a deny for anything no rule covers.
864
+
865
+ Every preset also denies reading `~/.ssh`, `~/.aws`, `~/.gnupg`, `.env*` and
866
+ the daemon's token, and asks before every push into a `release` or
867
+ `release/*` branch, which starts a signed build (an approval there is never
868
+ saved). It allows reading files git tracks, the repo's `checks`, read-only
869
+ git commands and read-only pipelines (grep, rg, ls, cat, head, `sed -n` ...
870
+ joined by pipes or `&&`, quoting read as the shell reads it). A pipeline
871
+ stops being read-only when it writes a file, substitutes a command, sets a
872
+ variable, recurses into a tree with a file a read deny covers, or reads such a
873
+ file. Precedence: a deny wins, then an ask, then an allow, then the preset's
874
+ setting for what no rule covers (ask, allow or deny). A deny or ask also
875
+ catches a part of a compound command however it's spelled (spacing, quotes,
876
+ assignments, redirects, `$(...)`, patterns the shell expands); when brindle
877
+ can't read the syntax and a deny or ask could match it, it asks.
878
+
879
+ Add your own with `brindle permissions allow|deny KIND MATCH` (kinds: `read`,
880
+ `write`, `edit`, `bash`, `fetch`, `mcp`, `other`; the match is exact unless
881
+ `--prefix` or `--glob`) or in the app, which can also make ask rules with
882
+ limits on saved approvals, timed rules and exceptions. A repo's
867
883
  `.brindle/permissions.json` (`{"rules": [...]}`, same format) can only add
868
884
  denies. `brindle permissions list` shows every rule and where it came from,
869
- `forget ID` removes one of yours, `reset` goes back to the starting rules.
885
+ `forget ID` removes one, `reset` goes back to the preset's rules. A store
886
+ written by brindle 0.4.7 is migrated on first load: its permission editor's
887
+ preset becomes Permissive, Standard or Secure (Autonomous when it chose
888
+ none), its own rules stay, and its remembered approvals become saved actions;
889
+ its old rows stay in the file.
870
890
  Profiles can add narrower, profile-local denies with a JSON array in frontmatter,
871
891
  for example `permission_denies: ["read glob ~/.private*", "bash prefix deploy"]`.
872
892
  Built-in profiles ship with no profile-specific denies by default. These denies
@@ -887,63 +907,37 @@ on without the tool running counts for nothing. Every decision is in
887
907
  `brindle history --kind permission`, and when a worker sits on a prompt, its
888
908
  supervisor is told exactly which request is pending (only you can answer it).
889
909
 
890
- **Grants.** When the app holds a Claude Code or Codex worker's request in the
891
- Inbox, you can answer **Allow for N min**. That lets this agent repeat exactly
892
- this request (the whole command, the resolved path, the https URL or the tool)
893
- in this repo until the time or the uses run out, or until you revoke it under
894
- Settings → Permission grants. A grant is spent when the hook allows the call,
895
- not when the tool succeeds (a hook can't see the result). Grants live in the
896
- daemon's memory, so a restart ends them all.
897
-
898
- Two built-in denies can also be lifted by an explicit approval. When nothing else
899
- denies the request, it is still denied at once, never held. A held request
900
- shows the CLI's own prompt, and an answer typed there would run it. The worker
901
- is told it was refused and that it asked you, and the Inbox shows the exact
902
- request with **Allow once**, **Deny**, and, when policy permits, **Always allow in this repository**. Allow it once, and the worker's next
903
- try of exactly that command uses your one-time grant. What it covers and what
904
- the Inbox shows are worked out by the daemon from the agent's own workspace,
905
- never taken from the agent. The grant expires after 15 minutes, or sooner if
906
- your org's `max_minutes` is shorter, even when the org shortens it later. It is
907
- used up even if the push then fails. If nobody answers, or the
908
- daemon isn't running, it stays denied.
909
-
910
- **Remembered approvals** are saved in your user permission preferences, across
911
- sessions and daemon restarts, until you revoke them in Settings → Permission
912
- grants. A remembered push covers the same repository, remote URL and destination
913
- branch across future commits; a file approval covers that specific file.
914
- Every use still checks the current policy, explicit denies and adapter protections.
915
- Temporary time/use limits apply to temporary grants; persistent approvals require
916
- their own policy permission. Turning that permission off prevents their use.
917
- - `git push` qualifies only as `git push <remote> <full commit sha>:refs/heads/<branch>`, run in the agent's own worktree (not another clone or a nested repository), and only when:
918
- - the remote resolves (insteadOf applied) to exactly one https, ssh or `user@host:path` URL;
919
- - none of `core.sshCommand`, `core.hooksPath`, the remote's receivepack/vcs/mirror, `push.followTags` or `push.recurseSubmodules` is set;
920
- - there is no pre-push or reference-transaction hook;
921
- - neither the repo nor your org sets protected paths (a push skips the merge check that enforces them).
922
- - `.env*` qualifies for one file inside the worktree.
923
-
924
- After your answer, brindle checks the policy, every rule and that scope again
925
- before allowing. Anything that changed meanwhile refuses it. The command then
926
- runs outside brindle.
927
-
928
- Never lifted: your own, the repo's, a profile's or your org's denies, git force
929
- flags, and reads of `~/.ssh`, `~/.aws`, `~/.gnupg` or the daemon's token.
930
- Antigravity and OpenCode workers get no grants: agy ignores a hook's allow, and
931
- OpenCode's plugin has no hold.
932
-
933
- In a Team org, only the org policy's `permission_grants` block decides what may
934
- be granted. It is read again on every request:
910
+ **Saved actions.** The app's Inbox groups pending asks by kind and general
911
+ form (a push by its destination, a file by its top folder, a command by its
912
+ first two words, an MCP tool or its server). One decision answers the group:
913
+ once, for the session, for a time, until revoked, deny, or always deny. Anything
914
+ kept is a saved action, listed and editable under Settings → Permissions. A
915
+ saved deny stops every ask it covers. A saved allow settles an ask only within
916
+ the limits of the ask rule that asked (its scope and how long approvals may
917
+ last), never past a deny rule, and never once it has expired. Every use reads
918
+ the current rules again.
919
+
920
+ Saved actions and the grouped Inbox are in the app only for now. From the
921
+ CLI, saved actions still apply to every worker, but you can't list, edit or
922
+ delete one (`brindle permissions reset` removes them all, with your own and
923
+ learned rules), and without the app a held request falls back to the worker
924
+ CLI's own prompt in its pane, one exact request at a time. CLI commands for
925
+ both are tracked in
926
+ [#285](https://github.com/PawDelta/brindle/issues/285).
927
+
928
+ In a Team org, the org policy's `permission_grants` block bounds what a saved
929
+ allow may do. It is read again on every request:
935
930
 
936
931
  ```json
937
- "permission_grants": {"allow": true, "kinds": ["bash", "fetch"], "overridable": ["git-push"],
938
- "max_minutes": 30, "max_uses": 5, "allow_persistent": true,
939
- "deny": ["bash prefix terraform apply"]}
932
+ "permission_grants": {"allow": true, "kinds": ["bash", "fetch"], "max_minutes": 30,
933
+ "allow_persistent": true, "lifetimes": ["session", "time", "forever"],
934
+ "scopes": ["branch", "remote"], "deny": ["bash prefix terraform apply"]}
940
935
  ```
941
936
 
942
937
  - `deny` entries are enforced for every provider and can't be lifted.
943
- - `allow_persistent` permits remembered overrides of these two built-in denies; omitted or false allows only temporary overrides. This setting does not control ordinary saved allow rules. The admin policy editor is not delivered by this local engine change.
944
- - A policy without the block, an unknown or malformed field (which makes the whole policy unusable), a policy that can't be read, or a paused or shut-down member: no grants.
945
- - Without a Team org, any kind can be granted, for up to 60 min and 20 uses. Both built-in denies support one-time or remembered approvals.
946
- - The org backend that serves this block isn't part of this repository. Until it serves one, Team orgs get no grants. It is enforced from the cached policy, or in air-gap mode from `.brindle/policy.json`.
938
+ - A saved allow applies only for the listed `kinds`, `lifetimes` and `scopes`, with timed ones capped at `max_minutes`; one that lasts until revoked also needs `allow_persistent`.
939
+ - A policy without the block, one that can't be read, or a paused or shut-down member: no saved allow applies.
940
+ - Org admins edit the block in Settings → Organization approvals. It is enforced from the cached policy, or in air-gap mode from `.brindle/policy.json`.
947
941
 
948
942
  **Codex.** Codex runs a hook only once it's trusted, so with
949
943
  `permission_policy` on, brindle trusts its own hook the first time a Codex worker
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "brindle"
3
- version = "0.4.7"
3
+ version = "0.5.1"
4
4
  description = "Run coding agents in parallel, each on its own git branch, with a supervisor that delegates, reviews, and merges"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -36,7 +36,7 @@ Changelog = "https://pawdelta.com/brindle/changelog"
36
36
  Support = "https://pawdelta.com/brindle/docs/faq"
37
37
 
38
38
  [project.scripts]
39
- brindle = "brindle.cli:app"
39
+ brindle = "brindle.entry:main"
40
40
 
41
41
  # brindle Pro and Team (src/brindle/pro): inert without a verified entitlement.
42
42
  [project.entry-points."brindle.account"]
@@ -15,6 +15,6 @@ if len(sys.argv) >= 3 and sys.argv[1] == "_sidebar-follow":
15
15
  pass
16
16
  raise SystemExit(0)
17
17
 
18
- from brindle.cli import app
18
+ from brindle.entry import main
19
19
 
20
- app()
20
+ main()