Skip to content

fix(runtime): accept fractional USD in driver prompt-cache evidence - #818

Merged
drewstone merged 2 commits into
mainfrom
fix/prompt-cache-usd-validation
Aug 13, 2026
Merged

fix(runtime): accept fractional USD in driver prompt-cache evidence#818
drewstone merged 2 commits into
mainfrom
fix/prompt-cache-usd-validation

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

Problem

driverAgent validated every numeric promptCache field 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:

ValidationError: driverAgent: prompt-cache field "readSavingsUsd" must be a non-negative safe integer

tangle-router returns exactly that (readSavingsUsd: 0.0034 on a healthy response). The failure is total, not cosmetic: the root driver never completes a turn, so the run ends no-winner with reason: "driver-failed" and zero work done.

Observed on runtime 0.133.3 driving a runGraph root through the router substrate — every graph lane with a router-driven root is currently unable to start.

Fix

Split the rule 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
  • string fields and absent evidence are untouched

The check moves out of the turn loop into an exported validateDriverPromptCache, so the contract is named and unit-testable rather than buried in act().

Verification

  • 6 new tests in 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 (usdTokens stays a count, savingsUSD is dollars).
  • npx vitest run src/runtime/supervise/ → 55/55 pass (4 files).
  • npx tsc --noEmit → clean.

🤖 Generated with Claude Code

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 tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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. validateDriverPromptCache is called on every driver inference turn at coordination-driver.ts:532 inside meteredBrain, 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 gates readTokens/writeTokens throu
  • 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 the typeof !== 'number' guard; absent/empty evidence accepted without inventing zeros. The suffix regex /usd$/i correctly keeps usdTokens as a count (doesn't end in usd) and treats savingsUSD as 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$/i on 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/*SavingsUsd fields. The risk is narrow and future-only: a provider field reporting dollars under a name not ending in usd (e.g. a hypothetical savingsDollars) 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.

value-audit · 20260813T084833Z

@tangletools

Copy link
Copy Markdown
Contributor

✅ No Blockers — 559026b5

Review health 100/100 · Reviewer score 86/100 · Confidence 65/100 · 6 findings (6 low)

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: 5 also 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 open Record<string, number | string>, so a provider reporting a dollar field named savingsDollars, cacheSavings, or bare cost would 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
tangletools previously approved these changes Aug 13, 2026

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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.
@drewstone
drewstone merged commit 0565e80 into main Aug 13, 2026
4 checks passed
@drewstone
drewstone deleted the fix/prompt-cache-usd-validation branch August 13, 2026 09:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants