Breeze RMM v0.108.0 — AI agents, per-partner BYO LLM keys, multi-currency, and quote revisions all land together.
This is a large release: 1,307 files and 26 migrations since v0.107.0. Most of it is four multi-wave programs finishing at once.
Summary
- AI agents (waves 1–3d) — agents, a runs ledger, an
ai_agentprincipal, agent-originated action intents, a headless runner in shadow mode, and anai_triageautomation action. Off by default behindBREEZE_AI_AGENTS_ENABLED. - Per-partner LLM BYOK (waves 1–4) — bring your own model provider key per partner, threaded through every AI call site, with a billing-source split so BYO usage doesn't draw platform credits. New AI Provider settings tab.
- Multi-currency (waves 3–8) — catalog price books and costs, ticketing currency snapshots, org-level currency selection, reporting-only FX with exchange rates, and Stripe/QBO seams.
- Quote revisions (W01–W05) — revision lineage, supersede-at-send, a Revise action, lineage banners, and portal replaced-quote views.
- The Guest Ledger — customer portal redesign.
- Tenant secret variables —
tenantSecretscript parameters are now live.
Added
- AI agents — agent records, runs ledger,
ai_agentprincipal and event types (#3838); permission-gated onai_agents:read/write(#3840); in-app approval notifications and an/approvalsinbox (#3877); agent-originated action intents — propose, decide, release (#3893, #3931); headless runner in shadow mode (#3953);ai_triageautomation action with event-target binding (#3976). - Per-partner LLM BYOK —
partner_llm_configswith a fail-loud resolver and/ai/providerroutes (#3889), partner key threaded through every AI call site (#3904),billing_sourcesplit with credit-deduction suppression (#3907), AI Provider settings tab and usage indicator (#3914). - Multi-currency — catalog price books and costs (#3811), ticketing currency snapshots (#3810), formatting and checkout readiness (#3809), org currency selection with an owner-approved active-contract escape hatch (#3874), reporting-only FX and exchange rates (#3883), Stripe freeze and typed QBO accounting seam (#3882).
- Quote revisions — revision lineage schema and superseded status (#3806),
reviseQuoteservice andPOST /quotes/:id/revise(#3829), supersede-at-send with concurrency hardening (#3873), retired public links now die at every token route (#3875), Revise action with lineage banners and portal replaced views (#3878). - Portal — The Guest Ledger customer portal redesign (#3814).
- Scripts — secret tenant variables via
tenantSecretparameters (#3820). - Mobile — swipe and multi-select to acknowledge alerts (#3727).
- Devices — durable agent uninstall on device Remove, behind fail-closed provenance (#4001). Behaviour-neutral in this release: the flag defaults off and no caller sets it yet.
Fixed
- Auth — passwordless SSO accounts can now enrol a first MFA factor via IdP re-authentication; previously such accounts could never satisfy
requireMfa()(#4018). - Auth — a throttled
/auth/refreshis treated as a wait rather than a logout (#3696). - Tenancy — the offboarding repair now rechecks its precondition under its own row lock, closing a race where an aborted offboarding could still queue agent uninstalls (#4022).
- Notifications — channel credentials are redacted before the test-failure reason is composed, not after (#3992).
- Web — late-arriving JWT scope no longer breaks ticketing tabs, patch approval, or the
#ringsdeep link (#4013, #4010). - Agent (macOS) — capture errors no longer conflate "no display attached" with "Screen Recording denied", and a capture timeout no longer asserts a permission cause it hasn't established (#4042).
- Plus ~40 further fixes across billing, patching, portal and CI.
Security
- SSO accounts could not satisfy the MFA gate at all — passwordless SSO users had no path to enrol a first factor, so MFA-gated actions were unreachable for them. Fixed via a bound, single-use IdP re-authentication grant scoped to first-factor enrolment only (#4018).
- Offboarding race — an operator aborting an offboarding could still have fresh
self_uninstallcommands queued against the rescued tenant, collected by the fleet as ordinary commands (#4022). - Credential exposure in notification-channel test errors — redaction now happens before the message is composed; normalisation is not substring-preserving, so scrubbing afterwards could publish a secret (#3992).
- Partner-wide write guard extended to
services/**, with distributor credential routes gated (#3928).
Two previously-fixed High-severity issues from v0.107.0 have now been publicly disclosed: GHSA-22hw-qc22-8g35 (shared patch-catalog metadata writable by any enrolled agent) and GHSA-2h5h-36rx-9r6r (restricted partner users could issue partner-wide credentials and rotate other admins' API keys). Both are fixed in 0.107.0 and later — if you are still on < 0.107.0, upgrade.
Self-Hosting / Upgrade Notes
No breaking changes.
Upgrade command — bump BREEZE_VERSION in /opt/breeze/.env, then:
docker compose pull api web portal && docker compose up -d binaries-init api web portal(portal is a separate container — include it.)
Database — 26 migrations, and they matter. All are idempotent and auto-apply on API boot via autoMigrate (unless AUTO_MIGRATE=false). They create the AI-agent, LLM-config, exchange-rate, quote-revision and currency tables and add columns to existing ones. No large-table rewrites or backfills — the one backfill (managed-triage-automation-backfill) touches automation rows only. Take a database snapshot before upgrading anyway: this is the largest migration set in a single Breeze release so far.
Privileged statements were checked against a non-superuser role before tagging: the range contains no CREATE ROLE, no ALTER … OWNER TO, and no CREATE EXTENSION — the three constructs that broke a managed-Postgres deploy in v0.95.0. The GRANTs present are all on tables created in the same migration.
No new required environment variables. One new optional flag:
BREEZE_AI_AGENTS_ENABLED— platform kill switch for AI agents. Defaults tofalse. Leave it unset and nothing in the AI-agent feature set runs. If you set it, remember it must also be mapped in theapiservice'senvironment:block of your compose file — a value in.envalone is a silent no-op.
Behaviour changes
- AI agents are off by default and additionally gated on the
ai_agents:read/writepermission, so no existing role gains access implicitly. - Multi-currency is opt-in per organization. Existing orgs keep their current currency until an owner selects otherwise; there is an owner-approved escape hatch for orgs with active contracts.
- Device Remove is unchanged in this release. The durable-uninstall path (#4001) ships disabled — its flag defaults off and no caller sets it. The UI that opts in comes later.
Full Changelog: v0.107.0...v0.108.0
What's Changed
- feat: multi-currency wave 5 — formatting & checkout readiness (#3777) by @ToddHebebrand in #3809
- feat: multi-currency wave 3 — catalog price books & costs (#3775) by @ToddHebebrand in #3811
- fix(mobile): throttle truncation reports to a state change (#3783) by @bdunncompany in #3795
- feat(portal): The Guest Ledger — customer portal redesign by @ToddHebebrand in #3814
- feat(quotes): W01 — revision lineage schema + superseded status by @ToddHebebrand in #3806
- refactor(portal): MarkTone union, shared reply cap, authorType on comments by @ToddHebebrand in #3815
- feat: multi-currency wave 4 — ticketing currency snapshots, match-or-skip rates, assembly blocked groups (#3776) by @ToddHebebrand in #3810
- feat(quotes): W02 — reviseQuote service + POST /quotes/:id/revise by @ToddHebebrand in #3829
- feat(api,web): activate secret tenant variables in scripts via tenantSecret parameters (#3409 PR4c-2) by @ToddHebebrand in #3820
- fix: multi-currency wave 5 review follow-ups — legacy document_locale snapshot, portal pay-link transactions, Stripe cache failure modes, PDF safety (#3777) by @ToddHebebrand in #3833
- fix: multi-currency wave 3 review follow-ups — contract catalog lines, feed-currency costs, resolver representability, bundle currency math (#3775) by @ToddHebebrand in #3832
- feat(ai): AI agents wave 1 — agents, runs ledger, ai_agent principal, event types by @ToddHebebrand in #3838
- docs: v0.107.0 release sweep (v0.105.1..v0.107.0) by @ToddHebebrand in #3835
- docs(release-skill): fork-era abuse-asset gate + hosted signing lane step by @ToddHebebrand in #3830
- feat(api,web): gate AI agent policies on ai_agents:read/write by @ToddHebebrand in #3840
- feat: multi-currency wave 6 — org currency selection, release gate, active-contract escape hatch (#3778) by @ToddHebebrand in #3874
- feat: multi-currency wave 8 — Stripe §10 freeze + typed QBO accounting seam (#3780) by @ToddHebebrand in #3882
- feat: multi-currency wave 7 — reporting-only FX, exchange rates and dashboard segmentation (#3779) by @ToddHebebrand in #3883
- docs(specs): per-partner LLM API config (hosted Anthropic BYOK) design by @ToddHebebrand in #3888
- fix(api): hosted agent update 409 — canonical asset names, key-ID-bound verification, fail-closed local registration (#3836) by @ToddHebebrand in #3872
- feat(api): per-partner LLM BYOK wave 1 — partner_llm_configs, fail-loud resolver, /ai/provider routes by @ToddHebebrand in #3889
- chore(deps): bump the github-actions group across 1 directory with 5 updates by @dependabot[bot] in #3819
- fix(api): lock the devices row before the cascade deletes its children by @bdunncompany in #3760
- feat(quotes): W03 — supersede-at-send + concurrency hardening by @ToddHebebrand in #3873
- feat(api): per-partner LLM BYOK wave 2 — partner key threaded through every AI call site by @ToddHebebrand in #3904
- feat(quotes): W04 — retired public link dies at every token route by @ToddHebebrand in #3875
- feat(quotes): W05 — Revise action, lineage banners, portal replaced views by @ToddHebebrand in #3878
- test(quotes): W06 — revision race matrix + constraint proofs (integration) by @ToddHebebrand in #3879
- feat(api): agent-originated action intent schema (AI agents wave 3a) by @ToddHebebrand in #3893
- feat(api): per-partner LLM BYOK wave 3 — billing_source split, credit-deduction suppression by @ToddHebebrand in #3907
- feat(api,web): in-app approval notifications + /approvals inbox (AI agents wave 2) by @ToddHebebrand in #3877
- feat(web,docs): per-partner LLM BYOK wave 4 — AI Provider settings tab, usage indicator, BYO-LLM docs by @ToddHebebrand in #3914
- fix(api): align stripeConnect route gating with the partner-wide access pattern by @ToddHebebrand in #3916
- test(api): extend the partner-wide write guard to services/**; gate distributor credential routes by @ToddHebebrand in #3928
- fix(api): make every captureMessage event groupable in Sentry (BREEZE-18) by @ToddHebebrand in #3913
- fix(api): deadlock on vuln correlation vs agent software ingest (40P01) by @ToddHebebrand in #3911
- fix(mobile): make a release build without a Sentry DSN impossible by @ToddHebebrand in #3910
- fix(api): stagger epoch-aligned repeatable jobs off the 00:00 UTC pile by @ToddHebebrand in #3915
- fix(api): stop reporting expected BullMQ retries as error-level Sentry events (BREEZE-1A, BREEZE-1J) by @ToddHebebrand in #3912
- feat(api): agent-originated intents live — propose, decide, release (AI agents wave 3b) by @ToddHebebrand in #3931
- chore(deps): bump the react-native-animation group with 2 updates by @dependabot[bot] in #3934
- chore(deps): bump @types/pg from 8.21.0 to 8.23.1 in /e2e-tests by @dependabot[bot] in #3935
- chore(deps): bump vitest from 4.1.10 to 4.1.11 in /e2e-tests by @dependabot[bot] in #3936
- chore(deps): bump @types/office-js from 1.0.605 to 1.0.606 in the typescript-tooling group by @dependabot[bot] in #3938
- chore(deps): bump the astro group with 3 updates by @dependabot[bot] in #3939
- chore(deps): bump @fastify/busboy from 3.2.0 to 3.2.2 by @dependabot[bot] in #3950
- fix(api): serialise bundle composition on a globally ordered item lock (#3816) by @bdunncompany in #3831
- feat(api): headless AI-agent runner in shadow mode (AI agents wave 3c) by @ToddHebebrand in #3953
- chore(deps): bump node-stream-zip from 1.15.0 to 1.16.0 by @dependabot[bot] in #3949
- perf(api): stop the hourly risk-score refresh pinning a DB connection for 2 minutes by @ToddHebebrand in #3793
- chore(deps): bump hono from 4.13.2 to 4.13.3 in the hono group across 1 directory by @dependabot[bot] in #3942
- chore(deps): bump the testing group across 1 directory with 3 updates by @dependabot[bot] in #3947
- chore(deps): bump eslint from 10.8.1 to 10.9.0 in the linting group across 1 directory by @dependabot[bot] in #3948
- chore(deps): bump the mobile group across 1 directory with 5 updates by @dependabot[bot] in #3937
- fix(deps): repair broken pnpm-lock.yaml (duplicated hono key reddens every CI job) by @ToddHebebrand in #3957
- test(web): give the InvoiceWorkspace waitFor budget room under the test timeout (#3277) by @ToddHebebrand in #3956
- docs(mobile): implementation plan for mobile time entry (#3206) by @ToddHebebrand in #3903
- fix(api): unwrap DrizzleQueryError when reading SQLSTATE in softwareInstallMethods (#3881) by @ToddHebebrand in #3921
- fix(web,api): alert rule "Test" reports the real evaluation verdict (#3752) by @ToddHebebrand in #3923
- fix(api,shared): source audit actor_type/result enums from shared constants (#3908) by @ToddHebebrand in #3924
- fix(web,api): persist and show WHY a notification channel test failed (#3697) by @ToddHebebrand in #3926
- fix(web): stop a double-click on a confirm dialog hit-testing through to the list (#3705) by @ToddHebebrand in #3929
- fix(agent): force a desktop repaint before the remote-desktop startup probe (#3951) by @ToddHebebrand in #3960
- fix(agent): volume-aware recursive-delete boundary so Windows nested paths delete (#3932) by @ToddHebebrand in #3961
- fix(api): count partner-level ring assignments via organizations.partner_id (#3954) by @ToddHebebrand in #3962
- feat(api,web): ai_triage automation action with event-target binding (AI agents wave 3d) by @ToddHebebrand in #3976
- fix(web): render angle brackets and HTML entities in locale strings (#3964) by @ToddHebebrand in #3971
- fix(web): restore the dropped space between i18n labels and their values (#3965) by @ToddHebebrand in #3972
- fix(alerts): resolve acknowledged/resolved-by user ids to display names (#3966) by @ToddHebebrand in #3973
- fix(api): enforce organizations.slug uniqueness per partner in Postgres (#3967) by @ToddHebebrand in #3974
- fix(auth): treat a throttled /auth/refresh as a wait, not a logout (#3696) by @ToddHebebrand in #3975
- fix(web): keep the #rings deep link alive until the JWT scope is known (#4010) by @ToddHebebrand in #4012
- fix(web): confirm-gate decommission from the device list/grid kebab (#4009) by @ToddHebebrand in #4011
- fix(web): rename device Decommission to Remove, and stop every surface offering the action the API rejects by @ToddHebebrand in #3994
- fix(web): put the alert-rule Test verdict on the live editor (#3988) by @ToddHebebrand in #4016
- chore(deps): bump the aws-sdk group in /agent with 5 updates by @dependabot[bot] in #4005
- chore(deps): bump cloud.google.com/go/storage from 1.64.0 to 1.65.0 in /agent by @dependabot[bot] in #4006
- fix(api): unwrap the SQLSTATE in tenantCascade's isUndefinedTable (#3927) by @bdunncompany in #3998
- fix(web): un-nest the AutomationTab delete button from the row expander (#3990) by @bdunncompany in #3999
- fix(web): surface the API's own error on the Organizations page mutations (#3989) by @bdunncompany in #4003
- feat(mobile): swipe and multi-select to acknowledge alerts without waiting by @bdunncompany in #3727
- fix(web): update #4009's confirm-dialog assertions to #3994's renamed copy (main is red) by @ToddHebebrand in #4023
- fix(web): stop the grid DeviceCard offering agent actions for network assets (#4014) by @ToddHebebrand in #4031
- fix(web): react to late-arriving JWT scope in ticketing tabs + patch approval (#4013) by @ToddHebebrand in #4030
- feat(api): durable agent uninstall on device Remove, behind fail-closed provenance by @ToddHebebrand in #4001
- fix(web): distinguish a zero-data list from a search that matched nothing (#3992) by @bdunncompany in #4032
- fix(api,web): stop losing agent error messages behind Cloudflare's 502/504 page by @ToddHebebrand in #4029
- fix(api): redact channel credentials before composing the test-failure reason (#3992) by @ToddHebebrand in #4015
- fix(api): recheck the tenant precondition under the repair's own lock (#4022) by @bdunncompany in #4034
- fix(ci): reconcile the drift detector with side-branch hotfix releases (#4037) by @ToddHebebrand in #4043
- fix(auth): let passwordless SSO accounts enroll a first MFA factor via IdP re-authentication (#4018) by @ToddHebebrand in #4041
- docs(claude-md): fix broken pnpm vitest filter guidance (#4047) by @ToddHebebrand in #4058
- fix(api,ci): pin a non-UTC TZ for auth/SSO test runs (#4046) by @ToddHebebrand in #4056
- fix(config,api): pass DB password vars through turbo + fail fast on skipped breeze_app bootstrap (#4048) by @ToddHebebrand in #4057
- fix(agent): stop macOS capture errors from guessing at their own cause (#4042) by @ToddHebebrand in #4061
- fix(ci,docs): enforce that a new migration sorts after every committed one by @ToddHebebrand in #4063
- test(api): pin the SSO reauth binding re-check against real Postgres (#4049) by @ToddHebebrand in #4065
Full Changelog: v0.107.0...v0.108.0