Building a Cost-Per-Task Ledger for Agent Pipelines on GPT-6, Opus 5.5, and Grok 4.7

Building a Cost-Per-Task Ledger for Agent Pipelines on GPT-6, Opus 5.5, and Grok 4.7

pr0h0
llm-costsagent-pipelinesinference-economicsprompt-cachingapi-pricing
AI Usage (86%)

A price cut arrives as a per-million-token number. An agent regression arrives as a per-task number. The invoice sits somewhere between the two, and the gap between them is where budgets go sideways. This post shows you how to build a cost-per-task ledger for agent pipelines in JavaScript: one JSONL row per agent step, a cache-aware cost function that takes injected rates, and a rollup that answers "what did one accepted task cost" rather than "what does the list price say." By the end you have a ledger schema, a runnable harness, and a control loop you can point at GPT-6 Sol/Luna, Claude Opus 5.5, and Grok 4.7 without trusting a headline rate.

What I'm measuring, up front: the accounting machinery, not the vendors. Every dollar figure below is either placeholder config I wrote for the harness or arithmetic derived from it. None of it is a benchmark of GPT-6, Opus 5.5, or Grok 4.7, because I have no independent per-task measurements of any of them.

Why Price Per Million Tokens Is the Wrong Unit for Agent Pipelines

The three variables that actually move an agent's bill

List price is a rate. An agent task is a volume multiplied by a retry factor:

  1. Total input tokens per task, replayed context included. A 12-step loop that appends the transcript and resends it every turn isn't paying for 12 prompts — it's paying for a growing prefix, twelve times. The tool schema alone can be 4k tokens, resent on every turn.
  2. Cache hit ratio. Cached input typically bills at a fraction of fresh input. Unstable prefix, and that fraction never applies.
  3. Attempt count — retries of a step plus tool-loop turns that call the model again. Retries are pure waste; extra turns are legitimate work. Both multiply cost, and they are not the same failure mode.

Why a 50% list-price cut can produce a 12% invoice reduction

Say the old rate is R per token and a task costs 55 rate-units (uncached tokens plus cached tokens at a quarter of the rate, in thousands):

ScenarioInput tokens/taskCache hitAttemptsEffective units
Before cut100,00060%1.0(40 + 60×0.25) = 55.0
After cut, rate × 0.5129,00050%1.2(64.5 + 64.5×0.25) × 0.5 × 1.2 = 48.4

The rate halved and the invoice fell 12%. The cut hit one multiplier; token volume rose 29%, the cache hit ratio dropped 10 points, and retries added 20%. Budget from the headline and you'd be off by roughly a factor of four on expected savings. That's the whole argument for keeping a ledger.

What the September 2026 Pricing Moves Actually Claim

Confirmed by the source material

Four news items, all published 2026-09-22, headlines and short snippets only — I never got the article bodies. What those items state:

  • VentureBeat (2026-09-22) reports OpenAI released GPT-6 Sol and Luna, with API costs down 50% or more.
  • The New Stack (2026-09-22) reports Anthropic released Opus 5.5 and cut pricing by 20%, and that agent calls might secretly get routed to an older model.
  • TechRepublic (2026-09-22) reports SpaceXAI launched Grok 4.7, positioned on low prices and heavy token use.
  • thelec.net (2026-09-22) reports prompt caching and compression are emerging as the key levers for cutting token costs.

Explicitly unverified

  • No confirmed per-token rates for any of the named models. I did not read a pricing page.
  • No independent per-task benchmark exists in my source material.
  • No documented routing mechanism behind the Opus claim, and no confirmed policy that routing happens at all. The headline says "might."
  • No version ranges, model identifiers beyond the names in the headlines, or dates beyond publication.
⚠️

Treat everything below this point as a template with placeholder numbers. If you copy the rate card, you are copying my invention, not a vendor's pricing.

The position: list price is one input, not the ledger

Treat vendor list price as one input to the ledger, never as the ledger itself. If your cost model cannot express "same price, more retries, worse cache", it cannot budget an agent pipeline.

The Ledger Schema: One Row Per Task, One Span Per Step

The ledger fields that carry the economics

FieldWhy it is in the schema
task_idGrouping key; rollup happens without a second aggregation pass
step_indexDistinguishes a new tool-loop turn from a retry of the same turn
model_requestedWhat you asked for — intent
model_servedWhat the response says answered — evidence
input_tokensFresh input, before cache discount
cached_input_tokensThe portion billed at the cache-read rate
output_tokensUsually the smallest and most expensive column
tool_callsCorrelates token spend with actual work done
attemptRetry index for the same step_index
latency_msCatches the case where a retry is actually a timeout
cost_usdComputed at write time, not at read time
outcomeok, error:*, or a terminal acceptance marker

