All notable changes to floe-guard are documented here. The repo ships two
packages — floe-guard on PyPI and
floe-guard on npm (Vercel AI SDK)
— versioned independently; entries are tagged py / js.
The format follows Keep a Changelog and both packages adhere to Semantic Versioning.
- Budget-aware retry: reject non-integer
max_attempts, and catchException(notBaseException) soKeyboardInterrupt/SystemExit/CancelledErrorpropagate instead of being retried.
- Budget-aware retry helper (
with_budget_retry/withBudgetRetry, issue #45): retry normally when budget is healthy, ask a caller-supplied degrade callback for a cheaper retry plan whenadvisory().near_limit/nearLimitis set, and hard-block an over-budget retry withcheck()before it runs. Ships with a no-network Python example.
- LiveKit Agents adapter (
floe-guard[livekit], issue #39):LiveKitBudgetGuard.attach(session, agent)wires the reserve-before / settle-after contract onto a LiveKitAgentSession— reserve in the agent'sllm_node, settle on the session'smetrics_collectedLLMMetrics, release oncloseor a bypassed turn. Optional per-second / per-1k-char knobs meter STT/TTS spend viarecord_tool.on_budget_exceededasync callback for a graceful spoken wrap-up instead of a hard cut.
- Tool spend as a first-class primitive with the full reserve/settle
contract, sharing the token ceiling:
reserve_tool(estimated_cost)/reserveToolholds a tool call's known price in-flight and raisesBudgetExceededBEFORE the call would cross the cap (stronger than the LLM path — the price is exact, not an estimate);settle_tool(name, cost_usd, reserved=…)/settleToolreleases the hold and accrues the actual cost;record_tool/recordToolremains the post-hoc form. The caller supplies the USD — there is no tool cost-map. tool_costs/toolCosts: per-tool-name running totals (e.g.{"apollo.people_lookup": 0.42, "exa.search": 0.11}), so the token/tool split of the one shared ceiling is inspectable. Tool settles land in the spend ledger askind: "tool"events with the reservation recorded.- Example:
examples/tool_budget.py(no API key) — a prospecting loop whose Apollo/Exa spend dies at the ceiling.
record_tool/recordTool(and the newsettle_tool/settleTool) now update the next-call estimate, so a plaincheck()+record_toolloop stops BEFORE the crossing tool call — the same stop-one-early contract as tokens. Previously tool costs accrued but did not inform the prediction. LLM and tool costs are tracked as separate last-costs and the defaultcheck()/reserve()prediction is the max of the two, so a cheap tool call can't shrink the estimate ahead of an expensive LLM call (or vice versa).
- Over-release fails loud instead of open:
settle(),settle_tool(), andrelease()previously clamped the in-flight tally to zero when handed areservedhandle larger than everything currently held — silently freeing OTHER callers' reservations and weakening the ceiling under concurrency. A handle exceeding the total in-flight sum (which cannot have come from a matchingreserve()) now raisesValueError/RangeErrorwithout mutating any state; sub-epsilon float dust still settles cleanly.
- LangGraph adapter (
floe-guard[langgraph], issue #33):guarded_nodewraps graph nodes with the reserve-before / settle-after contract, so aStateGraphfan-out of parallel sub-agents holds one shared ceiling atomically;AdvisoryChannel/latest_advisoryexpose the typedBudgetAdvisoryin graph state after each metered node, so a router can downshift models onnear_limitbefore the hard-stop. Ships with a no-API-key example (examples/langgraph_budget_aware.py).
LatencyBudget— BudgetGuard's sibling for time: tracks cumulative elapsed time across an agentic tool chain against an end-user SLA.check(expected_ms)raises the newDeadlineExceededbefore a call whose projected duration would blow the SLA;remaining_msis the readable mid-chain signal for router fallback/truncation;advisory()returnsnear_deadline/used_bps/remaining_ms, symmetric to the budget advisory'snear_limit. Monotonic clock (time.monotonic/performance.now); cooperative by design — the guard supplies the deadline signal, killing a stalled in-flight call remains the framework's job.
- Request-sized pre-call estimates:
BudgetGuard.estimate_call(model, prompt_tokens, max_completion_tokens)prices the actual incoming request from the cost map, soreserve(est)/check(est)block an oversized call — including the very first one, which the last-cost prediction is blind to. The LiteLLM adapter reserves request-sized automatically (prompt vialitellm.token_counter, cap frommax_tokens); the LangChain handler sizes its pre-callcheck()from the serialized model config and a ~4 chars/token prompt heuristic. Anything unpriceable/unsized falls back to the previous last-cost behaviour. - Mid-stream enforcement:
StreamGuard/guard_stream()re-price a streaming response chunk-by-chunk and raiseBudgetExceededmid-generation when the running call would cross the ceiling — the partial spend is settled (and lands inspend_log) instead of the whole overshoot being discovered post-mortem.finish(prompt_tokens=…, completion_tokens=…)reconciles the chunk heuristic to provider-reported usage; unpriceable models fail closed before the stream starts; parallel streams count each other's in-flight accrual, so unreserved streams share the ceiling instead of each spending it in full. Demo:examples/streaming_guard.py(no API key).
- Per-call spend ledger: every priced
record()/settle()appends a typedSpendEvent(timestamp,kind: llm|tool,model_or_tool,prompt_tokens,completion_tokens,cost_usd, optionallabelandreserved) toguard.spend_log(py) /guard.spendLog(js), so the ledger sums to the running total (unless the ring-buffer cap below has evicted old events) — no more rebuilding per-call breakdowns outside the guard.export_log()/exportLog()serialises it as JSONL with an identical snake_case schema in both languages, so heterogeneous agents emit one concatenable stream. An optionalmax_log_events/maxLogEventsring-buffer cap bounds memory for long-running agents. record_tool()/recordTool(): accrue a non-LLM cost (paid tool/API call) against the same ceiling and log it as akind: "tool"event, socheck()/reserve()enforce the budget across LLM and tool spend together.record()/settle()accept an optionallabelto tag events with an agent/task name.
floe_guard.__version__now reports the real package version (it had been stuck at0.1.0since the 0.2.0 release).
Everything the repo grew between the 0.1.0 uploads and this release ships here — the earlier revision of this changelog misattributed several of these features to the py 0.1.0 entry; that entry now reflects what the released artifact actually contained.
- Concurrency-safe enforcement: atomic
reserve()/settle()/release()with a lock-guarded running total, closing the check-then-record race that let parallel callers blow the ceiling (issue #18).check()/record()are unchanged for sequential use. - Context-aware budgeting:
BudgetGuard.advisory()returning aBudgetAdvisory(near_limit,used_bps,remaining_usd, totals), with anear_limit_bpsconstructor threshold (default 8000 = 80%). - Adapters: LangChain (
budget_guard_callback_handler), OpenAI (guarded_completion/guarded_acompletion), and Anthropic (same pair, with cache-token metering) — each behind an optional extra ([langchain],[openai],[anthropic]). - Hosted budget read:
hosted_remaining_usd()(GET/v1/agents/credit-remaining, opt-in viaFLOE_API_KEY, host override viaFLOE_API_BASE_URL),HostedEnforcementError, and package-root export ofhosted_enforcement_available(). This is the package's only network call and never runs unless you set the key.
- Groq pricing: curated Groq models vendored in the cost map —
llama-3.1-8b-instant,llama-3.3-70b-versatile,meta-llama/llama-4-scout-17b-16e-instruct,qwen/qwen3-32b(new for py; these four already shipped in js 0.2.0), plus the current production lineupopenai/gpt-oss-120b,openai/gpt-oss-20b,openai/gpt-oss-safeguard-20b(new for both packages). Kept as an explicit allowlist (scripts/update-cost-map.mjs) so generic multi-provider names stay fail-closed instead of under-metering. - Smarter model-id resolution (both packages, identical logic): lookup
candidates are tried most-specific-first — the raw id, the id with a known
openai//anthropic//groq/first segment stripped (sogroq/qwen/qwen3-32band ChatGroq'sqwen/qwen3-32bhit the same entry), then the bare last segment; each also with a trailing dated-snapshot suffix removed, so an unlisted snapshot likeclaude-opus-4-8-<date>prices at its alias entry instead of failing closed. Unknown provider prefixes are deliberately not bridged. - Cost-map refresh adds
claude-sonnet-5(both packages; the py package also gainsclaude-fable-5, which already shipped in js 0.2.0 —claude-opus-4-8andclaude-sonnet-4-6shipped in the 0.1.0 maps) and warns when a curated Groq model disappears upstream instead of silently dropping it.
- CrewAI / LiteLLM-callback silent footgun: LiteLLM runs custom-logger
hooks inside
except Exception, so the callback's enforcement raise (pre-callBudgetExceeded, fail-closedUnpriceableModelError) could be swallowed and a crew kept running unmetered with no visible signal. The callback now records the violation on itstrippedattribute and logs it at ERROR level on thefloe_guardlogger, andbudget_guarded_llmreturns acrewai.LLMsubclass that re-raisestrippedand runscheck()in the call path — outside LiteLLM — so the crew hard-stops at the next call.guard_crewnow returns the registered callback (previouslyNone) and reuses an existing registration for the same guard.
- Price lookup order is now most-specific-first (raw id before stripped
forms) for both
price_overridesand the cost map. Previously the bare name was tried before the raw id.
- Curated Groq cost-map entries:
llama-3.1-8b-instant,llama-3.3-70b-versatile,meta-llama/llama-4-scout-17b-16e-instruct,qwen/qwen3-32b, plusclaude-fable-5. - Vercel AI SDK v5 support. The middleware now works with both
ai@4(LanguageModelV1Middleware,promptTokens/completionTokens) andai@5(LanguageModelV2Middleware,inputTokens/outputTokens) from a single build — it no longer imports types fromai, and reads whichever usage field pair the installed SDK reports. Peer dependency widened to>=4.0.0 <6.0.0. - Exported the
BudgetGuardMiddlewaretype.
- A response or stream
finishpart with no usable token counts is now rejected with a clear error (fail-closed) instead of surfacing an internal pricing error; the in-flight reservation is released either way.
Initial public releases (PyPI 2026-06-15, npm 2026-06-16).
BudgetGuardwithcheck()/record()(sequential; the atomic reservation API landed in py 0.2.0).- Offline pricing from a vendored LiteLLM cost map covering OpenAI and
Anthropic; unpriceable models fail closed (
UnpriceableModelError) with manualprice_overridesas the escape hatch. - Python adapters: CrewAI and LiteLLM, behind optional extras; the core stays dependency-free. (The LangChain, OpenAI, and Anthropic adapters landed in py 0.2.0.)
- Hosted-Floe hook as a stub (
hosted_enforcement_available()underfloe_guard.hosted); this release performs no network calls of any kind. - TypeScript package (
js/) with Vercel AI SDK middleware (budgetGuardMiddleware), verified againstai@4. - No runtime telemetry of any kind.