Specification · v0.2 (draft) · CC BY 4.0

Turn Accounting

Normative — defines what a turn is, not what tack does. View source on GitHub

Version: 0.2 (draft) Date: 3 August 2026 Editor: Yacine — Code75 Licence: specification text under CC BY 4.0; conformance suite under Apache-2.0 Status: draft for comment. Nothing here is stable until 1.0.

Editor's disclosure: the editor is the author of tack, one of the implementations listed in §13. This document is licensed and governed so that it can be adopted against tack. If any rule in this specification can only be satisfied by tack, that rule is a defect — report it.

On this draft. This specification was drafted and then revised against an audit of a production system before publication, rather than published on reasoning alone. Three rules earned their place by catching real defects in that audit: unknown is not zero found ambiguous zero-cost rows; the explicit-currency rule found an implicit currency on the cost side against a different one on the purchase side; the refusal-recording rule found that refused turns were not being written at all. Six gaps in the specification itself were found and closed, and one rule was found over-specified and relaxed. A rule that has never met an implementation is a hypothesis.

v0.2 was revised against a second audit of a different kind: a from-scratch implementation of this unit, reviewed at design time. That review caught a sequencing defect before it was built — a paid routing operation placed ahead of the admission gates, so that a refused turn would have already spent provider money — and the pre-admission rules in §4.3 and §6.7 exist because of it. Design-time review is where these rules are cheapest to apply; the defect they catch is the same either way.

0. Scope

In scope. How to define, bound, record and count a turn as a unit of cost, so that a cost-per-turn figure means the same thing across two independent systems.

Out of scope. Tenant isolation, access control, data residency, model quality, latency objectives, and pricing guidance. This specification says how to count. It does not say what to charge.

A narrow specification gets adopted. A broad one gets read.


1. Motivation

Providers price inference in tokens. Nobody operates a product in tokens.

A single user-facing exchange in an agentic system is not one model call. It is an admission check, one or more retrieval legs, a loop of model calls interleaved with tool executions, one or more guardrail evaluations, occasional retries, and an emission. The tokens are a component of that cost. They are not the cost — and on many workloads they are not even the largest component, once the tools the agent actually calls are counted.

The consequence is that the industry has no comparable unit. Two vendors can both publish a cost per interaction and mean entirely different things — one counting a single model call, the other counting the full loop; one excluding refusals, the other including them; one counting only inference, the other counting the paid APIs the agent invoked; one measuring a component, the other estimating it; one reporting a mean, the other a median. Neither is lying. The figures are simply not comparable, which makes them useless for procurement, for margin modelling, and for public benchmarking.

Cost-per-completed-task, cost-per-resolution and cost-per-trace all circulate as concepts. What is missing is not the idea. It is the definition: what counts as one, what does not, how the money is represented, how confident the numbers are, and what must be published alongside them for them to mean anything.

This document supplies that definition.


2. Terminology

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as in RFC 2119.

turn — the unit defined in §3.

submission — the point at which a caller hands work to the system. May be followed by queueing.

admission — the point at which the system accepts a submitted unit of work for execution, after gate checks. Admission, not submission, opens a turn.

emission — the point at which the system delivers its response to the caller, or terminates without one.

component — a cost-bearing operation inside a turn: an inference call, a guard evaluation, a retrieval leg, a tool execution, an infrastructure operation.

gate — a pre-execution check that may prevent a turn from being admitted: balance, policy, class availability, envelope.

payer — the account to which a turn's cost is attributable.

On the choice of word. Several terms are in circulation for approximately this unit: run, invocation, task, interaction, trace, request, exchange, episode. This specification uses turn and treats the others as synonyms where they denote the same boundary. An implementation MAY use any label in its own interface and still claim conformance, provided the boundary it uses is the one defined in §3. Conformance is about the boundary, not the vocabulary.


3. The turn

A turn is one bounded unit of work, from admission to emission, including every component required to produce that emission.

3.1 Boundary

3.2 The counting rule

One emission, one turn.

Everything the system had to do to reach that emission belongs to it. This is the entire specification in one line; §4 is the application of it to cases where the answer is not obvious.

3.3 The decomposition rule

This rule exists because a developer asks where did the cost go and a buyer asks how much and is it stable. They are entitled to different views. They are not entitled to different numbers.

