## Background The 2026-04-26 incident saw 716M Sonnet 4.6 tokens consumed in a single trial workspace. Two causes: failed agent executions weren't billed (addressed by #20065) and the credit-cap gate had been removed from `WorkflowExecutorWorkspaceService.executeStep` in #19904, leaving no enforcement point at all. ## Why not just revert #19904 #19904 was right that gating at the workflow executor is too coarse. When one user exhausted a workspace's credits via chat, *all* workflows hard-failed mid-run — including cheap DB/CRUD/branch automations costing essentially nothing. Reverting would re-introduce that cliff. ## New design: gate at the AI entry points The chat resolver already gates this way (`agent-chat.resolver.ts:137-148`). This PR replicates the same pattern at every other point where the workspace can incur real AI cost: - `executeAgent` in `agent-async-executor.service.ts` - the REST handler in `ai-generate-text.controller.ts` - `generateThreadTitle` in `agent-title-generation.service.ts` In each, after auth/validation: skip if `IS_BILLING_ENABLED` is false; otherwise call `BillingService.canBillMeteredProduct(workspaceId, BillingProductKey.WORKFLOW_NODE_EXECUTION)`; on `false`, throw `BillingException(BILLING_CREDITS_EXHAUSTED)`. No new method, no new exception code, no new product key. This matches industry convention (Lovable/Replit also gate at the expensive-operation boundary, not at every cheap step). ## Deliberately not gated - `WorkflowExecutorWorkspaceService.executeStep` — the design choice is now intentional, so the #19904 TODO is replaced by a one-line absolute-behavior comment explaining why the gate isn't here. Cheap workflow steps (DB CRUD, branching, action steps) are not gated, so a chat-driven cap exhaustion does not block non-AI automations. - `repair-tool-call.util` — repair is a sub-call inside an already-gated AI flow. If the parent is gated, repair will naturally not run. Adding a gate here adds complexity without value. ## Net effect A workspace that exhausts credits via chat or AI agent stops making AI calls. Its non-AI workflows continue running normally. A workflow with both AI and non-AI steps fails at the AI step with `BILLING_CREDITS_EXHAUSTED`, but downstream non-AI steps that don't depend on the AI output still run. ## Conflicts This PR overlaps with three other in-flight PRs in the same files. None of them touch the gate logic; rebasing on top of any of them is trivial: - #20065 (agent-async-executor): adds `workspaceId` to `executeAgent` args and bills in `finally`. The gate at the top of `executeAgent` from this PR sits naturally above that. - #20066 (REST controller): adds usage billing to the controller. - #20067 (title gen): adds usage billing to title generation and tool-call repair. Recommend landing #20065/#20066/#20067 first; this PR rebases trivially on top. ## Tests Out of scope per the PR series convention. The existing chat-resolver gate isn't unit-tested either; this PR follows the same precedent. Follow-up: add integration coverage that exercises a workspace at `hasReachedCurrentPeriodCap=true` against each of the three new gates plus the pre-existing chat-resolver gate. ## Future follow-ups - Per-user soft cap inside a workspace (the Lovable Business-tier pattern), so one user can't exhaust the workspace's cap. - Pre-flight cost estimate so the user sees an "approaching cap" warning before the hard stop. - Rename `BillingProductKey.WORKFLOW_NODE_EXECUTION` — the name predates this design choice and is misleading now that it gates AI entry points rather than workflow nodes. ## Test plan - [ ] Trigger a workspace into `hasReachedCurrentPeriodCap=true`. - [ ] Send a chat message — expect failure with `BILLING_CREDITS_EXHAUSTED`. - [ ] Run a workflow whose only AI step is an `ai-agent` action — expect that step to fail with `BILLING_CREDITS_EXHAUSTED`, downstream non-AI steps still run. - [ ] POST to `/rest/ai/generate-text` — expect `BILLING_CREDITS_EXHAUSTED`. - [ ] Create a new chat thread (which kicks off `generateThreadTitle`) — expect `BILLING_CREDITS_EXHAUSTED`. - [ ] Run a workflow with no AI step (only DB CRUD/branching/actions) — expect it to run unaffected. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The #1 Open-Source CRM
Website ·
Documentation ·
Roadmap ·
Discord ·
Figma
Why Twenty
Twenty gives technical teams the building blocks for a custom CRM that meets complex business needs and quickly adapts as the business evolves. Twenty is the CRM you build, ship, and version like the rest of your stack.
Learn more about why we built Twenty
Installation
Cloud
The fastest way to get started. Sign up at twenty.com and spin up a workspace in under a minute, with no infrastructure to manage and always up to date.
Build an app
Scaffold a new app with the Twenty CLI:
npx create-twenty-app my-app
Define objects, fields, and views as code:
import { defineObject, FieldType } from 'twenty-sdk/define';
export default defineObject({
nameSingular: 'deal',
namePlural: 'deals',
labelSingular: 'Deal',
labelPlural: 'Deals',
fields: [
{ name: 'name', label: 'Name', type: FieldType.TEXT },
{ name: 'amount', label: 'Amount', type: FieldType.CURRENCY },
{ name: 'closeDate', label: 'Close Date', type: FieldType.DATE_TIME },
],
});
Then ship it to your workspace:
npx twenty deploy
See the app development guide for objects, views, agents, and logic functions.
Self-hosting
Run Twenty on your own infrastructure with Docker Compose, or contribute locally via the local setup guide.
Everything you need
Twenty gives you the building blocks of a modern CRM (objects, views, workflows, and agents) and lets you extend them as code. Here's a tour of what's in the box.
Want to go deeper? Read the User Guide for product walkthroughs, or the
Documentation for developer reference.
|
|
|
|
|
|
Stack
TypeScript
Nx
NestJS, with BullMQ,
PostgreSQL,
Redis
React, with Jotai, Linaria and Lingui
Thanks
Thanks to these amazing services that we use and recommend for UI testing (Chromatic), code review (Greptile), catching bugs (Sentry) and translating (Crowdin).
Join the Community
Star the repo ·
Discord ·
Feature requests ·
Releases ·
X ·
LinkedIn ·
Crowdin ·
Contribute





