brindle 0.3.0__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {brindle-0.3.0 → brindle-0.3.2}/PKG-INFO +90 -3
- {brindle-0.3.0 → brindle-0.3.2}/README.md +89 -2
- {brindle-0.3.0 → brindle-0.3.2}/pyproject.toml +1 -1
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/agents.py +53 -8
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/antigravity.py +111 -20
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/autopilot.py +4 -1
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/cli.py +28 -7
- brindle-0.3.2/src/brindle/company_identity.py +531 -0
- brindle-0.3.2/src/brindle/company_login.py +416 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/config.py +6 -2
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/cull.py +12 -4
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/doctor.py +59 -2
- brindle-0.3.2/src/brindle/identity_lock.py +71 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/keys_cmds.py +18 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/legacy.py +12 -1
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/mcp_server.py +16 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pane_auth.py +34 -3
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/permissions.py +183 -1
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pro/account.py +16 -1
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pro/auth.py +1 -1
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pro/team_policy.py +243 -2
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/providers.py +147 -1
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/repos.py +5 -1
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/rewind.py +2 -1
- brindle-0.3.2/src/brindle/self_serve.py +344 -0
- brindle-0.3.2/src/brindle/signin_pause.py +384 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/tasks.py +8 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/view.py +12 -1
- {brindle-0.3.0 → brindle-0.3.2}/.gitignore +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/LICENSE +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/SCHEDULE-A +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/__init__.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/__main__.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/account.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/airgap.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/budget.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/builtin_agents/__init__.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/builtin_agents/backend-auditor.md +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/builtin_agents/developer-codex.md +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/builtin_agents/developer-heavy.md +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/builtin_agents/developer-light.md +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/builtin_agents/developer-local.md +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/builtin_agents/developer.md +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/builtin_agents/qa.md +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/builtin_agents/reviewer-codex.md +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/builtin_agents/reviewer-local.md +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/builtin_agents/reviewer.md +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/builtin_agents/subagent.md +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/builtin_agents/supervisor.md +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/builtin_rules/__init__.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/builtin_rules/security/backend.md +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/builtin_rules/style/minimal-diff.md +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/builtin_rules/tests/only.md +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/ci_adapters.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/ci_client.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/ci_federation.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/ci_hosts.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/codemap.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/codex_hook.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/conflicts.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/cost.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/cost_estimate.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/db.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/demo.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/detect.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/escapes.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/events.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/gates.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/git.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/guardrails.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/history.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/inbox.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/keystore.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/learned_rules.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/learning.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/model_access.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/native/__init__.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/native/client.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/native/loop.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/native/permissions.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/native/runner.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/native/serve.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/native/tools.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pipeline.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/plugins.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/policy.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pool.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pricing.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pro/__init__.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pro/_files.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pro/audit_chain.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pro/audit_export.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pro/brief_learning.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pro/cost_centers.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pro/credentials.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pro/features.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pro/keys.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pro/learning.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pro/license.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pro/loopback.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pro/managed_models.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pro/org_profiles.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pro/orgkey.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pro/rollout.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pro/settings_sync.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pro/status.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/pro/team_events.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/procs.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/profiles.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/quota.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/repo_cmds.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/rule_checks.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/savings.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/scratch.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/secrets.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/services.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/sessions.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/sidebar_follow.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/status_cache.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/tmux.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/usage.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/watch.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/watch_costs.py +0 -0
- {brindle-0.3.0 → brindle-0.3.2}/src/brindle/workspaces.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: brindle
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Run coding agents in parallel, each on its own git branch, with a supervisor that delegates, reviews, and merges
|
|
5
5
|
Project-URL: Homepage, https://pawdelta.com/brindle/
|
|
6
6
|
Project-URL: Documentation, https://pawdelta.com/brindle/docs/recommended-use
|
|
@@ -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.3.
|
|
92
|
+
session shows it too (`brindle 0.3.2`). 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
|
|
@@ -363,6 +363,7 @@ your own status line prints, so what you see doesn't change.
|
|
|
363
363
|
| `brindle learning` | whether brindle Pro's hosted learning is on for this repo, and if not, why (nothing is learned on your machine) |
|
|
364
364
|
| `brindle learning seed` | Pro: sends this repo's finished routing history to the hosted learner once, so it starts with what this machine has seen. Only the usual coarse record fields leave (kind/size one-hots, counters, HMAC keys), never task text or paths. A second run sends nothing new; decisions already sent live are skipped |
|
|
365
365
|
| `brindle account [login\|logout\|status\|upgrade\|portal\|seats\|org]` | paid features: bare `brindle account` shows what your plan has and how to get the rest (see "brindle Pro and Team" below) |
|
|
366
|
+
| `brindle login` | the same as `brindle account login`; when your org's policy has an `agent_setup` block it also signs you in to the Claude and Codex route the company chose (AWS SSO profile, `gcloud`, `az`, `claude auth login`, `codex login`), stores the Claude Code environment for brindle's panes in `~/.brindle/config.json`, and checks the pinned models. `brindle doctor --fix` does the same |
|
|
366
367
|
| `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) |
|
|
367
368
|
| `brindle watch [--all] [--once]` | the dashboard on its own (the same view as the sidebar): enter attaches, `p` peeks, `x` closes |
|
|
368
369
|
| `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 |
|
|
@@ -538,6 +539,7 @@ Autopilot, merge gates and cleanup:
|
|
|
538
539
|
| `merge_into` | none | branch that worker branches are cut from and merge into, whatever branch the supervisor is on |
|
|
539
540
|
| `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) |
|
|
540
541
|
| `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 |
|
|
542
|
+
| `agy_approvals` | `"prompt"` | `"brindle"`: with the policy on, Antigravity workers run without approval prompts and brindle's hook allows or denies every call (see "Antigravity" under "Permission policy") |
|
|
541
543
|
| `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 |
|
|
542
544
|
| `plan_first` | `false` | workers propose a plan (`submit_plan`) and wait for `approve_plan` before editing |
|
|
543
545
|
| `overlap` | `"block"` | a task whose `files` overlap a running task's is refused (`"warn"` starts it with a warning) |
|
|
@@ -725,6 +727,21 @@ does it by hand. To undo, turn `permission_policy` off and run `sync-agy`: brind
|
|
|
725
727
|
removes exactly its own entries. brindle doesn't learn from agy approvals (its
|
|
726
728
|
hook runs for every tool call, so a tool running doesn't mean you approved it).
|
|
727
729
|
|
|
730
|
+
With `"agy_approvals": "brindle"` (and the policy on), brindle's hook is the
|
|
731
|
+
only gate instead: agy workers start with `--dangerously-skip-permissions`,
|
|
732
|
+
under which agy still honors a hook's deny, and the hook runs on every tool,
|
|
733
|
+
subagents' calls included. It allows what a rule allows (a repo's checks
|
|
734
|
+
included), the shell commands in the profile's `allowed_tools` (what a Claude
|
|
735
|
+
Code worker on it runs without asking), files inside the workspace given as one
|
|
736
|
+
absolute path (writes under `.agents/`, `.git/` and
|
|
737
|
+
`.brindle/` only by a rule, since those hold the hook, git's hooks and the
|
|
738
|
+
repo's checks), brindle's own tools and agy's tools that act only on the
|
|
739
|
+
conversation, and denies everything else, telling the agent to
|
|
740
|
+
ask its supervisor; you allow it with `brindle permissions allow`. Any failure
|
|
741
|
+
in the hook is a deny, and a tool brindle doesn't know is denied. Nobody can
|
|
742
|
+
approve a prompt in the agent's pane in this mode. The default, `"prompt"`,
|
|
743
|
+
leaves agy asking as described above.
|
|
744
|
+
|
|
728
745
|
## brindle Pro and Team
|
|
729
746
|
|
|
730
747
|
brindle is complete on its own. brindle Pro adds hosted learning (which profile
|
|
@@ -762,6 +779,7 @@ workflow.
|
|
|
762
779
|
|
|
763
780
|
```sh
|
|
764
781
|
brindle account # paid features: what you have, how to use them, how to get the rest
|
|
782
|
+
brindle login # the same as `brindle account login`; also applies your org's agent_setup (company agent sign-in)
|
|
765
783
|
brindle account login # opens your browser to sign in (--device: enter a code instead, e.g. over SSH); brindle checks the plan offline from then on
|
|
766
784
|
brindle account status # your plan, features, hosted learning on or off, when the entitlement expires
|
|
767
785
|
brindle account savings # what hosted learning's picks gained in this repo, this month and last: estimates, from this machine's records only
|
|
@@ -778,6 +796,51 @@ brindle account org member policy-role <member> <role|none> # give a member a
|
|
|
778
796
|
brindle account org company [link <org_id> | unlink] # link orgs you own into one company; learning is pooled only within it
|
|
779
797
|
```
|
|
780
798
|
|
|
799
|
+
**Sign-in loss pauses agents.** While agents run, brindle checks every 30 s that
|
|
800
|
+
Claude and Codex are still signed in (`claude auth status` or the cloud route's
|
|
801
|
+
identity, re-checked only when its credentials change; `codex login status`;
|
|
802
|
+
Antigravity can't be checked). If one is signed out, its running agents pause
|
|
803
|
+
with their worktrees, commits and conversations kept, and the supervisor and
|
|
804
|
+
your session are told; a timeout or missing CLI never pauses, it warns once.
|
|
805
|
+
A wrong company identity pauses them only when the org policy enforces it
|
|
806
|
+
(Enterprise); otherwise it warns once. While it enforces, Claude agents run
|
|
807
|
+
on exactly the route brindle checked: a profile's env lines can't change the
|
|
808
|
+
backend, account, key or proxy, and other cloud and Claude credential variables
|
|
809
|
+
(`ANTHROPIC_*`, `AWS_*`, `GOOGLE_*`, `CLOUDSDK_*`, `AZURE_*`, ...) in your
|
|
810
|
+
environment or `brindle keys` are left out of their panes, so those agents
|
|
811
|
+
can't use your own AWS, Google Cloud or Azure access in their shell either.
|
|
812
|
+
Claude Code's own settings files can still set variables inside it; to rule
|
|
813
|
+
that out too, use Claude Code's managed settings. After `brindle login`,
|
|
814
|
+
`brindle doctor --fix` or the CLI's own login, the agents brindle paused resume
|
|
815
|
+
into their conversations, but only as the account they started under: if
|
|
816
|
+
someone else is signed in (a personal login, say) they stay paused until the
|
|
817
|
+
original account is back. Your own chat session is never paused, only the
|
|
818
|
+
workers. A cloud sign-in is re-checked only when its credential files or
|
|
819
|
+
variables change, so a token that expires silently isn't noticed until then.
|
|
820
|
+
`brindle doctor` lists agents paused for sign-in.
|
|
821
|
+
|
|
822
|
+
**Sessions stay with their identity.** Each Claude agent records the identity it
|
|
823
|
+
launched under (Claude org, AWS account and role, GCP project, Azure
|
|
824
|
+
subscription; ids only, never tokens). While a different identity is signed in,
|
|
825
|
+
that agent is locked: brindle refuses to resume it (`brindle continue`), rewind
|
|
826
|
+
it, message or hand off to it, start a new agent in its worktree (including
|
|
827
|
+
naming its branch for a new worker), or `peek` at it, with "This session
|
|
828
|
+
belongs to <org/account>; sign in as it to continue (`brindle login`)".
|
|
829
|
+
`brindle ls`, `watch`, the sidebar, `list_agents` and `agent_turns` show it as
|
|
830
|
+
`locked: <org/account>` without its conversation (usage, last activity, turn
|
|
831
|
+
summaries), and `depends_on` and `merge_workspace` won't pull its branch into a
|
|
832
|
+
session under another identity. Agents with no recorded identity (started before
|
|
833
|
+
this, or without a company setup) are unaffected, and so is everything while the
|
|
834
|
+
current identity can't be told (signed out, CLI missing).
|
|
835
|
+
|
|
836
|
+
This is a guard inside brindle, not a sandbox: on one OS user account the
|
|
837
|
+
worktrees, transcripts and credential files stay readable outside brindle (a
|
|
838
|
+
shell, another tool). Real separation needs a separate OS user or your
|
|
839
|
+
company's device management. Codex and Antigravity can't be told apart by
|
|
840
|
+
account (brindle only sees "signed in"), so their sessions are paused on
|
|
841
|
+
sign-out but can't be locked to one account; isolation applies to Claude routes
|
|
842
|
+
(subscription, Bedrock, Vertex, Foundry).
|
|
843
|
+
|
|
781
844
|
### Several repos in one session (brindle Pro)
|
|
782
845
|
|
|
783
846
|
A session can work across several repos. Attach another local repo you own
|
|
@@ -1258,7 +1321,7 @@ it's signed in without a plan that includes it, is reported to its supervisor.
|
|
|
1258
1321
|
| Provider | Key login (environment variables) |
|
|
1259
1322
|
|---|---|
|
|
1260
1323
|
| `claude` | yes: `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, `CLAUDE_CODE_OAUTH_TOKEN`, or Bedrock/Vertex/Foundry (`CLAUDE_CODE_USE_BEDROCK`, `CLAUDE_CODE_USE_VERTEX`, `CLAUDE_CODE_USE_FOUNDRY`) |
|
|
1261
|
-
| `codex` | yes: `OPENAI_API_KEY`, `CODEX_API_KEY` |
|
|
1324
|
+
| `codex` | yes: `OPENAI_API_KEY`, `CODEX_API_KEY`. With Codex signed in with ChatGPT (checked on Codex CLI 0.162.0): interactive `codex`, which brindle runs in agent panes, ignores both keys and uses the ChatGPT plan, and only `codex login --with-api-key` switches it to a key; headless `codex exec` (Brindle-CI) bills `CODEX_API_KEY` over the login and ignores `OPENAI_API_KEY`. `brindle doctor` and `brindle keys set` say so, and `auth: api_key` on an interactive Codex profile is refused under a ChatGPT login (run `codex login --with-api-key`, or use `auth: subscription`) |
|
|
1262
1325
|
| `antigravity` | `GEMINI_API_KEY`, only with `"modelProvider": "gemini"` in `agy`'s `settings.json` (see [Google Antigravity](#google-antigravity)); otherwise `agy`'s own browser sign-in |
|
|
1263
1326
|
|
|
1264
1327
|
Workers that run unattended should use key login where the CLI supports it.
|
|
@@ -1666,6 +1729,30 @@ marks the agent idle and delivers any queued message. Codex has no Stop hook, so
|
|
|
1666
1729
|
an autopilot supervisor on Codex that turn end is also where brindle tells it to keep
|
|
1667
1730
|
going, with the same limit on reminders as Claude Code.
|
|
1668
1731
|
|
|
1732
|
+
### Using your company's access
|
|
1733
|
+
|
|
1734
|
+
Two ways to run brindle's agents on your company's Claude (Bedrock, Vertex,
|
|
1735
|
+
Foundry, a Team or Enterprise plan) and Codex, without handling keys:
|
|
1736
|
+
|
|
1737
|
+
- **Your org sets it up (brindle Team).** When your org's policy has an
|
|
1738
|
+
`agent_setup` block, `brindle login` (and `brindle doctor --fix`) signs you
|
|
1739
|
+
in to the route the company chose and `brindle doctor` shows which identity
|
|
1740
|
+
each agent uses. It always wins over anything below.
|
|
1741
|
+
- **You set it up yourself (any plan).** When your org has no `agent_setup`,
|
|
1742
|
+
`brindle login` looks, read-only, at what this machine is already signed in
|
|
1743
|
+
to: Claude Code's managed settings (if your IT configures Claude Code,
|
|
1744
|
+
brindle just uses it), `claude auth status`, AWS SSO profiles in
|
|
1745
|
+
`~/.aws/config`, the active `gcloud` project, the active `az` subscription
|
|
1746
|
+
and `codex login status`. It never reads tokens or credential files. It
|
|
1747
|
+
proposes what it found ("Use Bedrock (AWS 1234…, role Dev, us-east-1)?");
|
|
1748
|
+
the default is no, and with no terminal it is always no. A yes is saved as
|
|
1749
|
+
`"agent_setup"` in `~/.brindle/config.json`, in the same format as the org
|
|
1750
|
+
policy's (`enforce` is never applied to it), and `brindle doctor` labels it
|
|
1751
|
+
as self-serve and says who is billed.
|
|
1752
|
+
|
|
1753
|
+
You must be authorized to use any company credentials you connect: brindle only
|
|
1754
|
+
signs in with the access you already have, it doesn't grant any.
|
|
1755
|
+
|
|
1669
1756
|
## Google Antigravity
|
|
1670
1757
|
|
|
1671
1758
|
brindle runs Google Antigravity's terminal agent, `agy`, as well as Claude Code and
|
|
@@ -59,7 +59,7 @@ uv tool install --editable ~/Projects/brindle # or from a local checkout
|
|
|
59
59
|
brindle drives Claude Code, so you need that too (`npm install -g @anthropic-ai/claude-code`).
|
|
60
60
|
|
|
61
61
|
`brindle --version` prints the installed version; the tmux status bar of every brindle
|
|
62
|
-
session shows it too (`brindle 0.3.
|
|
62
|
+
session shows it too (`brindle 0.3.2`). A session started before an upgrade keeps
|
|
63
63
|
running the old code, and shows the old number, until you restart it.
|
|
64
64
|
|
|
65
65
|
## Quick start
|
|
@@ -333,6 +333,7 @@ your own status line prints, so what you see doesn't change.
|
|
|
333
333
|
| `brindle learning` | whether brindle Pro's hosted learning is on for this repo, and if not, why (nothing is learned on your machine) |
|
|
334
334
|
| `brindle learning seed` | Pro: sends this repo's finished routing history to the hosted learner once, so it starts with what this machine has seen. Only the usual coarse record fields leave (kind/size one-hots, counters, HMAC keys), never task text or paths. A second run sends nothing new; decisions already sent live are skipped |
|
|
335
335
|
| `brindle account [login\|logout\|status\|upgrade\|portal\|seats\|org]` | paid features: bare `brindle account` shows what your plan has and how to get the rest (see "brindle Pro and Team" below) |
|
|
336
|
+
| `brindle login` | the same as `brindle account login`; when your org's policy has an `agent_setup` block it also signs you in to the Claude and Codex route the company chose (AWS SSO profile, `gcloud`, `az`, `claude auth login`, `codex login`), stores the Claude Code environment for brindle's panes in `~/.brindle/config.json`, and checks the pinned models. `brindle doctor --fix` does the same |
|
|
336
337
|
| `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) |
|
|
337
338
|
| `brindle watch [--all] [--once]` | the dashboard on its own (the same view as the sidebar): enter attaches, `p` peeks, `x` closes |
|
|
338
339
|
| `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 |
|
|
@@ -508,6 +509,7 @@ Autopilot, merge gates and cleanup:
|
|
|
508
509
|
| `merge_into` | none | branch that worker branches are cut from and merge into, whatever branch the supervisor is on |
|
|
509
510
|
| `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) |
|
|
510
511
|
| `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 |
|
|
512
|
+
| `agy_approvals` | `"prompt"` | `"brindle"`: with the policy on, Antigravity workers run without approval prompts and brindle's hook allows or denies every call (see "Antigravity" under "Permission policy") |
|
|
511
513
|
| `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 |
|
|
512
514
|
| `plan_first` | `false` | workers propose a plan (`submit_plan`) and wait for `approve_plan` before editing |
|
|
513
515
|
| `overlap` | `"block"` | a task whose `files` overlap a running task's is refused (`"warn"` starts it with a warning) |
|
|
@@ -695,6 +697,21 @@ does it by hand. To undo, turn `permission_policy` off and run `sync-agy`: brind
|
|
|
695
697
|
removes exactly its own entries. brindle doesn't learn from agy approvals (its
|
|
696
698
|
hook runs for every tool call, so a tool running doesn't mean you approved it).
|
|
697
699
|
|
|
700
|
+
With `"agy_approvals": "brindle"` (and the policy on), brindle's hook is the
|
|
701
|
+
only gate instead: agy workers start with `--dangerously-skip-permissions`,
|
|
702
|
+
under which agy still honors a hook's deny, and the hook runs on every tool,
|
|
703
|
+
subagents' calls included. It allows what a rule allows (a repo's checks
|
|
704
|
+
included), the shell commands in the profile's `allowed_tools` (what a Claude
|
|
705
|
+
Code worker on it runs without asking), files inside the workspace given as one
|
|
706
|
+
absolute path (writes under `.agents/`, `.git/` and
|
|
707
|
+
`.brindle/` only by a rule, since those hold the hook, git's hooks and the
|
|
708
|
+
repo's checks), brindle's own tools and agy's tools that act only on the
|
|
709
|
+
conversation, and denies everything else, telling the agent to
|
|
710
|
+
ask its supervisor; you allow it with `brindle permissions allow`. Any failure
|
|
711
|
+
in the hook is a deny, and a tool brindle doesn't know is denied. Nobody can
|
|
712
|
+
approve a prompt in the agent's pane in this mode. The default, `"prompt"`,
|
|
713
|
+
leaves agy asking as described above.
|
|
714
|
+
|
|
698
715
|
## brindle Pro and Team
|
|
699
716
|
|
|
700
717
|
brindle is complete on its own. brindle Pro adds hosted learning (which profile
|
|
@@ -732,6 +749,7 @@ workflow.
|
|
|
732
749
|
|
|
733
750
|
```sh
|
|
734
751
|
brindle account # paid features: what you have, how to use them, how to get the rest
|
|
752
|
+
brindle login # the same as `brindle account login`; also applies your org's agent_setup (company agent sign-in)
|
|
735
753
|
brindle account login # opens your browser to sign in (--device: enter a code instead, e.g. over SSH); brindle checks the plan offline from then on
|
|
736
754
|
brindle account status # your plan, features, hosted learning on or off, when the entitlement expires
|
|
737
755
|
brindle account savings # what hosted learning's picks gained in this repo, this month and last: estimates, from this machine's records only
|
|
@@ -748,6 +766,51 @@ brindle account org member policy-role <member> <role|none> # give a member a
|
|
|
748
766
|
brindle account org company [link <org_id> | unlink] # link orgs you own into one company; learning is pooled only within it
|
|
749
767
|
```
|
|
750
768
|
|
|
769
|
+
**Sign-in loss pauses agents.** While agents run, brindle checks every 30 s that
|
|
770
|
+
Claude and Codex are still signed in (`claude auth status` or the cloud route's
|
|
771
|
+
identity, re-checked only when its credentials change; `codex login status`;
|
|
772
|
+
Antigravity can't be checked). If one is signed out, its running agents pause
|
|
773
|
+
with their worktrees, commits and conversations kept, and the supervisor and
|
|
774
|
+
your session are told; a timeout or missing CLI never pauses, it warns once.
|
|
775
|
+
A wrong company identity pauses them only when the org policy enforces it
|
|
776
|
+
(Enterprise); otherwise it warns once. While it enforces, Claude agents run
|
|
777
|
+
on exactly the route brindle checked: a profile's env lines can't change the
|
|
778
|
+
backend, account, key or proxy, and other cloud and Claude credential variables
|
|
779
|
+
(`ANTHROPIC_*`, `AWS_*`, `GOOGLE_*`, `CLOUDSDK_*`, `AZURE_*`, ...) in your
|
|
780
|
+
environment or `brindle keys` are left out of their panes, so those agents
|
|
781
|
+
can't use your own AWS, Google Cloud or Azure access in their shell either.
|
|
782
|
+
Claude Code's own settings files can still set variables inside it; to rule
|
|
783
|
+
that out too, use Claude Code's managed settings. After `brindle login`,
|
|
784
|
+
`brindle doctor --fix` or the CLI's own login, the agents brindle paused resume
|
|
785
|
+
into their conversations, but only as the account they started under: if
|
|
786
|
+
someone else is signed in (a personal login, say) they stay paused until the
|
|
787
|
+
original account is back. Your own chat session is never paused, only the
|
|
788
|
+
workers. A cloud sign-in is re-checked only when its credential files or
|
|
789
|
+
variables change, so a token that expires silently isn't noticed until then.
|
|
790
|
+
`brindle doctor` lists agents paused for sign-in.
|
|
791
|
+
|
|
792
|
+
**Sessions stay with their identity.** Each Claude agent records the identity it
|
|
793
|
+
launched under (Claude org, AWS account and role, GCP project, Azure
|
|
794
|
+
subscription; ids only, never tokens). While a different identity is signed in,
|
|
795
|
+
that agent is locked: brindle refuses to resume it (`brindle continue`), rewind
|
|
796
|
+
it, message or hand off to it, start a new agent in its worktree (including
|
|
797
|
+
naming its branch for a new worker), or `peek` at it, with "This session
|
|
798
|
+
belongs to <org/account>; sign in as it to continue (`brindle login`)".
|
|
799
|
+
`brindle ls`, `watch`, the sidebar, `list_agents` and `agent_turns` show it as
|
|
800
|
+
`locked: <org/account>` without its conversation (usage, last activity, turn
|
|
801
|
+
summaries), and `depends_on` and `merge_workspace` won't pull its branch into a
|
|
802
|
+
session under another identity. Agents with no recorded identity (started before
|
|
803
|
+
this, or without a company setup) are unaffected, and so is everything while the
|
|
804
|
+
current identity can't be told (signed out, CLI missing).
|
|
805
|
+
|
|
806
|
+
This is a guard inside brindle, not a sandbox: on one OS user account the
|
|
807
|
+
worktrees, transcripts and credential files stay readable outside brindle (a
|
|
808
|
+
shell, another tool). Real separation needs a separate OS user or your
|
|
809
|
+
company's device management. Codex and Antigravity can't be told apart by
|
|
810
|
+
account (brindle only sees "signed in"), so their sessions are paused on
|
|
811
|
+
sign-out but can't be locked to one account; isolation applies to Claude routes
|
|
812
|
+
(subscription, Bedrock, Vertex, Foundry).
|
|
813
|
+
|
|
751
814
|
### Several repos in one session (brindle Pro)
|
|
752
815
|
|
|
753
816
|
A session can work across several repos. Attach another local repo you own
|
|
@@ -1228,7 +1291,7 @@ it's signed in without a plan that includes it, is reported to its supervisor.
|
|
|
1228
1291
|
| Provider | Key login (environment variables) |
|
|
1229
1292
|
|---|---|
|
|
1230
1293
|
| `claude` | yes: `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, `CLAUDE_CODE_OAUTH_TOKEN`, or Bedrock/Vertex/Foundry (`CLAUDE_CODE_USE_BEDROCK`, `CLAUDE_CODE_USE_VERTEX`, `CLAUDE_CODE_USE_FOUNDRY`) |
|
|
1231
|
-
| `codex` | yes: `OPENAI_API_KEY`, `CODEX_API_KEY` |
|
|
1294
|
+
| `codex` | yes: `OPENAI_API_KEY`, `CODEX_API_KEY`. With Codex signed in with ChatGPT (checked on Codex CLI 0.162.0): interactive `codex`, which brindle runs in agent panes, ignores both keys and uses the ChatGPT plan, and only `codex login --with-api-key` switches it to a key; headless `codex exec` (Brindle-CI) bills `CODEX_API_KEY` over the login and ignores `OPENAI_API_KEY`. `brindle doctor` and `brindle keys set` say so, and `auth: api_key` on an interactive Codex profile is refused under a ChatGPT login (run `codex login --with-api-key`, or use `auth: subscription`) |
|
|
1232
1295
|
| `antigravity` | `GEMINI_API_KEY`, only with `"modelProvider": "gemini"` in `agy`'s `settings.json` (see [Google Antigravity](#google-antigravity)); otherwise `agy`'s own browser sign-in |
|
|
1233
1296
|
|
|
1234
1297
|
Workers that run unattended should use key login where the CLI supports it.
|
|
@@ -1636,6 +1699,30 @@ marks the agent idle and delivers any queued message. Codex has no Stop hook, so
|
|
|
1636
1699
|
an autopilot supervisor on Codex that turn end is also where brindle tells it to keep
|
|
1637
1700
|
going, with the same limit on reminders as Claude Code.
|
|
1638
1701
|
|
|
1702
|
+
### Using your company's access
|
|
1703
|
+
|
|
1704
|
+
Two ways to run brindle's agents on your company's Claude (Bedrock, Vertex,
|
|
1705
|
+
Foundry, a Team or Enterprise plan) and Codex, without handling keys:
|
|
1706
|
+
|
|
1707
|
+
- **Your org sets it up (brindle Team).** When your org's policy has an
|
|
1708
|
+
`agent_setup` block, `brindle login` (and `brindle doctor --fix`) signs you
|
|
1709
|
+
in to the route the company chose and `brindle doctor` shows which identity
|
|
1710
|
+
each agent uses. It always wins over anything below.
|
|
1711
|
+
- **You set it up yourself (any plan).** When your org has no `agent_setup`,
|
|
1712
|
+
`brindle login` looks, read-only, at what this machine is already signed in
|
|
1713
|
+
to: Claude Code's managed settings (if your IT configures Claude Code,
|
|
1714
|
+
brindle just uses it), `claude auth status`, AWS SSO profiles in
|
|
1715
|
+
`~/.aws/config`, the active `gcloud` project, the active `az` subscription
|
|
1716
|
+
and `codex login status`. It never reads tokens or credential files. It
|
|
1717
|
+
proposes what it found ("Use Bedrock (AWS 1234…, role Dev, us-east-1)?");
|
|
1718
|
+
the default is no, and with no terminal it is always no. A yes is saved as
|
|
1719
|
+
`"agent_setup"` in `~/.brindle/config.json`, in the same format as the org
|
|
1720
|
+
policy's (`enforce` is never applied to it), and `brindle doctor` labels it
|
|
1721
|
+
as self-serve and says who is billed.
|
|
1722
|
+
|
|
1723
|
+
You must be authorized to use any company credentials you connect: brindle only
|
|
1724
|
+
signs in with the access you already have, it doesn't grant any.
|
|
1725
|
+
|
|
1639
1726
|
## Google Antigravity
|
|
1640
1727
|
|
|
1641
1728
|
brindle runs Google Antigravity's terminal agent, `agy`, as well as Claude Code and
|
|
@@ -20,7 +20,7 @@ import sys
|
|
|
20
20
|
import time
|
|
21
21
|
import uuid
|
|
22
22
|
|
|
23
|
-
from brindle import git, keystore, pane_auth, providers, secrets, tmux, workspaces
|
|
23
|
+
from brindle import company_identity, company_login, git, keystore, pane_auth, providers, secrets, tmux, workspaces
|
|
24
24
|
from brindle.config import RepoConfig
|
|
25
25
|
from brindle.db import DB, Agent, Workspace
|
|
26
26
|
from brindle.profiles import load_profile, missing_add_dirs
|
|
@@ -183,7 +183,19 @@ def agent_env(ws: Workspace, agent_id: str, agent: Agent | None = None,
|
|
|
183
183
|
# Claude Code's own managed settings (apiKeyHelper, a managed env) pick
|
|
184
184
|
# the credential and backend: brindle adds no provider_config env there.
|
|
185
185
|
org = agent.provider == "claude" and providers.claude_org_managed()
|
|
186
|
-
|
|
186
|
+
# The company's Claude Code env (`brindle login`): the lowest layer, so a
|
|
187
|
+
# profile's env lines and the org's managed provider both beat it; none
|
|
188
|
+
# at all under Claude Code's managed settings (stored_env).
|
|
189
|
+
company = {} if agent.provider != "claude" else company_login.stored_env()
|
|
190
|
+
managed_env = {} if org else managed_models.agent_env(m, agent.provider)
|
|
191
|
+
enforced = None if agent.provider != "claude" or org else company_identity.enforced_route_env()
|
|
192
|
+
if enforced is None:
|
|
193
|
+
env = {**company, **profile_env, **managed_env, **env}
|
|
194
|
+
else:
|
|
195
|
+
# An enforced company identity (Enterprise): the pane runs on the route
|
|
196
|
+
# launch_problem checked, whatever a profile's env lines say.
|
|
197
|
+
profile_env = {k: v for k, v in profile_env.items() if company_identity.profile_may_set(k)}
|
|
198
|
+
env = {**profile_env, **managed_env, **enforced, **env}
|
|
187
199
|
if agent is not None and preload_tools(agent, ws):
|
|
188
200
|
# Claude Code defers MCP tools and loads them on demand, which costs a
|
|
189
201
|
# worker an extra round trip at the moment it's told to report (and
|
|
@@ -262,7 +274,9 @@ def spawn(
|
|
|
262
274
|
finish line run it as a Claude Code ``/goal``. With ``autopilot``, the
|
|
263
275
|
agent is a session root that drives toward a goal (see brindle.autopilot)."""
|
|
264
276
|
from brindle import autopilot as pilot
|
|
277
|
+
from brindle import identity_lock
|
|
265
278
|
|
|
279
|
+
identity_lock.ensure_workspace(db, ws) # no new agent in another identity's worktree
|
|
266
280
|
profile = load_profile(profile_name, ws.repo_root)
|
|
267
281
|
provider = get_provider(provider_name or profile.provider)
|
|
268
282
|
agent_id = new_id()
|
|
@@ -440,6 +454,12 @@ def _open_window(db: DB, agent: Agent, ws: Workspace, name: str, argv: list[str]
|
|
|
440
454
|
org_env = managed_models.org_key_env(m, agent.provider)
|
|
441
455
|
except managed_models.ManagedUnavailable as e:
|
|
442
456
|
raise AgentError(f"managed models: {e}") from None
|
|
457
|
+
# An enforced company identity: also every account picker the tmux server
|
|
458
|
+
# would hand the pane or `brindle keys` holds for it (pane_auth covers this process's).
|
|
459
|
+
route = None if agent.provider != "claude" or org_managed else company_identity.enforced_route_env()
|
|
460
|
+
if route is not None:
|
|
461
|
+
extra = company_identity.enforced_deny(route, tmux.inherited_names(ws.tmux_session), credentials)
|
|
462
|
+
deny = (*deny, *(n for n in extra if n not in deny))
|
|
443
463
|
# Claude Code's managed settings supply the credential: no stored Claude key.
|
|
444
464
|
no_stored = set(deny) | (set(providers._ENV_AUTH["claude"]) if org_managed else set())
|
|
445
465
|
env = {**keystore.pane_keys(credentials - no_stored, {**env, **org_env}), **env, **org_env}
|
|
@@ -612,6 +632,14 @@ def _launch(db: DB, agent: Agent, ws: Workspace, *, prompt: str | None,
|
|
|
612
632
|
db.set_status(agent.id, status)
|
|
613
633
|
agent.status = status
|
|
614
634
|
return
|
|
635
|
+
# An org that enforces its company sign-in (Enterprise) refuses a
|
|
636
|
+
# different identity; one that can't be told never blocks.
|
|
637
|
+
why = company_identity.launch_problem(provider.name)
|
|
638
|
+
if why:
|
|
639
|
+
raise AgentError(why)
|
|
640
|
+
from brindle import signin_pause
|
|
641
|
+
|
|
642
|
+
signin_pause.record_launch(agent) # who it runs as, so a resume never crosses accounts
|
|
615
643
|
# What the agent will run with: the managed provider's variables, and
|
|
616
644
|
# no personal keys (blank, so the launcher's own copy doesn't count).
|
|
617
645
|
from brindle.pro import managed_models
|
|
@@ -906,10 +934,14 @@ def resume(db: DB, root_id: str, *, watch_pane: bool = True,
|
|
|
906
934
|
restart in their own workspaces. Claude Code picks up its previous
|
|
907
935
|
conversation (--resume); other CLIs restart on their original task.
|
|
908
936
|
With ``only``, just those agents (if paused) restart."""
|
|
937
|
+
from brindle import identity_lock
|
|
938
|
+
|
|
939
|
+
candidates = [a for a in tree(db, root_id)
|
|
940
|
+
if a.status == "paused" and (only is None or a.id in only)]
|
|
941
|
+
for a in candidates: # all or nothing: a session never half-resumes under another identity
|
|
942
|
+
identity_lock.ensure(a)
|
|
909
943
|
resumed = []
|
|
910
|
-
for a in
|
|
911
|
-
if a.status != "paused" or (only is not None and a.id not in only):
|
|
912
|
-
continue
|
|
944
|
+
for a in candidates:
|
|
913
945
|
ws = db.get_workspace(a.workspace_id)
|
|
914
946
|
if ws is None or not os.path.isdir(ws.path):
|
|
915
947
|
continue
|
|
@@ -1082,7 +1114,7 @@ def format_message(db: DB, body: str, sender_id: str | None) -> str:
|
|
|
1082
1114
|
return body
|
|
1083
1115
|
sender = db.get_agent(sender_id)
|
|
1084
1116
|
who = f"{sender.profile} agent {sender_id}" if sender else f"agent {sender_id}"
|
|
1085
|
-
return f"
|
|
1117
|
+
return f"<details><summary>Message from {who}</summary>\n\nReply with the brindle send_message tool, to_agent_id={sender_id}\n\n{body}\n</details>"
|
|
1086
1118
|
|
|
1087
1119
|
|
|
1088
1120
|
def message_lead(db: DB, agent: Agent, sender_id: str | None) -> str | None:
|
|
@@ -1129,8 +1161,8 @@ def render_unread(db: DB, messages: list) -> str:
|
|
|
1129
1161
|
if m.sender_id:
|
|
1130
1162
|
parts.append(m.body) # stored through format_message: sender and reply hint
|
|
1131
1163
|
else:
|
|
1132
|
-
parts.append(f"
|
|
1133
|
-
return "\n\n
|
|
1164
|
+
parts.append(f"<details><summary>Message from brindle</summary>\n\n{m.body}\n</details>")
|
|
1165
|
+
return "\n\n".join(parts)
|
|
1134
1166
|
|
|
1135
1167
|
|
|
1136
1168
|
def send_message(db: DB, to_id: str, body: str, sender_id: str | None = None,
|
|
@@ -1138,7 +1170,10 @@ def send_message(db: DB, to_id: str, body: str, sender_id: str | None = None,
|
|
|
1138
1170
|
"""Deliver now if the agent is idle; otherwise queue until it is.
|
|
1139
1171
|
Returns ``"delivered"`` or ``"queued"``. ``person`` marks a message from
|
|
1140
1172
|
the person running brindle, which is always pushed."""
|
|
1173
|
+
from brindle import identity_lock
|
|
1174
|
+
|
|
1141
1175
|
agent = get(db, to_id)
|
|
1176
|
+
identity_lock.ensure(agent)
|
|
1142
1177
|
if not person and runs_process(agent) and is_alive(agent) and pulls_messages(db, agent):
|
|
1143
1178
|
# Keep the message unread and deliver only a notice, once for everything unread.
|
|
1144
1179
|
db.enqueue_held(agent.id, format_message(db, body, sender_id), sender_id)
|
|
@@ -1168,6 +1203,9 @@ def _send_message(db: DB, agent: Agent, body: str, sender_id: str | None) -> str
|
|
|
1168
1203
|
raise AgentError(f"agent {agent.id} is not running")
|
|
1169
1204
|
provider = get_provider(agent.provider)
|
|
1170
1205
|
text = format_message(db, body, sender_id)
|
|
1206
|
+
if agent.provider == "antigravity":
|
|
1207
|
+
from brindle.antigravity import format_delivered_message
|
|
1208
|
+
text = format_delivered_message(text)
|
|
1171
1209
|
if agent.headless:
|
|
1172
1210
|
# Its runner starts the next turn with this, or the Stop hook hands it
|
|
1173
1211
|
# over if a turn is still running.
|
|
@@ -1648,6 +1686,13 @@ def delegate(
|
|
|
1648
1686
|
raise AgentError(str(e)) from e
|
|
1649
1687
|
if plan_first is None:
|
|
1650
1688
|
plan_first = load_repo_config(caller_ws.repo_root).plan_first
|
|
1689
|
+
from brindle import identity_lock
|
|
1690
|
+
|
|
1691
|
+
identity_lock.ensure_workspace(db, caller_ws)
|
|
1692
|
+
if isolate and branch: # naming a branch must not reuse another identity's worktree
|
|
1693
|
+
for other in db.find_workspaces(caller_ws.repo_root):
|
|
1694
|
+
if other.branch == branch:
|
|
1695
|
+
identity_lock.ensure_workspace(db, other)
|
|
1651
1696
|
if isolate:
|
|
1652
1697
|
caller_ws = workspaces.refresh_branch(db, caller_ws)
|
|
1653
1698
|
# merge_into applies to the supervisor's workers; a worker's own
|