3.4 Attribution and orphans

Every cost-bearing operation performed in service of a turn MUST carry that turn's identifier.

This rule exists because orphaned components bias the unit twice in the same direction: the parent turn's cost is understated, and the turn count is inflated by records that were never turns. Cost per turn is then wrong in both the numerator and the denominator, and no other test in this specification detects it. Guard evaluations, embedding calls and retries that mint their own identifiers instead of inheriting the parent's are the common cause.


4. Edge cases (normative)

The rulings below follow from §3.2. They are enumerated because implementations diverge here, and divergence here is what makes figures incomparable.

4.1 Loop and retry

CaseRulingRationale
Loop of N model calls with tool execution between them, one responseOne turn, iterations = NOne emission
Several tool calls issued in parallel within one model round-tripOne iteration, tool_calls counts each calliterations counts model round-trips; tool_calls counts calls. The two are not interchangeable
Model retries a tool after a tool errorSame turnNo new emission
Provider-level retry after a failed attempt (timeout, 5xx, malformed response)Same turn; provider_attempts incremented; tokens burned on the failed attempt MUST be counted where the provider reports or charges for themNo new emission, but money was spent
Best-of-N, speculative or parallel sampling where one completion is keptOne turn; all N completions counted in costThe discarded completions were paid for

4.2 Supporting components

CaseRulingRationale
Guardrail / judge evaluation before emissionSame turn, own cost component, MUST carry the parent turn identifier (§3.4)Required to reach the emission
Retrieval and embedding callsSame turn, own cost component, MUST carry the parent turn identifier (§3.4)Required to reach the emission
Tool execution with an external monetary cost (paid API, SMS, payment fee)Same turn, tool_execution component (§5.2)End-to-end means end-to-end
Tool result served from a memoised or cached storeCost attributed to the turn that caused the original external call; the reusing turn records the call in tool_calls at zero costSymmetric with the cache rule below
Context compaction or summarisation triggered during the turnSame turn, own cost componentCaused by this turn
Context compaction run as scheduled maintenanceSeparate turn, class maintenanceIts own admission, no caller emission
Cache writeAttributed to the turn that caused the writeOtherwise later turns are silently subsidised by an earlier one
Cache readAttributed to the turn that read it, at the read rateDirect attribution

4.3 Outcomes other than success

CaseRulingRationale
Refused at admission (insufficient funds, unavailable class, policy, envelope)One turn, outcome = refused, MUST be recorded, MUST NOT be billed. cost_total = 0 where admission is zero-cost — the normal case; see the pre-admission rowRefusal is an outcome, not an absence
Cost-bearing operation performed between submission and admission (routing, selection embedding, paid pre-admission evaluation)Belongs to the turn. If the turn is then refused, those components MUST be recorded with their cost, and cost_total MUST equal their sumMoney spent in service of a turn is that turn's cost; hiding it under a zero corrupts the refusal distribution the same way an unrecorded refusal corrupts the success one
Guard or policy verdict stops the turn after admission, before or during emissionOne turn, outcome = refused; cost incurred MUST be counted; billability MUST be declaredThe system declined; the spend is real
Gate evaluated in shadow mode: refusal logged but execution proceedsNot a refusal. The turn's real outcome is recorded, and gate_mode = shadow is declared (§6.7)A gate that does not stop spending has not refused anything
Submitted work expiring in queue before admissionOne turn, outcome = refused, reason refused_expired, cost_total = 0The caller is entitled to know it did not run
Failure after admission, before emissionOne turn, outcome = failed; cost incurred MUST be counted; billability MUST be declaredMoney was spent
Streaming response interrupted by the callerOne turn, outcome = interrupted; cost incurred up to interruption countedEmission began
Turn re-executed after a system-side defect (replay, incident recovery)New turn, billable = false, reason replay_system_defectThe payer did not ask twice

4.4 Composition and boundaries

