fix(runtime): accept fractional USD in driver prompt-cache evidence - #818
Conversation
The driver applied one rule to every numeric prompt-cache field: a non-negative safe integer. Token counts satisfy it; USD amounts do not. Any provider that reports cache savings in dollars therefore killed the driver on its first turn with driverAgent: prompt-cache field "readSavingsUsd" must be a non-negative safe integer which is what tangle-router returns (readSavingsUsd 0.0034 on a healthy response). The failure is total: the root driver never completes a turn, so the whole supervised run ends no-winner with reason driver-failed. The rule now splits by field kind. A name ending in usd (case-insensitive) must be a non-negative FINITE number; every other numeric field keeps the non-negative safe-integer rule. The check moves into an exported validateDriverPromptCache so the contract is named and unit-testable. 6 new tests cover the real router shape, fractional token counts, negative and non-finite USD, string fields, absent evidence, and the suffix rule (usdTokens stays a count). Typecheck clean; supervise suite 55/55.
tangletools
left a comment
There was a problem hiding this comment.
✅ Auto-approved drewstone PR — 559026b5
This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.
tangletools · auto-approval · reason: drewstone_author · 2026-08-13T08:44:38Z
tangletools
left a comment
There was a problem hiding this comment.
🟢 Value Audit — sound
| Verdict | sound |
| Concerns | 1 (1 weak-concern) |
| Heuristic | 0.0s |
| Duplication | 0.0s |
| Interrogation | 114.1s (2 bridge agents) |
| Total | 114.1s |
💰 Value — sound
Splits driver prompt-cache validation so fractional USD fields (the shape tangle-router returns) no longer kill the driver's first turn; minimal, correct, and in the grain of the deliberately-loose seam.
- What it does: Extracts the prompt-cache numeric check out of the driver turn loop into an exported, unit-tested validateDriverPromptCache. A field whose name ends in 'usd' (case-insensitive) must be a non-negative finite number; every other numeric field keeps the non-negative safe-integer (token-count) rule; strings and absent evidence are untouched. Wired in at coordination-driver.ts:532 replacing the old sin
- Goals it achieves: Stop the driver from refusing a healthy router response that reports cache savings in fractional dollars (readSavingsUsd: 0.0034), which made every router-driven root fail its first turn with reason 'driver-failed' and ended runs no-winner. Restores router-driven graph lanes and makes the validation contract named and testable rather than buried in act().
- Assessment: Good change on its merits. The fix is correct and minimal, and it matches the codebase's grain: the ToolLoopChat seam is intentionally typed as a loose Record<string, number | string> (tool-loop.ts:54) to stay transport-agnostic, so a name-based rule that classifies by suffix is consistent with that design rather than fighting it. The extraction to a named, exported, testable function is a strict
- Better / existing approach: Searched for an existing equivalent and a typed alternative. The router already has a closed, typed PromptCacheUsage (router-client.ts:523) naming readSavingsUsd as the single dollar field, and the driver already has an isTokenCount helper (coordination-driver.ts:916) plus name-specific consumption of readTokens/writeTokens in driverPromptCacheUsage. A structurally-typed seam would make validation
- Model: opencode/zai-coding-plan/glm-5.2
- Bridge attempts: 2
- Bridge warning: opencode/kimi-for-coding/k2p7: opencode: opencode error
🎯 Usefulness — sound
A correct, minimal fix that splits prompt-cache validation by field kind (USD = fractional, tokens = integer) to unblock the total first-turn failure the router's fractional readSavingsUsd caused.
- Integration: Fully reachable.
validateDriverPromptCacheis called on every driver inference turn at coordination-driver.ts:532 insidemeteredBrain, which is the live production path for every router-brained root driver (the exact path the PR says was dying). The export is also unit-tested directly. No dead surface: the call site pre-existed (this is an extract-and-fix of an inline loop, not new wiring), an - Fit with existing patterns: Fits the established pattern exactly. This was already the single validation site for promptCache (grep confirms no competing validator exists anywhere in src). The change preserves the identical refusal shape and message style, just splits the rule. It also aligns with the downstream consumer:
driverPromptCacheUsage(coordination-driver.ts:892-914) already gatesreadTokens/writeTokensthrou - Real-world viability: Holds up on and off the happy path. NaN and Infinity rejected via
Number.isFinite(test at line 23-27); negatives rejected for both kinds; strings (status,tier) skipped via thetypeof !== 'number'guard; absent/empty evidence accepted without inventing zeros. The suffix regex/usd$/icorrectly keepsusdTokensas a count (doesn't end inusd) and treatssavingsUSDas dollars — the t - Model: opencode/zai-coding-plan/glm-5.2
- Bridge attempts: 1
🎯 Usefulness Audit
🟡 Suffix heuristic classifies by name, not by declared kind [robustness] ``
The rule keys off
/usd$/ion the field NAME rather than a declared field-kind map. For the current router shape (PromptCacheUsage, router-client.ts:523-535) this is exactly correct, and it gracefully auto-extends to future*CostUsd/*SavingsUsdfields. The risk is narrow and future-only: a provider field reporting dollars under a name not ending inusd(e.g. a hypotheticalsavingsDollars) would be held to the safe-integer count rule and refused. No such field exists today. An explicit
What this audit checks
It judges the change on its merits — not whether it was tasked out in an issue. Unticketed, fast-moving work is fine; the question is whether the change is good and whether a better or existing approach should be used instead.
| Pass | What it asks |
|---|---|
| Heuristic | Vague title? Whitespace-only or cruft-bearing diff? (content signals only) |
| Duplication | Do added function/class names already exist elsewhere in the repo? |
| Value Audit | What does it do? What goal does it achieve? Is it good? Better architecture or already-exists? |
| Usefulness Audit | Does it integrate and fit? Will it hold up in real use and actually get used? |
Findings are concerns, not blocks — the human reviewer decides what to do with them.
✅ No Blockers —
|
| opencode GLM 5.2 | opencode DeepSeek v4 Pro | opencode DeepSeek v4 Flash | aggregate | |
|---|---|---|---|---|
| Readiness | 89 | 92 | 86 | 86 |
| Confidence | 65 | 65 | 65 | 65 |
| Correctness | 89 | 92 | 86 | 86 |
| Security | 89 | 92 | 86 | 86 |
| Testing | 89 | 92 | 86 | 86 |
| Architecture | 89 | 92 | 86 | 86 |
Reviewer score is advisory once the run is complete and the verdict has no blockers.
Full multi-shot audit completed 1/1 planned shots over 2 changed files. Global verifier still owns final merge decision. | Full multi-shot audit completed 1/1 planned shots over 2 changed files. Global verifier still owns final merge decision. | Full multi-shot audit completed 1/1 planned shots over 2 changed files. Global verifier still owns final merge decision.
🟡 LOW No integration test that the metered driver turn accepts fractional USD — src/runtime/supervise/coordination-driver.test.ts
All 6 tests exercise the exported validator directly. The wiring line (evidenceError = validateDriverPromptCache(res.promptCache) ?? evidenceError, coordination-driver.ts:532) and the regression path 'a full driverAgent act() survives readSavingsUsd: 0.0034 and still throws on readTokens: 12.5' are not covered end-to-end. Also untested: -0 (passes USD check since -0 < 0 is false) and the real router's missTokens field. Non-blocking coverage gap.
🟡 LOW No test for a USD field that is a whole number — src/runtime/supervise/coordination-driver.test.ts
The USD-path tests only cover fractional (0.0034, 1.25) and invalid (-0.01, NaN, Infinity) values. A whole-dollar USD value like
readSavingsUsd: 5also passes the USD branch (Number.isFinite and >= 0), verified standalone, but is not asserted. Minor gap; the behavior is correct.
🟡 LOW Field-name suffix heuristic is the semantic discriminator — src/runtime/supervise/coordination-driver.ts
Semantics are inferred from /usd$/i rather than a schema/allowlist. Correct for today's router contract (readSavingsUsd) and the test pins the suffix rule (usdTokens stays a count), but a future fractional cache field not ending in 'usd' will re-trigger the refusal the PR fixes. Consider matching the explicit PromptCacheUsage field set or documenting the heuristic as the contract. Non-blocking.
🟡 LOW USD classification is name-suffix heuristic, not schema-driven — src/runtime/supervise/coordination-driver.ts
The /usd$/i suffix test classifies numeric prompt-cache fields as fractional-USD vs integer-count by field NAME rather than by the known PromptCacheUsage schema. It is correct for every current field (readSavingsUsd is the sole USD field; readTokens/writeTokens/missTokens are counts). However, promptCache can also arrive from the sandbox path where finiteMetadata (sandbox-events.ts:243) passes arbitrary provider fields through verbatim, so an unknown provider field whose name ends in 'usd' but is a count (or a dollar field that does not end in 'usd') would be mis-validated: a count would silently skip integer checking, or a dollar field would be rejected as non-integer. Not a defect today; a whitelist over the known keys would make the boundary explicit. No action required to merge.
🟡 LOW USD fields now accept absurd-but-finite values — src/runtime/supervise/coordination-driver.ts
Number.isFinite(value) with value >= 0 accepts e.g. readSavingsUsd: 1e308, which the prior universal safe-integer check rejected. Impact is journal-only (promptCache detail, line 563) and never reaches spend accounting (turnSpend.usd derives from costUsd; driverPromptCacheUsage reads only readTokens/writeTokens via isTokenCount), so this is a robustness nit, not a data-integrity bug. A sane upper bound or safe-integer-plus-fraction rule would keep the intent.
🟡 LOW USD-vs-count classification relies on an implicit name-suffix convention — src/runtime/supervise/coordination-driver.ts
The split uses
/usd$/i.test(field)to decide a field is a dollar amount. This is correct for every field the router currently emits (readSavingsUsd is the only USD field in PromptCacheUsage, router-client.ts:531), and the doc-comment documents the rule. But the consumer type is an openRecord<string, number | string>, so a provider reporting a dollar field namedsavingsDollars,cacheSavings, or barecostwould still be rejected as a non-safe-integer. Not a regression (the prior code rejected these too) and strictly an improvement over the all-integer rule; noting because the contract is convention-based rather than schema-based. No action required for this PR.
tangletools · 2026-08-13T08:52:16Z · trace
tangletools
left a comment
There was a problem hiding this comment.
✅ Approved — 6 non-blocking findings — 559026b5
Full multi-shot audit completed 1/1 planned shots over 2 changed files. Global verifier still owns final merge decision. | Full multi-shot audit completed 1/1 planned shots over 2 changed files. Global verifier still owns final merge decision. | Full multi-shot audit completed 1/1 planned shots over 2 changed files. Global verifier still owns final merge decision.
Full immutable report for this review: trace
Summary comment for this run: full summary
tangletools · 2026-08-13T08:52:16Z · immutable trace
Addresses the review's convergent finding (4 of 6): the /usd$/i suffix was the sole semantic discriminator, so a provider dollar field named savingsDollars would still be refused and an unknown count ending in 'usd' would skip integer checking. Classification is now schema-first. PromptCacheUsage names its members and readSavingsUsd is its only dollar amount, so known fields are validated by what they ARE; promptCache stays an open record because the sandbox path forwards provider fields verbatim, so unknown fields keep the documented usd-suffix convention as the fallback. Also from the review: a USD ceiling keeps 1e308 out (the old all-integer rule rejected it as a side effect), and three tests were added — whole dollars, -0, the absurd value, the full router shape with missTokens, and known-member classification. 9 tests here, supervise suite 58/58, lint and typecheck clean.
Problem
driverAgentvalidated every numericpromptCachefield with one rule — non-negative safe integer. Token counts satisfy that; USD amounts cannot.Any provider that reports cache savings in dollars kills the driver on its first turn:
tangle-routerreturns exactly that (readSavingsUsd: 0.0034on a healthy response). The failure is total, not cosmetic: the root driver never completes a turn, so the run endsno-winnerwithreason: "driver-failed"and zero work done.Observed on runtime 0.133.3 driving a
runGraphroot through the router substrate — every graph lane with a router-driven root is currently unable to start.Fix
Split the rule by field kind:
usd(case-insensitive) must be a non-negative finite numberThe check moves out of the turn loop into an exported
validateDriverPromptCache, so the contract is named and unit-testable rather than buried inact().Verification
src/runtime/supervise/coordination-driver.test.ts: the real router shape, a fractional token count still refused, negative and non-finite USD refused, string fields ignored, absent evidence accepted, and the suffix rule (usdTokensstays a count,savingsUSDis dollars).npx vitest run src/runtime/supervise/→ 55/55 pass (4 files).npx tsc --noEmit→ clean.🤖 Generated with Claude Code