phoenix1331/laravel-ai-headroom
Laravel AI Headroom gives Laravel apps a simple, configurable way to add AI features, manage prompts and providers, and keep token usage within safe limits. Integrates with Laravel's config, facades, and queues for reliable AI workflows.
EnsureHeadroom sits in the agent middleware stack and evaluates every generation step — including streams — against maxPercentOfContext, maxCost and the context window. This is the difference between "we noticed the bill at month end" and "the request never left the building." It directly de-risks shipping agentic features with loops and tool calls, where a single bad prompt can fan out into dozens of paid steps.->report() turns a hard block into a soft warning (HeadroomLow + pass-through), so you can ship a "you're approaching your limit" UX instead of a 500. Per-tenant ceilings become a plan-tier differentiator (free / pro / enterprise token allowances) rather than an ad-hoc rate limiter.Headroom::resolveContextUsing() tags requests with scalars like tenant_id, and events (TokensCounted, TokensUsed, RequestFailed) are linked by ULID requestId plus laravel/ai's invocation ID. That is enough to build per-tenant, per-agent, per-model cost reporting without Headroom becoming a data store — a meaningful build-vs-buy win, since the alternative is instrumenting every call site yourself.php artisan headroom:models surfaces context windows, max output, prices, cache prices and long-prompt tiers, flags overrides/additions, and warns when shipped prices are >60 days stale. This is the artifact you bring to a "should we move this agent from Claude to Gemini" conversation — and the staleness warning is an honest signal that pricing is a maintained dependency, not a one-time config.headroom:inspect --json exits non-zero when a request doesn't fit, so prompt/tool/schema changes that blow the context window get caught in the pipeline instead of in production. Same for regression-testing prompt bloat as a reviewable diff.Headroom::fake() with assertCounted(), assertCountedTimes(), assertNothingCounted() and matching by agent class, model or * means feature tests can assert "this request would have been blocked" without spending a cent or hitting a provider. Unmatched requests estimate locally. This lowers the cost of writing tests around AI features enough that teams will actually do it.auto counting estimates first and only escalates to a native provider count when within escalation_band of a limit; self-calibration learns a per-model correction from real usage and reports CountMethod::Calibrated with measured error as confidence. This lets you default to cheap/fast and tighten only where limits are close.currency + exchange_rates. Small, but it removes a "why is our dashboard in dollars" objection for UK/EU-facing products.Adopt when most of these hold:
laravel/ai (the middleware, counters and Headroom::for() are written against its agent API — this is the load-bearing dependency).resolveContextUsing() gives you attribution while your existing logging/metrics stack does the storing.Look elsewhere or wait when:
laravel/ai. Nothing here is portable; the value proposition collapses.safety_margin_floor as calibration improves. It is a guardrail, not an invoice. If money changes hands on the number, meter provider-side.TokenCount, Cost, config keys and event payloads as provisional until 1.0.HEADROOM_CALIBRATION_STORE, HEADROOM_CACHE_STORE). On ephemeral or per-node caches, calibration will diverge or reset; verify your cache backend before promising accuracy.auto avoids most native calls, but escalation and any native configuration add a provider round-trip to the critical path.fallback_model with estimate_ratios, so accuracy degrades quietly. Check headroom:models covers your real model list first.attachment_tokens — fine for guardrails, wrong for precise cost.Rollout note: start in report() mode with a generous maxPercentOfContext, watch HeadroomLow and TokensCounted for a week to establish real distributions, then tighten limits and flip to blocking. Verify headroom:calibration reaches "active" per model (needs min_samples) before relying on calibrated confidence.
For executives (one paragraph): This package puts a hard ceiling on what any single AI request can spend or consume before the request is sent, and gives us per-customer cost attribution without storing prompt or response content. It converts AI cost from an unpredictable monthly surprise into a configured limit we can price into plans, and it lets us enforce those limits in CI so prompt changes can't silently blow up spend. It's a small, self-contained Laravel package rather than a platform commitment — the main caveat is that it's a v0.1.0 guardrail, not a billing system, so we should pilot it in report-only mode before enforcing.
For engineering (the honest version): It's a middleware plus a counting layer for laravel/ai. auto counting estimates locally with OpenAI's tokeniser and only calls the provider's native counter when the estimate is within escalation_band of a limit, so you don't pay latency on clear passes or clear fails. Native counters exist for Anthropic, OpenAI and Gemini, pluggable via the counters map. Streaming is covered. Events are the integration point — they carry a ULID requestId, agent, model, counts and cost, and never prompt or response text, so wire them into existing logging/metrics and don't expect this thing to be your datastore. Headroom::fake() makes AI features testable without provider calls or spend. Things to scrutinise in review: pre-1.0 API surface (pin it), the cache store dependency for calibration in multi-node setups, price-registry staleness (there's a 60-day warning — honour it), and the fact that attachment and non-OpenAI estimates carry safety margins rather than precision. Ship it behind ->report() first, measure, then enforce.
How can I help you explore Laravel packages today?