Skip to content

Phase 3: wire AgentBudgetGuard onto the LLM USD cost seam - #38

Open
cryptoxdog wants to merge 2 commits into
claude/new-session-br7qjb-phase-2from
claude/new-session-br7qjb-phase-3
Open

Phase 3: wire AgentBudgetGuard onto the LLM USD cost seam#38
cryptoxdog wants to merge 2 commits into
claude/new-session-br7qjb-phase-2from
claude/new-session-br7qjb-phase-3

Conversation

@cryptoxdog

Copy link
Copy Markdown
Collaborator

Stacked PR — 3 of 3

Top of the stack. Stacked on #37 (Phase 2), which is stacked on #36 (Phase 1). Base is claude/new-session-br7qjb-phase-2; this PR's diff shows only Phase 3. Merge #36 then #37 first (or retarget to main once they land).

What this does

Activates AgentBudgetGuard (ADR-0008) in LlmService.execute against real USD signals — the seam left deferred in Phase 2.

Guard move Real signal
Admission / reserve (pre-dispatch) Seeded with the day's persistent spend (getDailySpend()llm_usage); reserves the call's real estimate from router.route().estimatedCost
Reconcile (post-dispatch) The actual response.cost
Mode signal getLlmService().getBudgetMode() exposes the ADR-0008 mode (normalstop) for cheaper-tier routing

Key behavioral win: a call is now deferred before any spend when its estimate wouldn't fit under the cap — earlier and stricter than the old enforceDailyCap, which only reacted after the day was already over. Opt-in via DAILY_SPEND_CAP; unset ⇒ guard inert, behavior unchanged.

Deferred (documented, not faked)

budget_violations rows are still not persisted: that table's job_id is NOT NULL REFERENCES agent_jobs(job_id), and no agent-job execution model exists yet to own the parent row. Left in TODO.md item 3 with the exact unblock trigger.

Validation

tsc --noEmit 0 errors · eslint src/ 0 errors · vitest 149/149 (4 new budget-guard-on-LLM tests) · manifest:check clean


Generated by Claude Code

Activate AgentBudgetGuard in LlmService.execute against real USD signals:

- Admission/reserve: before each call the guard is seeded with the day's real
  persistent spend (getDailySpend → llm_usage) and reserves the call's real
  pre-dispatch estimate (router.route().estimatedCost). If the reservation
  cannot fit even after the guard downshifts its ADR-0008 mode, the call is
  deferred BEFORE any spend — earlier and stricter than the previous post-hoc
  `spent >= cap` check, which ignored the incoming call's cost.
- Reconcile: after a successful call the guard reconciles the real
  response.cost; a post-dispatch breach flips mode to `stop` (logged, not
  thrown — the response was already paid for).
- getBudgetMode() exposes the current ADR-0008 mode so callers can route to a
  cheaper tier / narrow scope under pressure.

Opt-in via DAILY_SPEND_CAP; unset ⇒ guard inert, behaviour unchanged. Replaces
the ad-hoc enforceDailyCap. budget_violations persistence stays deferred (its
NOT NULL FK to agent_jobs needs an agent-job execution model that does not yet
exist) — recorded in TODO.md. RUNBOOK documents the guard and the new env var.

Validated: tsc --noEmit 0 errors, eslint src/ 0 errors, vitest 149/149,
manifest:check clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SjcHn8jZErKo37ZJi6qybX
The `typecheck` gate (tsconfig.check.json, which includes tests/) inferred
DAILY_SPEND_CAP as `number` from baseConfig, so the DAILY_SPEND_CAP: undefined
override in the "cap unset" case failed with TS2322. Widen the field to
`number | undefined`. No behavior change; the 4 budget-guard tests still pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SjcHn8jZErKo37ZJi6qybX
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant