doc/plans/2026-03-14-budget-policies-and-enforcement.md
Paperclip already treats budgets as a core control-plane responsibility:
doc/SPEC.md gives the Board authority to set budgets, pause agents, pause work, and override any budget.doc/SPEC-implementation.md says V1 must support monthly UTC budget windows, soft alerts, and hard auto-pause.Today the system has narrow money-budget behavior:
budgetMonthlyCents and spentMonthlyCentsbudgetMonthlyCents and spentMonthlyCentscost_events ingestion increments those countersThat leaves major product gaps:
This plan defines the concrete budgeting model Paperclip should implement next.
Paperclip should let operators:
/costs, and scope detail pages.The system should make one thing very clear:
They are related, but they are not the same concept.
For the next implementation pass, Paperclip should enforce these defaults:
This gives a clean mental model:
The first enforceable metric should be billed_cents.
Reasoning:
Token budgets should not be the first hard-stop policy. They should come later as advisory usage controls once the money-based system is solid.
Paperclip should separate subscription-included usage from billed spend:
subscription_included
subscription_overage
metered_api
This keeps the budget system honest:
Paperclip should have two threshold classes:
Default thresholds:
80%100%These should be configurable per policy later, but they are good defaults now.
Budget policies should support:
companyagentprojectThis plan focuses on finishing agent and project first while preserving the existing company budget behavior.
billed_centscalendar_month_utcbilled_centscalendar_month_utcbilled_centslifetimeFuture extensions can add:
The current codebase is not starting from zero, but the existing shape is too ad hoc to extend safely.
budget_policiesCreate a new table for canonical budget definitions.
Suggested fields:
idcompany_idscope_typescope_idmetricwindow_kindamountwarn_percenthard_stop_enablednotify_enabledis_activecreated_by_user_idupdated_by_user_idcreated_atupdated_atNotes:
scope_type is one of company | agent | projectscope_id is nullable only for company-level policy if company is implied; otherwise keep it explicitmetric should start with billed_centswindow_kind starts with calendar_month_utc | lifetimeamount is stored in the natural unit of the metricbudget_incidentsCreate a durable record of threshold crossings.
Suggested fields:
idcompany_idpolicy_idscope_typescope_idmetricwindow_kindwindow_startwindow_endthreshold_typeamount_limitamount_observedstatusapproval_id nullableactivity_id nullableresolved_at nullablecreated_atupdated_atNotes:
threshold_type: soft | hardstatus: open | acknowledged | resolved | dismissedProjects need explicit pause semantics.
Recommended approach:
Preferred shape:
execution_status: active | paused | archivedpause_reason: manual | budget | system | nullIf that is too large for the immediate pass, a smaller version is:
paused_atpause_reasonThe key requirement is behavioral, not cosmetic: Paperclip must know that a project is budget-paused and enforce it.
Existing company and agent monthly budget columns should remain temporarily for compatibility.
Migration plan:
budget_policies rowsBudget enforcement should move into a dedicated service.
Current logic is buried inside cost ingestion. That is too narrow because budget checks must apply at more than one execution boundary.
New service: budgetService
Responsibilities:
When a new cost_event is written:
cost_eventBudget enforcement cannot rely only on post-hoc cost ingestion.
Paperclip must also block execution before new work starts.
Add budget checks to:
If a scope is budget-paused:
When a hard-stop is triggered while a run is already active:
This mirrors the general pause semantics already expected by the product.
Budget hard-stops should create a first-class approval.
Add approval type:
budget_override_requiredPayload should include:
scopeTypescopeIdscopeNamemetricwindowKindthresholdTypebudgetAmountobservedAmountwindowStartwindowEndtopDriverspausedThe approval UI should support:
Optional later action:
Soft alerts should create:
They should not create an approval by default.
Budget events need obvious operator visibility.
Required outputs:
/costs summary of active incidents and policy healthLater channels:
Add routes for:
Add routes for:
Budget approvals should use the existing approval system once the new approval type is added.
Expected flows:
When work is blocked by budget, the API should return explicit errors.
Examples:
Do not silently no-op.
Budgeting should be visible in the places where operators make decisions.
/costsAdd a budget section that includes:
The page should make this visual distinction obvious:
Add an agent budget card:
Add a project budget card:
Project detail should also show if issue execution is blocked because the project is budget-paused.
Add a high-signal budget section:
The operator should not have to visit /costs to learn that work has stopped.
For V1.5 enforcement, include:
metered_api cost eventssubscription_overage cost eventsDo not include:
subscription_included cost events with zero billed centsToken budgets should not be the first hard-stop because:
Future policy metrics can include:
total_tokensinput_tokensoutput_tokensrequestsfinance_amount_centsBut they should enter only after the money-budget path is stable.
budget_policiesbudget_incidents/costs budget sectionbudget_policiesRequired coverage:
subscription_included usage does not consume money budgetsubscription_overage does consume money budget/costs surface active incidentsThese should be explicitly deferred unless they block implementation:
finance_events such as OpenRouter top-up fees and Bedrock provisioned charges?Implement the first coherent budgeting system with these rules:
This solves the real operator problem without mixing together spend control, provider quota windows, and token accounting.