Repository navigation
FinOps
-
Every model call is accounted: a
UsageRecord(caller, model, input/output/cache tokens, USD,costSource) per call, accumulated in graph state. -
Cost source — the call's model provider: a cost the provider reports
in its answer (Jev, OpenRouter's
usage.cost) is exact ("api") and always comes first; else the provider's price table (ModelCost.fromPrices) or the component's ownprice("price-table"): uncached input × input price + cache read/write × cache prices (fallback: input price) + output × output price. A call with neither fails — never silently free. -
Tool costs: a paid tool reports its own cost with
ctx.reportCost(usd); it is recorded astool:<name>(costSource: "tool") in the same loop as the model calls — so it counts in the run budget check before the next model call, in the daily ledger and in a failed agent's spend. An invalid cost (negative, NaN) becomes a tool error the model sees.
import { Tool, type ToolContext, type ToolHandler } from "agboss";
import { Text } from "agboss/dto";
export class RateQuery {
@Text({ prompt: "currency pair, e.g. USD/ILS" }) pair!: string;
}
export class Rate {
@Text() rate!: string;
}
@Tool({ name: "exchange_rate", description: "Today's rate (a paid API)", input: RateQuery, output: Rate })
export class ExchangeRate implements ToolHandler<RateQuery, Rate> {
run({ pair }: RateQuery, ctx: ToolContext): Promise<Rate> {
ctx.reportCost(0.001); // USD for this call, recorded as tool:exchange_rate
return Promise.resolve({ rate: `${pair} 3.71` });
}
}-
Report: every run returns
CostReport(total, calls, cache-read tokens, per caller) and the budget it was allowed; the CLI prints it. Per-call traces: Tracing.
The workflow's limits come from its settings() (Workflow):
| Limit | Boundary key | Meaning |
|---|---|---|
perRun.cost |
limits.perRun.cost |
USD one run may spend |
perDay.cost |
limits.perDay.cost |
USD the workflow may spend per UTC day — resets at 00:00 UTC |
perRun.steps |
limits.perRun.steps |
agent and router visits per run (default (agents + routers) × 3) |
@Router({ maxVisits }) |
routers.<name>.maxVisits |
visits of one router per run |
-
Ledger: append-only JSONL outside the repo, one file per workflow per UTC day —
$SPEND_LEDGER_DIR/<workflow>/<YYYY-MM-DD>.jsonl(default~/.langgraph-agents/spend). A new day means a new file, which is the reset. -
Before a run: the day is read once; nothing left →
LimitExceededError(limits.perDay.cost), no calls made. The run budget (min(perRun.cost, perDay.cost − spent today)) also stops an agent's loop before a model call it cannot afford. -
During a run: every model call is written to the ledger as the graph streams. Before each
working node (agent, router) the limits are checked; crossing one fails the run with
LimitExceededError(key, limit, actual, path, spend) — never a quiet stop — and the failed Tern carries the spend. A paid failure (an agent crossingagents.<name>.limits.*, a router that could not routeTo) carries its spend, which is written to the ledger before rethrowing — failures are paid for too. A paused run's spend is written at the pause. -
Soft by one call: a call in flight cannot be stopped; parallel runs of one workflow can overshoot
the daily cap by their in-flight calls.
MODEL_MAXmakes a single call bounded only by the model. -
npm run studioruns the flow graph with the workflow's limits; the day's spend is read from the ledger. - Optional extra backstop outside the app: a spending limit on the OpenRouter key itself.
Every ExecutionOutput carries spend (CostReport) — the core always reports it; each client
decides what to show:
| Field | Meaning |
|---|---|
totalUsd, calls, cacheReadTokens
|
the turn in total |
byCategory |
agents, routing, guards, review, tools, compaction, retrieval — every key present |
byModel, byCaller
|
the same money by model and by caller |
trace |
every call in order: caller, category, model, cost, how it was priced, tokens |
Categories come from the caller: tool:* → tools, compaction → compaction, rag:* → retrieval, router:guard:* → guards, router:quality:* →
review, other router:* → routing, an agent name → agents. A resumed (paused) run reports the whole
run.
The CLI and the chat print it every turn:
thread 6cc2173e-… · http://localhost:3000/project/…/sessions/6cc2173e-…
researcher · stop: jev confidence 0.96
agents $0.002332 · routing $0.000038 · guards $0.000033 · tools $0.001000
1. router:guard:prompt_injection typesafe/jev-1.13 $0.000014 api-priced
2. router:main typesafe/jev-1.13 $0.000016 api-priced
3. researcher kimi-k2.6 $0.000426 306 in / 116 out
5. tool:exchange_rate exchange_rate $0.001000 tool-reported
…
total $0.003403 (8 calls)
api-priced = exact price from the provider (Jev bills per decision); N in / M out = tokens ×
price table; tool-reported = the tool's own cost.
npm run eval / npm run replay / compare pay from <workflow>:eval — its own day, capped at the
workflow's limits.perDay.cost (no cap when unset); they never eat the production day. See Terns and evaluation.
Unit tests use fakes and cost $0; real calls only in make smoke.
AGBoss
- Home
- Delivery Pipeline
- Architecture
- Configuration
- Workflow — the graph
- Advanced Routing (DSL)
- Routers
- Guards
- CLI — agboss
- App — createApp
- Context and Authorization
- Testing
- Suites and Comparisons
- Components
- Standard DTOs
- Example: job-scout
- Agents — the loop
- Model providers
- Tools
- Knowledge bases (RAG)
- FinOps
- Tracing
- Observability
- Terns and evaluation
- Profiles and comparisons
- Pause seam
- Decisions (ADR)