~/blogmissing-meter-zero-charge.md
cchu@nycu:~/blog$ cat missing-meter-zero-charge.md
2026.10.296 min[agents][tool-routing][billing][testing]

Missing Meters in Deferred Tool Settlement

A Treg settlement replay separates explicit zero, absent usage, partial meter responses, and charges that exceed the initial reserve.

A synthetic tool response containing {"usage":{"input":0}} settled at zero. Removing that meter settled at 100,000 micro-USD, the initial reserve. Providing only input: 40 settled at 80 micro-USD, even though the configured output and image meters were absent.

Those three answers come from the same Treg settlement function. The important boundary is whether a usable meter was observed. Missing individual fields can count as zero; missing every configured meter cannot. This keeps an empty usage envelope from silently becoming a free successful call, but it also leaves a dependency on the provider's response schema.

I examined revision 5b8b8cdca5ca90709093b8ee197639da907deb98, whose package configuration declares version 0.27.0 and Python >=3.12,<3.14. The replay ran unchanged pure modules with Python 3.12.14 on Linux. All requests, response envelopes, and prices were synthetic. I did not run the service, its database, the recovery worker, or any provider call.

A persisted settlement basis and terminal usage feed the pure evaluator. Usable usage is priced; unobserved usage falls back to the reserve. The caller-poll path can instead keep waiting. Database and worker paths were inspected, not executed.

The Reserve Is an Estimate

Treg exposes tools to agents through a credential-injecting registry. Its implementation includes a provider catalog, call orchestration, authorization, asynchronous task tracking, and a prepaid ledger. This is more than an API-name wrapper: an accepted asynchronous task can outlive the request that opened its spending hold.

The relevant separation is visible in three files:

FileResponsibilityVerification here
domain/money/settlement.pyDerive a pricing basis and evaluate evidenceExecuted
domain/asynctasks/__init__.pyPure task semantics, including JSON path lookupImported unchanged
application/asynctasks.pyPersist tasks, poll, close holds, and record outcomesCode inspection

The derive_basis() function records the pricing mode, reserve, fallback, and usage conversion rate. My fixture selected a $0.10 table row while retaining a $2.00 global fallback. That produced:

reserve=100000; fallback=2000000; unit=micro-USD

For usage settlement without usable evidence, the evaluator returned the reserve, not the global fallback. A reported $0.30 cost still evaluated to 300,000 micro-USD. The reserve is therefore neither the final bill nor a hard ceiling in this helper.

The ledger implementation separately applies margin, consumes credit blocks, and records shortfalls. The numbers in this note are raw evaluator outputs; they do not demonstrate a wallet debit or the behavior of concurrent settlements.

Zero, Missing, and Malformed

My synthetic rate card priced input at $0.000002 per unit, output at $0.000010, and image counts at $0.000100. The configured paths were usage.input, usage.output, and usage.details[modality=IMAGE].count.

The replay produced these exact integer results:

Response evidenceObserved usage amountEvaluator result, micro-USD
No configured meterUnobserved100,000
input: 0Zero0
input: 40, other meters absent$0.00008080
input: "40.0"$0.00008080
input: 40, output: -1Unobserved100,000
Boolean or nonfinite inputUnobserved100,000
output: 30000$0.30300,000

The implementation tracks whether it has seen any usable term. An absent term is skipped. A present but invalid number invalidates the combined figure rather than quietly dropping that term. Explicit zero remains evidence because the check does not rely on truthiness.

This handles sparse responses, including the omitted-zero-field convention described in the source tests. It cannot establish that every omitted field really means zero. If a provider renames only the output meter while retaining a valid input meter, this evaluator can still produce a partial charge. The replay's partial case exercises that exact structural condition; it does not prove that any provider currently sends such a broken response.

A Selector Is Not an Aggregation

The modality selector searches for the first matching list item. Moving an image entry behind a text entry did not change its charge: two image units still evaluated to 200 micro-USD.

I then supplied two image entries, with counts of two and nine. The result remained 200. usage_term_path() uses the first match; it does not sum repeated matches. A catalog path therefore carries an assumption about uniqueness in the provider envelope. A list selector alone is not enough to support a provider that partitions one modality across several entries.

Completion and Cost Can Arrive Separately

The inspected task orchestrator adds policy around the pure calculation. With require_usage=True, successful completion without usable usage returns awaiting_usage, leaving settlement open. Without that requirement, it evaluates the reserve fallback and flags the unobserved usage for reconciliation. Thus a successful provider status does not always close the hold immediately.

At task creation, the orchestrator stores a JSON value of the settlement basis. I tested that value boundary by serializing and loading the basis, editing the original synthetic catalog rate, then evaluating the saved value. The saved basis still returned 80 micro-USD. This checks JSON snapshot independence, not persistence, crash recovery, or transaction safety. The in-memory dictionaries themselves are not immutable.

A separate fixture supplied seven credit units without a frozen credit-to-money rate. Usage was observable, but the evaluator still returned the reserve. Reading a number and knowing how to price it are separate requirements. The orchestrator's unobserved-usage check alone does not cover this missing-rate condition.

Replay the Pure Boundary

The probe checks SHA-256 digests for both source files before importing them. It supplies namespace containers to bypass unrelated server package initializers; it replaces no settlement or path-lookup functions.

git clone https://github.com/superdesigndev/treg.git
git -C treg checkout 5b8b8cdca5ca90709093b8ee197639da907deb98
python settlement-probe.py ./treg

The actual replay command used the staged pinned source at /tmp/treg-note29 and exited 0. Selected terminal output:

missing: observed=None; settled=100000
explicit_zero: observed=0.0; settled=0
partial: observed=7.999999999999999e-05; settled=80
invalid_present: observed=None; settled=100000
above_reserve: observed=0.30000000000000004; settled=300000
duplicate_image: observed=0.0002; settled=200
serialized_basis_after_catalog_edit: settled=80
unrated_credit_meter: observed=7.0; settled=100000
PASS: 12 synthetic cases; pure modules only; no ledger or provider validation

The floating-point display comes from the production evaluator; the returned charge is integer micro-USD. The repository's asynchronous task tests cover related pricing and delayed-usage behavior, but I did not run that integration suite.

The practical contract is narrow: evidence must be usable, its unit must have a conversion rate, and the catalog must describe the response shape correctly. Treg makes those distinctions explicit in code. A successful tool result by itself cannot establish any of them.