feat(credits): gate scheduled agent runs on credits + Telegram alert on shortfall
What
runAgentWorkflow charged credits after the turn with no pre-gate, so an unfunded account (out of credits, no saved card to auto-recharge) kept running scheduled turns into a deep negative balance — WAVS Digital hit −1,563 credits (recoupable/chat#1885, "Split out — billing & engagement integrity").
This wires the existing request-path credit gate into the workflow so scheduled runs enforce the same rules as /api/research and the social-scrape routes, and alerts the team on shortfall.
Changes
gateWorkflowCredits(app/lib/workflows/) — a"use step"run BEFORErunAgentStep. ReusesensureCreditsOrShortCircuit(which attempts a silent auto-recharge against a saved card) with a 1-credit floor. On shortfall it fires a Telegram alert and returnshasCredits:false; the workflow skips the run before any model token is spent. Fails open on a transient Stripe/Supabase error so an infra blip never breaks every run.alertCreditShortfall(lib/credits/) — formats + sends the team Telegram alert (account/chat/session), swallowing errors so alerting never breaks the workflow (mirrorssendSalesNotification).runAgentWorkflow— gate first, inside the existingtry, sofinallystill runs cleanup (clear active stream, close writable, revoke ephemeral key) when a run is skipped. Accounts with a saved card top up transparently and proceed unchanged.
Covers both the headless /api/chat/runs path (the scheduled burners this targets) and the interactive /api/chat/workflow path — funded/carded accounts are unaffected either way.
Scope notes
- The optional floor on
deduct_credits_with_auditis intentionally left out — it's adatabase/migration (cross-submodule) and this pre-gate already stops the drain at its source. Recommend a follow-up in the billing-integrity tracker.
Verification
- TDD RED→GREEN for all three units:
alertCreditShortfall— sends alert w/ account+chat; never throws on Telegram failure.gateWorkflowCredits— available →hasCredits:trueno alert; shortfall →hasCredits:false+ alert; 1-credit floor; fail-open on throw.runAgentWorkflow— gates before the step; skips step/billing/auto-commit + no id minted on block; cleanup + ephemeral-key revoke still run on block.
- Full domain suites green:
lib/credits+app/lib/workflows— 140 tests passing. tsc --noEmit: no new errors in changed files (2 pre-existing baseline errors inrunAgentWorkflow.ts/.test.tsare unchanged, only line-shifted).eslintclean on all changed files.
Targets main.
Relates to recoupable/chat#1885 (split-out billing & engagement integrity).
🤖 Generated with Claude Code
Summary by cubic
Add a pre-run credit gate to scheduled and interactive agent runs to block unfunded accounts before any tokens are used. On shortfall, we send a Telegram alert; carded accounts auto‑recharge and proceed.
- New Features
- Gate checks a 1‑credit floor before
runAgentStep; on shortfall it skips the run and alerts Telegram. Cleanup still runs (stream clear/close, ephemeral key revoke). - Reuses the existing
ensureCreditsOrShortCircuitpath; silently tops up if a card is saved and fails open on transient Stripe/Supabase errors to avoid false blocks.
- Gate checks a 1‑credit floor before