CaseRulingRationale
Human-in-the-loop pause, then resumptionTwo turnsThe pause is an emission
Sub-agent or delegated agent invoked within a turnSame turn by default; components attributed to the parent. If recorded separately, child records MUST carry parent_turn_id and MUST NOT be double-counted in totalsOne emission
Sub-agent invocation crossing a payer boundarySeparate turn per payerCost must be attributable to exactly one payer
One call processing a batch of N itemsOne turnThe unit is the invocation boundary, not the payload
N items dispatched as N callsN turnsN emissions
Same logical conversation continued across channelsSeparate turnsEmission boundary, not conversation boundary
Turn spanning a provider or internal price changeRates fixed at admission and MUST NOT be re-derived later (§6.1, §6.3)A committed record does not change retroactively

An implementation encountering a case not listed here MUST resolve it by §3.2 and SHOULD report it for inclusion in a later version.


5. The canonical turn record

5.1 Required fields

FieldTypeNotes
record_typeenumturn or correction (§6.4)
turn_idopaque stringUnique
parent_turn_idopaque string, nullablePer §3.4 and §4.4
corrects_turn_idopaque string, nullableRequired when record_type = correction
idempotency_keyopaque string, nullable§6.5
payer_idopaque stringMUST be opaque — this specification requires no identity data
classstringImplementation-defined; MUST be published (§10)
class_basisenummodel_derived, shape_derived, or customer_selected. §5.7
gate_modeenumenforcing, shadow, or absent. §6.7
submitted_at / started_at / ended_attimestampSubmission, admission, emission
outcomeenum§5.3
billableboolean
billable_reasonenumRequired when billable = false. §5.4
cost_totaldecimal§6.2
cost_currencyISO 4217§6.1
cost_componentsmapEach entry carries a value and a provenance. MUST sum to cost_total (§3.3). §5.2
cost_completenessenumcomplete or partial. Derived from component provenance. §5.2
fxobject, nullableRequired when any component was incurred in another currency. §6.1
countersmap§5.5
models_usedarrayVendor, model identifier, and route, per inference component
price_chargeddecimal, nullableNull at Level 1
price_currencyISO 4217, nullable
record_versionstringThis specification version