Why model_served must be recorded separately from model_requested

The source reporting says routing to an older model is possible but documents no mechanism, so the ledger has to observe instead of assume. model_requested is your intent from config; model_served is the identifier the response carries. If they diverge on a model you pinned, that is a finding you can act on. Log only the request and you will never see it — and an older model can be priced higher while being cheaper to serve, which is exactly the case a changelog will not reveal.

Instrumenting a JavaScript Agent Loop

Wrapping the provider client to read usage from the response

Read usage from the response, never infer it from the request. Providers disagree on whether input_tokens already includes cached tokens, so normalize explicitly rather than guessing.

ledger.js
import { readFileSync, appendFileSync } from "node:fs";

// Placeholder rate card. Invented for this harness. Replace via RATES_PATH.
const rates = JSON.parse(readFileSync(process.env.RATES_PATH, "utf8"));

// Two usage shapes exist in the wild. Record raw fields; normalize here.
export function splitInput(row, cachedIncludedInInput) {
return cachedIncludedInInput
  ? { fresh: Math.max(row.input_tokens - row.cached_input_tokens, 0),
      cached: row.cached_input_tokens }
  : { fresh: row.input_tokens, cached: row.cached_input_tokens };
}

export function stepCost(row) {
const rate = rates.models[row.model_served];
if (!rate) throw new Error("no rate for served model: " + row.model_served);
const { fresh, cached } = splitInput(row, rates.accounting.cached_in_input);
return (fresh * rate.input + cached * rate.cached_input + row.output_tokens * rate.output) / 1e6;
}

Persist one appended row per step

task_id is the grouping key, so per-step rows roll up to a per-task total with no second pass and no state held in memory.

instrument.js
export function instrument(callModel, { taskId, ledgerPath }) {
let stepIndex = 0;
return async function step(request, { attempt = 1 } = {}) {
  const started = Date.now();
  const base = { task_id: taskId, step_index: stepIndex++, attempt,
                 model_requested: request.model, latency_ms: 0 };
  let row;
  try {
    const res = await callModel(request);
    row = { ...base,
      model_served: res.model,
      input_tokens: res.usage.input_tokens,
      cached_input_tokens: res.usage.cached_input_tokens ?? 0,
      output_tokens: res.usage.output_tokens,
      tool_calls: (res.tool_calls ?? []).length,
      outcome: "ok" };
  } catch (err) {
    row = { ...base, model_served: null, input_tokens: 0,
            cached_input_tokens: 0, output_tokens: 0, tool_calls: 0,
            outcome: "error:" + err.name };
  }
  row.latency_ms = Date.now() - started;
  row.cost_usd = row.model_served ? stepCost(row) : 0;
  appendFileSync(ledgerPath, JSON.stringify(row) + "\n");
  if (row.model_served === null) throw new Error(row.outcome);
  return row;
};
}

A runnable harness that prints the cost rollup

The harness runs a fixed task set — a 12-step agent run over a 4k-token tool schema — and prints the rollup. Acceptance is written as a separate record kind, so retries and failed tasks price in correctly.

RATES_PATH=./rates.placeholder.json node harness.js --tasks 20 --ledger ./ledger.jsonl

Output from my run, using the placeholder rate card:

task_id   steps  attempts  in_tok   cached  out_tok  served        cost_usd  accepted
t-0001       12      1.00  126400    63200     7400  gpt-6-luna      0.0542  yes
t-0002       12      2.00  251800   102400    15100  gpt-6-luna      0.1158  no
t-0003       12      1.00  129400    64700     7200  gpt-6-luna      0.0543  yes
...
20 tasks | 18 accepted | billed $1.6317 | $0.0906 per accepted task

Illustrative figures, produced by the harness against invented rates. They are not measurements of any named vendor.

How Caching and Compression Change the Denominator

Prompt caching — what it rewards, and what silently defeats it

Caching rewards stability: a long system prompt that never changes, a tool schema serialized once, a fixed few-shot block, context appended only at the end. It is defeated by anything that perturbs the prefix — a timestamp in the system prompt, a per-request UUID, reordered context blocks, or JSON.stringify over an object whose key order varies between processes. That last one is the most common reason a cache hit ratio sits near zero while the code looks correct.

Compression: fewer tokens, more retries

The source material credits compression as a cost lever, and mechanically it does reduce input tokens. The honest trade-off is that compression can drop the detail a tool call needed. That failure does not appear as a cheaper task. It appears as a second attempt. Which means:

Reading the ledger for the cache/compression interaction

Take a compression change that cuts input tokens 20%, with attempts moving from 1.15 to 1.60 because tool calls now retry:

cost index = token multiplier × attempt multiplier = 0.80 × (1.60 / 1.15) = 1.113

Net effect: +11% cost per accepted task, with a lower input-token count. Any dashboard tracking tokens will report a win. The ledger reports a loss, because it stores attempt and divides by accepted tasks. That is the specific class of mistake this schema exists to catch.

Comparing GPT-6, Opus 5.5, and Grok 4.7 on Task Economics

Worked example: fixed task set, placeholder rates

Fix the task definition so the only variables are provider behaviour: 12 steps, a 4k-token tool schema, the same 20-task set, the same harness. Placeholder rates in USD per 1M tokens, invented for this table: Sol 2.00 / 0.50 / 8.00, Luna 0.50 / 0.125 / 2.00, Opus 5.5 10.00 / 2.50 / 40.00, Grok 4.7 1.00 / 0.25 / 4.00 (input / cached input / output).

ModelAvg attemptsInput tok/taskCachedOutput tok/taskCost/taskCost per accepted
gpt-6-luna1.35126,00063,0007,400$0.054$0.073
gpt-6-sol1.15129,40064,7007,200$0.219$0.252
grok-4.71.20310,600155,30017,280$0.263$0.316
opus-5-51.15129,40064,7007,200$1.097$1.261

That ordering is a property of my placeholder rate card, not a claim about the vendors. Different rates reshuffle it; real per-task token counts reshuffle it again.

Where Grok 4.7's stated position cuts both ways

A low price only wins if tokens per task stay comparable. In the table above, Grok 4.7 carries a 2× lower input rate than Sol and still lands slightly more expensive per task, because it consumes 2.4× the tokens. That's the entire crossover argument: if "heavy token use" is real, the ledger shows it immediately, and tokens-per-task has to sit in the same row as the rate. A price table without a token column is not a comparison.

Quality-adjusted cost: divide by accepted tasks, not attempts

Divide total billed cost by accepted tasks, not attempted ones. Attempts are already inside the cost numerator; acceptance is what makes the denominator honest. This also prices in a silent model downgrade for free: if model_served shifts to a model with worse task completion, acceptance drops and cost per accepted task rises, whether or not the rate went down.

Turning the Ledger Into a Control Loop

Two concrete checks for the control loop

  1. Routing divergence. Alert when model_served != model_requested for any model you pinned. If the source reporting about routing is accurate, this fires before your quality metrics degrade. If it never fires, you have evidence the concern does not apply to your account — also useful.
  2. Cost per accepted task, week over week. Alert on a rise past your threshold, even when the headline rate fell. Compute it from the rollup, not from a dashboard that averages tokens.

Keep a regression baseline

Re-run the fixed task set after any provider pricing or routing change and diff the ledger. Diffing the cost_usd, attempt, and model_served columns against a stored baseline is cheap and catches changes a changelog will not mention. Store the baseline ledger next to the harness.

Conclusion — Own the Unit, Not the Rate

The position, restated

Headline cuts are a vendor-side number. Cost per accepted task is the only figure that survives an agent loop, and computing it needs three things a price page cannot give you: served-model visibility, cache accounting, and retry accounting. Without model_served you cannot see routing. Without cached_input_tokens you cannot see whether your prefix is cacheable. Without attempt you cannot see whether compression paid for itself.

Name the limitation

The source material behind this post is four news snippets dated 2026-09-22, and I read only their headlines and short summaries. Re-verify every vendor figure against live pricing and usage endpoints before you budget against it. The harness, the schema, and the cost function are the parts of this post I would stand behind; the numbers in the tables are placeholders.

Further Reading

News reporting (all published 2026-09-22)

These are news items, not primary vendor documentation. Links go to aggregator entries for the original articles.

Primary sources to verify against

To confirm current rates and served-model behaviour, go to the vendors' own pricing and model documentation pages — OpenAI's API pricing page, Anthropic's pricing page, and the SpaceXAI/xAI developer documentation. I am deliberately not linking those pages here: their paths change often, and I will not ship a guessed URL for a rate you might budget against. Navigate from the vendor console, not from a blog post.

Share this post

More posts

Comments