Recommended fields. conversation_id and turn_index (the turn's ordinal position within its conversation) SHOULD be recorded. Without them, cost growth as a function of conversation age — the single largest source of cost drift in long-lived threads — cannot be measured.

5.2 Cost components and provenance

The following component keys are defined. An implementation MUST populate every component it has knowledge of.

inference_input_uncached · inference_input_cached_read · inference_cache_write · inference_output · inference_reasoning · guard · retrieval · tool_execution · infrastructure

tool_execution is required, and it is the component most often omitted. An agent that sends messages, calls a paid enrichment API, or triggers a payment incurs real cost outside inference. On many production workloads it exceeds the model cost. An end-to-end unit that excludes it is not end-to-end.

Each component MUST carry a provenance:

ProvenanceMeaning
knownDerived from a figure the provider reported or charged
estimatedDerived by the implementation's own approximation. estimation_method is REQUIRED
unknownThe implementation cannot determine it

Provenance exists because implementations legitimately estimate components a provider does not break out — for example a reasoning-token share inferred from returned text where the provider bills it inside output tokens. Estimation is acceptable. Estimation indistinguishable from measurement is not.

infrastructure (queue, storage, egress, compute overhead) is OPTIONAL — see §15.

5.3 Outcomes

emitted · refused · failed · interrupted

Every turn MUST carry exactly one. The three non-emitted outcomes are distinguished by who stopped the turn: refused — the system declined, before or after admission; failed — the system attempted and broke; interrupted — the caller stopped it. A stop that fits none of these is a gap in this specification and SHOULD be reported.

Refusals and failures MUST be recorded with the same fidelity as successes. A system that only records successful turns cannot support a cost-per-turn figure, because its denominator is unknown — see §6.8.

5.4 Billable reasons (closed enum)

refused_insufficient_funds · refused_policy · refused_class_unavailable · refused_envelope_exceeded · refused_expired · failed_system · failed_provider · interrupted_by_caller · replay_system_defect · promotional · correction · other

other MUST be accompanied by a free-text billable_note, and SHOULD be reported for inclusion in a later version. Free text alone is not conformant: a reason that cannot be aggregated across implementations defeats the purpose of the field.

5.5 Required counters

iterations · tool_calls · provider_attempts · input_tokens · cached_input_tokens · cache_write_tokens · output_tokens · reasoning_tokens · retrieval_legs

5.6 Model attribution

models_used MUST reflect what actually served the turn, including any substitution or failover. A system that presents a class abstraction to its customers MAY hide the model in its pricing, but MUST NOT hide it in the record. A silent model substitution defeats the purpose of an auditable unit.

5.7 Class basis

class_basis declares how a turn's class was determined:

This specification does not prefer one basis over another. It requires the basis to be declared, because a model_derived class makes cross-vendor cost comparison circular: the class is defined by the thing being compared. A reader cannot interpret a per-class figure without knowing which basis produced it.


6. Accounting primitives

A unit of account requires more than a definition of the unit. This section defines how the money is represented, corrected, retained, gated and covered.

6.1 Currency and conversion

6.2 Precision and rounding

6.3 Immutability

6.4 Corrections

This is the same discipline as a reversing journal entry, and it is what makes a ledger auditable rather than merely current.

6.5 Idempotency

Without this, a retried client submission produces two turns and charges twice. Idempotency is a billing property, not only a transport convenience.

6.6 Retention

6.7 Gate mode

Where the implementation operates a gate (§2), it MUST declare the gate's mode:

ModeMeaning
enforcingA gate decision to refuse prevents execution and prevents spending
shadowA gate decision to refuse is recorded, but execution proceeds and money is spent
absentNo gate

Shadow mode is a legitimate rollout state. Declaring it is what makes it legitimate.

6.8 Record coverage

No test in this specification can detect a record that was never written. Absence is invisible from the inside.

Systems that only write a record on the success path exhibit high apparent data quality and a silently truncated distribution: every percentile is computed over survivors. This is the most common and least visible failure of turn accounting, and coverage is the only measurement that reveals it.


7. Conformance levels

Three levels. Each is a superset of the one before. Levels are self-declared and MUST be substantiated by §8.

Level 1 — Measured

Typical: observability platforms, gateways with per-customer cost reporting, internal instrumentation.

Level 2 — Counted

Level 1, plus:

Typical: products billing per interaction, resolution, or task.

Level 3 — Committed

Level 2, plus:

Level 3 is the difference between a system that reports what it spent and a system that cannot spend what it has not collected.


8. Conformance suite

An implementation claiming a level MUST pass all tests at that level and below.

IDLevelAssertion
T11For every record, cost_components sums to cost_total without residual at stored precision
T21No monetary field is stored in binary floating point; all carry an explicit currency
T31Every populated component carries a provenance; every estimated component carries an estimation_method
T41No component with provenance unknown is stored as a zero value; cost_completeness is partial whenever any component is not known
T51Every record carries exactly one outcome and a billable flag; billable_reason is drawn from the closed enum
T61A turn executing N > 1 iterations produces exactly one record with iterations = N, and iterations is not a function of tool_calls
T71A turn issuing M parallel tool calls in one round-trip records iterations = 1, tool_calls = M
T81A turn that succeeded after R failed provider attempts records provider_attempts = R + 1
T91Every cost-bearing record carries a parent turn identifier or its own emission; the orphan rate is computable
T101A sub-agent record, if separate, carries parent_turn_id and is not double-counted in any total
T111models_used reflects the model actually served, including under failover
T121Where currencies differ, fx.rate_at equals the admission timestamp and the rate is unchanged by later re-reads
T131A correction appends a record referencing the original; the original is byte-identical before and after
T141class_basis is populated on every record
T152A refused turn is recorded with billable = false; its cost_total equals the sum of its recorded components — zero where admission is zero-cost — and no cost-bearing component postdates the refusal decision
T162Record coverage equals 100%: the independent terminal-outcome counter matches the count of turn records for the same window
T172For any period, the amount charged to a payer equals the sum of that payer's billable turn records plus corrections referencing them
T182A turn exceeding its published class envelope produces the published behaviour, and the record states which
T192A replayed submission carrying a previously seen idempotency_key produces no second turn and returns the original record
T203gate_mode = enforcing: a refused turn results in no downstream provider spend
T213For every reservation, exactly one of {commitment, release} exists
T223No reservation exists older than its declared time-to-live
T233A turn failing after reservation and before emission results in a release, not an orphaned reservation
T243The usage record and the balance movement share a transaction identifier

T16 is the test most implementations will fail without noticing, because it is the only one that measures what is absent. T21–T23 are the tests most implementations will fail first, and the ones a payer cares about most: they are the difference between an accounting error and a lost balance.


9. Reference: what "one turn" is not

Stated explicitly, because these are the substitutions that make published figures incomparable:

  1. Not one model call. Loops are one turn.
  2. Not one conversation. Conversations contain many turns.
  3. Not one successful outcome. Refusals, failures and interruptions are turns.
  4. Not one token bundle. Payload size does not determine turn count.
  5. Not inference alone. Tools the agent called are part of the turn.
  6. Not a supporting call. A guard, an embedding or a retry is a component, not a turn (§3.4).

10. Publishing a cost-per-turn figure

Any party publishing a cost-per-turn figure and citing this specification MUST publish, alongside it:

10.1 Statistical adequacy

A percentile MUST NOT be published unless at least ten observations exceed it, and the sample size MUST accompany every percentile.

In practice this means n ≥ 200 for a p95 and n ≥ 1000 for a p99. Below those, the figure is an artefact of the two or three most expensive turns in the sample and will move by an order of magnitude with the next one.

A mean cost per turn over a right-skewed distribution is not a false statement, but it is not a useful one, and it systematically understates the cost of the most engaged payers. Where sample size does not support a percentile, report the sample size and the maximum observed rather than a percentile that cannot be supported.


11. Non-goals

This specification deliberately does not address: tenant isolation, permission and clearance models, data residency, model quality or evaluation, latency objectives, or what a turn should cost. Several of those are necessary to operate a product responsibly. None of them are necessary to make a cost figure comparable, and including them would make this document a product description rather than a unit definition.


12. Governance


13. Implementations

Entries are self-declared. No entry has been verified against the conformance suite at v0.1, because the suite is published with this draft and has not yet been run.

ImplementationDeclared levelNotes
tackLevel 3 (target)Editor's implementation. Level 3 claim is a target, not a verified result: T21–T23 are not yet satisfied at the time of writing

Additions welcome, including from implementations that only reach Level 1. Level 1 is a real and honest claim, and a specification with one implementation is a document, not a standard.


14. Changelog

v0.2 (draft) — 3 August 2026 — revised against a design-time audit of a second, from-scratch implementation (see On this draft). Added: the pre-admission cost rule and the post-admission guard-stop ruling (§4.3); the who-stopped-it outcome taxonomy (§5.3); the transport-independent retrieval-leg definition (§5.5); the async-provider-report-is-a-correction rule (§6.4); the zero-cost-gates expectation (§6.7). Amended: T15 to match the refined refusal-cost semantics. Resolved former open question 8 (async revision → always a §6.4 correction); added open questions 10–12.

v0.1 — 30 July 2026 — initial version. Drafted, then revised against a production implementation audit before publication (see On this draft). Prior working drafts are not published.


15. Open questions for v0.2

  1. Whether maintenance turns should be a distinct outcome rather than a class.
  2. Whether infrastructure should be promoted from OPTIONAL to REQUIRED. If it is, the allocation method (per turn, per span, per GB-month) and the reference volume MUST be declared, since infrastructure is largely a fixed cost and its per-turn share is a function of throughput rather than of the turn.
  3. How to represent a turn served by more than one vendor mid-loop.
  4. Whether the inference_reasoning component and the reasoning_tokens counter are redundant now that provenance exists.
  5. A canonical serialisation, and whether to align field names with an existing tracing standard.
  6. Whether Level 3 should require a published remedy for reservations stranded by prolonged provider unavailability.
  7. Whether the idempotency window should have a specified minimum.
  8. Whether record coverage should be required — rather than merely published — at Level 1.
  9. Whether a minimum sample size should gate conformance claims themselves, not only published figures.
  10. Whether Level 3 should require zero-cost admission outright. A paid gate is arguably incompatible with "cannot spend what it has not collected", but ruling it out may exclude legitimate paid pre-admission policy evaluation.
  11. Whether the billable_reason enum should distinguish pre-admission refusals from post-admission guard stops, now that both map to outcome = refused.
  12. Whether a race between a passing zero-cost pre-check and a failing authoritative reserve — where a pre-admission component was paid for in the window between them — needs its own declared accounting treatment, or is adequately covered by the §4.3 pre-admission row.