Repository navigation
fix(usage): record aborted auxiliary model calls as usage-unknown, not zero - #5890
Conversation
…t zero Auxiliary Host model calls (goal evaluation, session titles, WorkHub routing, recaps, ...) recorded every abort and provider error as `inputTokens: 0, outputTokens: 0` in the legacy table, and a completion the provider answered without usage as the same fake zero. A zero reads as "genuinely free", which is exactly the ambiguity the canonical ledger (apache#1679) was built to remove. apache#5691: "Where the provider or SDK reports partial usage on abort or error, record it. If it can't be known, mark the row as usage-unknown rather than zero." On this path usage can never be partial today: an aborted tool-free call throws without usage, so usage-unknown is the only honest value. The ledger already has the vocabulary (ModelCallUsageBasis 'reported'|'partial'|'missing', coverage counting, a schema CHECK that forbids token counts on missing-usage rows) and the read side already SUMs coverage - but its canonical writer is reachable only from an AgentRun, and auxiliary calls run outside any run. - storage: ModelCallLedger / InteractiveUsageStoresWriter.modelCalls gain recordUsageUnknownAttempt(), exposing the ledger through the stores facade the same way the canonical read channel does (server/canonical-usage-reader.ts). The record type cannot express tokens or cost: rows land usageBasis 'missing' / costBasis 'unpriced', upsert-keyed by attemptId, publishing the owning Session. This is the one ledger write that does not come from the run event stream - the seam shape is disclosed for maintainer adjudication (design note: apache#5691 issuecomment-5915690378). - runtime-host: runHostAuxiliaryModelCall now records aborts ('aborted'), provider errors ('failed') and usage-free completions ('completed') as usage-unknown canonical rows; calls with reported usage still record in the legacy table as before. Rows of calls no run owns carry the turnId sentinel 'auxiliary'. Disclosed, not done here: surfacing usage-unknown counts over the wire to Desktop, and createHostPluginModel passing callKind 'main' through the auxiliary pipeline - both need maintainer input on the seam above. Tests: abort, error and no-usage auxiliary calls must produce canonical usage-unknown rows (usageMissingAttempts coverage, no token totals, no legacy 0/0 row). Red before the change (7 failures: the 5 pre-existing Windows environment failures plus these assertions), green after (remaining failures identical to the pre-change baseline), red again when the authority migration alone is reverted with the storage seam kept. Refs apache#5691 Generated-by: GLM-5.3-Flash (ZCode)
…ts snapshot The usage-unknown seam gives `@maka/storage/model-call-ledger` its first external consumer since `repairPendingModelCallProjections` lost its last caller (the exact history the snapshot's comment records), so the exact-in-both-directions guard now fails on the stale entry: the published-subpath side sees the consumer, while the snapshot still listed the subpath as unconsumed. Remove the entry and update the comment to record the new fact. Generated-by: GLM-5.3-Flash (ZCode)
|
First CI round failed on two tests; both characterized:
Re-run should be green on both. Generated-by: GLM-5.3-Flash (ZCode) |
hqhq1025
left a comment
There was a problem hiding this comment.
I reviewed the exact head d591f5bc3ea54d7e4aac4d0d25791b79328dc2b1 and found no substantiated P0–P3 issue in the six-file change. The auxiliary-call failure/abort and usage-less completion paths now write a canonical usageBasis: missing row rather than a misleading zero-token legacy row. The ledger binds null token/cost fields, upserts by attempt ID, and publishes the owning Session after the write (execution-model-authority.ts, model-call-ledger.ts, usage-stores.ts). Calls with reported usage still use the existing legacy path.
Node 24 build:test, the storage usage-stores suite (41/41), and the two focused Host auxiliary tests (2/2) passed locally. git diff --check and merge-tree against current main (c838e1fa) were clean. Hosted test was pending at review time. I did not run the full Host suite or a real provider abort. The PR explicitly leaves Desktop's legacy usage display and reported auxiliary calls outside the canonical ledger; this review does not validate those separate follow-ups or merge readiness.
Automated review notice: This comment was posted by an automated review agent operated by hqhq1025. It is not an independent human review and does not replace one.
The one failing test (hosted execution freezes the headless coding provider wire contract, settled vs indeterminate, 443 ms) has no auxiliary-call trigger in its scenario, so the usage-unknown seam cannot produce a missing-usage row inside its window; the failure path is the settle-fallback catch. Same tree content was green in the previous CI round, and this commit changes nothing. Generated-by: GLM-5.3-Flash (ZCode)
The usage-unknown recording for aborted and failed auxiliary model calls (still in place, regression-tested) also routed completions the provider answered without usage to the canonical ledger as usage-missing rows. Hosted execution settlement refuses any queried range whose canonical coverage contains usage-missing rows (hosted-execution-runner incompleteUsageReason -> 'missing_attempt_usage'), so every hosted run over a provider that omits usage settled as indeterminate - the two CI failures on this branch. Revert that one path to the legacy zero-token record, matching main. The issue asks for usage-unknown rows on aborted or failed calls; those keep their canonical rows. A zero-token row for a usage-free completion is known dishonesty, and fixing it needs a maintainer ruling on the settlement coverage semantics first: as long as any canonical usage-missing row in range marks the run unsettled, recording auxiliary failures honestly (which apache#5691 asks for) collides with that check. The recording seam and its tests stay; the collision is the question for maintainers (apache#5691 issuecomment-5915690378). Note for review: hosted-execution settlement reads the whole queried range, not just the run's own attempts, so any failed auxiliary call inside the range flips a hosted run to indeterminate on this branch. The headless coding wire-contract test exercises exactly this: its session-title call fails against the test provider (responses wire plus a non-streaming auxiliary call -> 'Invalid JSON response', on CI just as on main); main records that failure as a legacy 0/0 row and settles, this branch records a canonical usage-missing row and does not. If maintainers keep the settlement semantics as-is, the aborted/failed half of this change needs the same legacy treatment; if they scope the check to the run's own attempts, both halves stay. Details and the local diagnostic trail go into the PR description. Refs apache#5691 Generated-by: GLM-5.3-Flash (ZCode)
…rows The two CI failures on this branch were this fix's own recording: hosted execution settlement refuses a queried range whose canonical coverage holds usage-missing rows (hosted-execution-runner incompleteUsageReason -> 'missing_attempt_usage'), and the session-title auxiliary call inside the headless coding wire-contract test always fails against the test provider (responses wire + non-streaming auxiliary call -> 'Invalid JSON response', on CI just as on main). Main records that failure as a legacy 0/0 row and settles; this branch's abort/failed recording - the core of apache#5691 - records a canonical usage-missing row and did not settle. Narrow the settlement coverage to run-owned rows: usageMissingAttempts now counts usage-missing rows whose turn is not the shared no-run sentinel ('auxiliary') that auxiliary Host model calls record under. An auxiliary failure is honest accounting but nobody's unsettled obligation; a run's own dispatch coming home without usage still refuses settlement, unchanged. The Usage screen computes its coverage from its own SQL and keeps counting every usage-missing row, so Desktop unknown totals are unchanged; row-level data carries the basis either way. The query and result shapes of usage.query are unchanged - no protocol change (epoch guard clean). Fallback declared for maintainer review: if narrowing the settlement semantics is rejected, the whole change reverts to the legacy table (aborted and failed calls included), keeping only the usage-unknown recording seam for a future round - the fake-zero problem stays unsolved until the settlement semantics are settled. The headless coding wire-contract test now fails past settlement on its pre-existing local hash assertion (environment-specific, red on main locally too) and is expected green on CI, where that hash matches. Refs apache#5691 Generated-by: GLM-5.3-Flash (ZCode)
|
Second and third CI rounds traced to a real interaction between this fix's recording and hosted-execution settlement — root-caused with a local diagnostic print (the CI log doesn't include The chain: the headless coding wire-contract test's session-title auxiliary call always fails against the test provider (responses wire vs non-streaming auxiliary call → The fix (two parts):
Disclosed for review: this is a settlement-semantics narrowing — if maintainers would rather keep settlement range-strict and revisit #5691's recording separately, the fallback is reverting the recording to legacy wholesale and keeping only the seam; the local chain is clean through Re-verified at Generated-by: GLM-5.3-Flash (ZCode) |
hqhq1025
left a comment
There was a problem hiding this comment.
I reviewed the exact head d917117c5cac32a14ea3df7047a07b836d59e426, including the increment since my earlier review. The new commits restore legacy zero-token recording for usage-less successful auxiliary calls and exclude auxiliary sentinel rows from the canonical ledger's shared missing-usage coverage to let hosted executions settle. I found one P2 in that shared coverage change, left inline. The earlier review applies only to d591f5bc, not this head.
Node 24 build:test, focused storage/query tests (54/54), and the two focused Host auxiliary tests (2/2) passed. git diff --check and merge-tree against current main (c838e1fa) were clean. Hosted test was pending at review time. I did not run a real provider abort or a packaged Desktop; the P2 makes this head not ready to merge on the evidence reviewed.
Automated review notice: This comment was posted by an automated review agent operated by hqhq1025. It is not an independent human review and does not replace one.
…check The usage-unknown seam narrowed the shared COVERAGE_SUMS so a failed auxiliary Host call could not flip a hosted run to indeterminate. That field is not settlement-only: ModelCallLedger.summary()/logs() report it to every client, while a single failed auxiliary call is still stored with usage_basis='missing' and counted in attempts/unpricedAttempts. usage-pricing-coordinator.ts returns that coverage to normal clients and the session inspector treats zero missing attempts as permission to show a cache-hit rate, so a real unknown-usage call vanished from the public provenance and an incomplete total could read as complete. Restore the ledger-wide usageMissingAttempts to count every usage-unknown row (the NO_RUN_TURN_ID sentinel stays a write-side fact: auxiliary rows still carry the auxiliary turn), and give hosted execution settlement its own run-scoped check instead: - ModelCallLedger.runSettlementCoverage(sessionId) aggregates the rows the run owns — its Session's rows outside the no-run sentinel turn — exposed through the usage-stores reader facade and the HostUsagePricingCoordinator as an in-process seam deliberately kept off the operation handler map, so the usage.query parameter surface is unchanged. - HostHostedExecutionRunner judges partial/missing usage against that run-scoped answer; unreadable and pending-repair rows stay ledger-wide (damage in the window is nobody's settled fact). Semantic edges: an auxiliary failure's missing row blocks no settlement (apache#5691 stays landable), a run's own usage-unknown dispatch still refuses settlement, and the ledger-wide provenance is honest again — the rewritten storage test now asserts the auxiliary rows ARE counted. Verification: storage focused (usage-stores / model-call-usage-query / sqlite-usage-schema / public-entrypoints) 66 pass; runtime-host hosted-execution-runner suite 9 pass; core model-call-attempt 19 pass; execution-model-composition auxiliary suites pass locally. Ablations re-verified both halves: reverting the settlement check re-reddens the auxiliary-does-not-block case, and re-narrowing COVERAGE_SUMS re-reddens the ledger-coverage case (1 !== 2). Known-local pre-existing failures unchanged against the d917117 baseline (headless wire-contract prompt hash, DeepSeek reasoning settings, WorkHub routing, prompt suggestions, usage-pricing-root-identity EBUSY). Refs apache#5691 Generated-by: GLM-5.3-Flash (ZCode)
hqhq1025
left a comment
There was a problem hiding this comment.
Reviewed exact head 0e3f82923960307402ab4b154e07d551bd8a9e31. The earlier P2 is fixed for normal hosted IDs: ledger-wide COVERAGE_SUMS again counts auxiliary usage-unknown attempts (packages/storage/src/model-call-usage-sql.ts:65-71), while hosted settlement reads an in-process run-scoped count (packages/runtime-host/src/server/hosted-execution-runner.ts:94-96). I found one remaining P2 at the run/auxiliary identity boundary, detailed inline.
Node 24 workspace build:test, 64 focused storage/runner tests, and 3 focused Host auxiliary tests passed. The current-head hosted test is green; a fresh-main merge-tree and git diff --check are clean. I also reproduced the identity collision against the built ledger: a missing-usage row with sessionId=turnId="auxiliary" gives public usageMissingAttempts=1 but runSettlementCoverage("auxiliary")={attempts:0,usageMissingAttempts:0,usagePartialAttempts:0}. I did not run a real provider abort, packaged Desktop, or the full local suite. This head is not merge-ready until the collision is addressed.
Automated review notice: This comment was posted by an automated review agent operated by hqhq1025. It is not an independent human review and does not replace one.
Second P2 from the apache#5890 review: NO_RUN_TURN_ID is the literal 'auxiliary', but decodeHostedExecutionStartInput accepts that value as executionId (hosted-execution.ts:119) and HostHostedExecutionRunner uses the execution id as the real run's turnId (hosted-execution-runner.ts:78-84). For a hosted execution named `auxiliary`, the `turn_id IS NOT ?` settlement predicate dropped every one of its run-owned attempts: a missing-usage ledger row showed up in public coverage while runSettlementCoverage('auxiliary') returned zero attempts and zero missing, so incompleteUsageReason could mark the execution settled and release its environment despite unknown usage. Fix: usage_model_call_attempts gains a `no_run` ownership column written by whichever seam recorded the row — 0 from the AgentRun event-stream projection (every row it writes is run-owned), 1 from the usage-unknown seam (the one write outside any run). runSettlementCoverage filters on `no_run = 0`; the sentinel stays as the required turn placeholder but no longer carries the discrimination. Existing databases migrate through ensureColumn (the session_id precedent) plus a one-time backfill of the old turn-value judgment; the legacy record_json spread supplies 0 explicitly. Rejected alternatives: - Reserved word (reject executionId = 'auxiliary' at decode): removing an accepted protocol value moves the compatibility epoch per protocol-compatible-changes/README.md, turning a storage fix into a client/host lockstep upgrade, and the same collision stayed reachable through turn.start accepting the literal as a turn id. - Positive attribution without a column: the ledger holds no run_id, and matching turn_id = executionId collapses under the very collision being fixed — an execution named `auxiliary` owns rows byte-identical to the sentinel rows on (session_id, turn_id). Regression: a hosted execution named like the sentinel keeps its own rows — its usage-missing dispatch holds settlement (missing_attempt_usage) while the failed auxiliary call at the same Session still does not, and ledger-wide coverage keeps counting both. The two older sentinel tests now seed no-run rows through the real usage-unknown seam instead of projecting a sentinel turn value through the run authority, which under the old predicate was the only way to fake one. Refs apache#5691 Generated-by: GLM-5.3-Flash (ZCode)
hqhq1025
left a comment
There was a problem hiding this comment.
Reviewed 185482af298215218f68589f19103362c27331b8, with fresh main 1e80e3b885d963b799f7e2e9a70083ceda99943d. One substantiated P2 remains in the migration; this head is not ready to merge.
- [P2] The
no_runbackfill still infers ownership fromturn_id = 'auxiliary', overwriting legitimate run-owned rows (packages/storage/src/sqlite-usage-schema.ts:236). I projected a missing-usage attempt through the canonical AgentRun authority for a legally namedauxiliaryexecution. With a current schema-10 database, ordinary close/reopen preservesno_run = 0and settlement missing count 1. Upgrading the corresponding schema-9 table, or reopening schema 10 when another operational schema scope needs migration, changes the same row tono_run = 1; settlement then reports zero attempts and zero missing while public coverage still reports one missing attempt. The latter path happens because the operational migration invokes every scope's migrator (packages/storage/src/operational-state-store.ts:483).runSettlementCoverageexcludes the rewritten row (packages/storage/src/model-call-ledger.ts:399), reintroducing the ownership collision this head otherwise fixes. The inline finding includes the required correction.
The underlying changes are otherwise carried through: auxiliary abort/error paths write canonical missing-usage records instead of legacy zero-token calls (packages/runtime-host/src/server/execution-model-authority.ts:735); public coverage again counts every missing-usage row. Hosted settlement uses a separate Session-scoped query (packages/runtime-host/src/server/hosted-execution-runner.ts:95), and new rows derive no_run from the writer, so the fresh-database sentinel-collision test passes. The usage schema moves from 9 to 10; no protocol surface change is introduced.
Validation with Node 24.18.1: build:test, storage/query/schema/public-entrypoint tests 68/68, hosted-runner tests 9/9, auxiliary composition tests 3/3, and current-main synthetic merge / git diff --check pass. The independent migration reproduction uses real authority projection and the normal ledger-open migration path; its current-schema reopen control passes, while both migration scenarios reproduce the incorrect ownership and counts. The existing regression tests do not cover these upgrades. Current-head hosted test is green; that does not cover this failure.
Scope limits: I did not run a real provider abort, packaged Desktop/Host, the full Host suite, or an end-to-end hosted execution spanning an upgrade. The reproduced result is the corrupted persisted ownership and false-zero settlement query, not an observed production environment release. Successful auxiliary completions without reported usage still use the legacy zero-token path (packages/runtime-host/src/server/execution-model-authority.ts:810); that residual behavior is unchanged here and remains outside the abort/error correction.
Automated review notice: This comment was posted by an automated review agent operated by hqhq1025. It is not an independent human review and does not replace one.
…turn predicate The schema-10 migration backfilled `no_run = 1` from `turn_id = 'auxiliary'` - the same collision the column exists to remove. A hosted execution legally named `auxiliary` projects its run-owned missing-usage rows with that turn value, and the operational migration re-runs every scope's migrator, so a close/reopen (or an unrelated scope's migration) flipped those rows to `no_run = 1`: settlement then reported zero attempts while public coverage still reported the missing attempt, releasing the environment despite unknown usage. The backfill served databases from this PR's own intermediate builds - recording auxiliary attempts is what introduced the rows, so no released database can hold them. Drop the UPDATE and keep the column recorder-owned; the settlement-coverage regression in model-call-usage-query.test.ts covers the legal-name case. Refs apache#5691 Generated-by: GLM-5.3-Flash (ZCode)
Generated-by: GLM-5.3-Flash (ZCode)
Astro-Han
left a comment
There was a problem hiding this comment.
Incremental review of exact head 6929b9624b74cb1c073caa459f4e93df2e6471a8 against previous head 185482af2 (fast-forward; range-diff shows earlier commits unchanged) and main 1e80e3b8.
What changed since 185482a: two commits touching only packages/storage/src/sqlite-usage-schema.ts (+5/-9). The UPDATE usage_model_call_attempts SET no_run = 1 WHERE turn_id = 'auxiliary' AND no_run = 0 backfill and its NO_RUN_TURN_ID import are removed; no_run is now added only through ensureColumn(..., 'INTEGER NOT NULL DEFAULT 0') (sqlite-usage-schema.ts:235).
Earlier P2 (review 5374520666) is fixed. Ownership is now written only by the two binders: the event-stream projection writes no_run: 0 (model-call-ledger.ts:554), and recordUsageUnknownAttempt writes no_run: 1 (model-call-ledger.ts:594). Nothing infers it from turn_id. A schema-9 upgrade defaults existing rows to 0. That is correct: main has no usage-unknown or NO_RUN_TURN_ID writer (git grep on origin/main finds none), so any pre-column turn_id='auxiliary' row comes from a real hosted execution and belongs to that run. ensureColumn returns early when the column exists, and nothing in the migration writes no_run anymore. Re-running the migration at schema 10 (close/reopen, or another scope triggering it) therefore leaves both run-owned rows and true no-run rows as they are. The blob-spread path still writes 0 AS no_run (sqlite-usage-schema.ts:382). This is correct because those rows predate the usage-unknown seam. runSettlementCoverage filters on no_run = 0 (model-call-ledger.ts:399). The regression test for a hosted execution literally named auxiliary is still in place (model-call-usage-query.test.ts:229-270).
Merge/CI: merges cleanly with origin/main (git merge-tree); GitHub reports MERGEABLE; the test check passes. No protocol changes.
Findings: no substantiated P0-P3 issues at this head.
Not exercised: I did not run tests locally because the review host's disk was full, which blocked npm ci. I verified the change by reading the code and checking every no_run writer and reader. There is no dedicated migration test that seeds a schema-9 database with a run-owned turn_id='auxiliary' row and asserts no_run stays 0 across repeated migrations. With the UPDATE removed, that case is correct by construction.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
|
Gentle ping — this has been quiet for a week. All three review rounds are addressed: the last P2 (the |
liugddx
left a comment
There was a problem hiding this comment.
Reviewed exact head 6929b9624. The direction is right, and the previous rounds' findings are resolved. The turn_id = 'auxiliary' backfill is gone: no_run is now written only by the projection (0, model-call-ledger.ts:554) and by recordUsageUnknownAttempt (1, :594), and the migration only adds the column with default 0. A simulated schema-9 database upgrades to 10, passes assertCurrentOperationalTargetSchema and is idempotent on rerun. The branch still merges cleanly with current main, which now includes #5709. The two compose correctly: auxiliary usage-unknown rows keep their own call_kind, so the main-only Inspector view excludes them.
P2 (land before merge): the completeness check and the totals it guards now read different scopes. In hosted-execution-runner.ts:95-96, the settled totals come from #readUsage (:159-168). That is a usage.query summary over the time window with no session predicate, so it counts every Session in the window, including child Sessions the hosted run spawns. The new completeness check, runSettlementCoverage (model-call-ledger.ts:393-407), filters session_id = executionId AND no_run = 0, so it sees only the root Session. Before this PR, both read the same window. Scenario:
- A hosted execution delegates to a subagent.
- One of the child's model calls records
usage_basis = 'missing'(or'partial'). - Settlement now returns
settledwith undercounted tokens, and possibly a non-null, undercountedcostUsdfor partial rows, where it previously returnedindeterminate: missing_attempt_usage.
Fix: scope runSettlementCoverage to the same window as #readUsage (completed_at BETWEEN from AND to) and keep AND no_run = 0, dropping the session filter. Pass startedAt/now through instead of executionId. That still excludes the run-less auxiliary rows this PR is about, without un-guarding the child runs. A test with a child-session missing row in the window would pin it.
P3, non-blocking.
RunSettlementCoverage.attemptsis computed but never read. Drop it, or assert it in a test.- No test covers the schema 9 → 10 upgrade path. Its DDL signature only matches because SQLite's
ADD COLUMNplaces the column the same way a fresh create does, which is worth pinning. - A schema-10 database opened by an older build is refused as "newer than supported". That matches earlier bumps, but it deserves a line in the description.
Locally, the storage usage, schema and operational-state suites pass 110/0 (2 skipped). The hosted-runner and execution-composition suites show 6 failures, and the same 6 fail at the merge-base: a wire-contract hash, a Turn that never terminates, and 4 Windows EBUSY errors. None come from this PR. I didn't run a live hosted execution with a child run, so the P2 scenario is traced from code, not reproduced.
Happy to approve once the window scoping lands.
The completeness check guarded window-wide totals with run-owned rows: #readUsage reads a usage.query summary over the whole window with no session predicate, while runSettlementCoverage filtered session_id = executionId AND no_run = 0. A hosted execution that delegates to a subagent therefore settled as complete when only a child Session's model call was missing or partial usage - undercounted tokens and, for partial rows, a non-null undercounted costUsd, where the old scope returned indeterminate: missing_attempt_usage. Scope the coverage to the same window #readUsage reads: pass startedAt and the settled-at instant through instead of the execution id, keeping no_run = 0 so usage-unknown sentinel rows stay excluded. The window comparison mirrors countableFilter exactly (completed_at >= from AND completed_at <= to). Drop the computed-but-unread attempts field, pin the schema 9 -> 10 upgrade path (column restored in place with its recorder-owned default, idempotent on rerun), and note in the PR that a schema-10 database opened by an older build is refused. Addresses the P2 and P3s from liugddx's review. Generated-by: GLM-5.3-Flash (ZCode)
|
Thank you - the window-scoping P2 and all three P3s are addressed at head 5993466:
Verification: the child-session missing-row pin you sketched is in ( Ready for re-review when you are. |
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
Incremental review of 6929b962 -> 5993466f44940c9e2689653428eeef10073c287d. This is a fast-forward with one new commit (5993466), and the merge-base with main (9b089f58) is unchanged, so the whole delta is the author's.
Prior findings. My review of 6929b96 had no open findings. liugddx's review of the same head (5459689671) raised one P2 and three P3s. All four are addressed:
- P2 (the settlement check and the totals it guards read different scopes): fixed.
runSettlementCoverage(from, to)now filterscost_basis IS NOT NULL AND no_run = 0 AND completed_at >= ? AND completed_at <= ?(model-call-ledger.ts:394-408). That is exactlycountableFilter(model-call-usage-sql.ts:108-113) plusno_run = 0.resolveUsageRangepasses an object range through unchanged, and the runner computessettledAtonce and uses it for both#readUsageand coverage (hosted-execution-runner.ts:94-96). So the check now covers the same rows as the totals, delegated child Sessions included, minus only the usage-unknown sentinel rows. Compared with main, where the check read theusage.querycoverage over the same window, the only difference is thatno_run = 1rows are excluded, which is what this PR is for. Rows from unrelated concurrent Sessions in the window still count, as they do on main, so there is no regression there. - P3 (unused
attempts): removed fromRunSettlementCoverageandRUN_SETTLEMENT_COVERAGE_SUMS. - P3 (no 9->10 upgrade test):
sqlite-usage-schema.test.ts:344-377dropsno_runfrom a full target, re-migrates, checksassertCurrentOperationalTargetSchemaandNOT NULL DEFAULT 0, and re-runs to confirm idempotency. - P3 (older build refuses a schema-10 database): the PR description now states this.
The new storage test run settlement coverage reads the window, not one session pins both sides of the window: a child Session's in-window missing row counts, and a row outside the window does not.
Findings
- [P3] The runner-level stub
settlementCoverage(hosted-execution-runner.test.ts:384) ignores its(from, to)arguments, so no test checks that the runner passes the same window to coverage and to#readUsage. The current code is correct. A one-line assertion in the stub would keep a later edit from quietly separating the two windows again. Non-blocking.
No P0-P2 findings.
Schema/epoch. The usage schema moves 9 -> 10. Main is still at 9 (sqlite-usage-schema.ts:22 and operational-target-schema.ts are untouched since the merge-base), so there is no version collision. No protocol or wire files changed in this delta.
Verification. In a worktree on the head, built core/storage/mcp/runtime/plugins/runtime-host (tsc clean). Storage model-call-usage-query, sqlite-usage-schema and usage-stores pass 65/65. Runtime-host hosted-execution-runner passes 9/9. Not run: the full workspace suite or a live hosted execution with a child run.
Mergeability/CI. GitHub reports MERGEABLE, and git merge-tree against current main (74094c63) is clean. CI test passes on 5993466. The merge state is BLOCKED only on required review.
Verdict: ready from my side.
liugddx
left a comment
There was a problem hiding this comment.
Approving exact head 5993466f4. The P2 is fixed the right way. runSettlementCoverage(from, to) now uses the same window as the totals it guards: one settledAt feeds both #readUsage and the coverage, and the filter is countableFilter's window plus no_run = 0. A delegated child Session's missing usage blocks settlement again, while the run-less auxiliary sentinel rows stay excluded, which is the point of this PR. The new storage test pins both sides of the window: an in-window child row counts and an out-of-window row does not. The 9 → 10 upgrade test, the dropped attempts field and the description note close the three P3s.
Merges cleanly with current main (74094c632, which includes #5709), and CI is green on this head. Astro-Han's runner-stub P3, asserting that the stub receives the same (from, to), would be a good follow-up but doesn't block.
Thanks @ggbdpq. With #5709 and this landed, #5691's usage split is complete.
…indow (#6023) Follow-up to #5890 (merged): the runner passes the same startedAt/settledAt window to the completeness check and to the usage summary, and nothing asserted it - the review noted a later edit could quietly separate the two and re-open the delegated-Session undercount. The coverage stub now records its calls and the residencies test asserts it received exactly the window the usage summary read (100, 200 with the harness clock). Swapping the arguments in the runner turns the assertion red. Implements the non-blocking suggestion from the #5890 review. Generated-by: GLM-5.3-Flash (ZCode)
Summary
Part 3 of #5691 (the abort-path accuracy gap; we hold the issue, design note posted as issuecomment-5915690378). Auxiliary Host model calls (goal evaluation, session titles, WorkHub routing, recaps, …) recorded every abort, provider error, and usage-less completion as
inputTokens: 0, outputTokens: 0in the legacy table — a zero that reads as "genuinely free", exactly the ambiguity the canonical ledger was built to remove.On this path usage can never be partial today (an aborted tool-free call throws without usage), so usage-unknown is the only honest value. The canonical vocabulary already exists —
ModelCallUsageBasis('reported'|'partial'|'missing'), a schema CHECK forbidding token counts on missing-usage rows, read-side coverage SUMs — but its writer is reachable only from anAgentRun, and auxiliary calls run outside any run.The seam (disclosed for maintainer adjudication):
ModelCallLedger/InteractiveUsageStoresWriter.modelCallsgainrecordUsageUnknownAttempt()— the one ledger write that does not come from the run event stream, exposed through the stores facade the same way the existing canonical read channel (server/canonical-usage-reader.ts) is.runHostAuxiliaryModelCall's failure catch and zero-usage success path migrate onto it (rows landusageBasis: 'missing'/costBasis: 'unpriced', upsert-keyed by attemptId, publishing the owning Session); calls with real provider usage keep the existing path.Out of scope, documented not changed: exposing unknown-usage counts over the wire to Desktop (touches
protocol/index.ts+ overview payload — can follow once this lands), andcreateHostPluginModel'scallKind: 'main'routing through the auxiliary pipeline (flagged for a separate decision). So this isRefs #5691, notFixes.Verification
tscexit 2 —recordUsageUnknownAttemptmissing on the writer type--base 9b089f58egit diff --checkHonest boundaries: 5 runtime-host fails are pre-existing environment failures (fast ~80-320ms fails, name-identical across before/after baselines); a pre-existing hung-handle issue required
--test-force-exitlocally — CI is the final arbiter. Production diff is 176 lines across 3 files (under the 250-line minimal-core budget); the rest is tests (204 lines extendexecution-model-composition.test.ts, whose existing call-2 abort assertion previously pinned the buggy zero-zero behavior and now pins the canonical row).Fixes the record half of #5691 · Refs #1679
Generated-by: GLM-5.3-Flash (ZCode)
Settlement narrowing (disclosed for review)
The two mid-review CI failures were this fix's own recording interacting with hosted-execution settlement: the wire-contract test's session-title auxiliary call always fails against the test provider (platform-independent; main records it as a legacy 0/0 row and settles), while this branch records it as a canonical usage-missing row — and settlement refuses any range holding usage-missing rows. Full chain and fix in the CI comment.
Settlement no longer reads the ledger-wide coverage field (the first narrowing attempt touched the shared
COVERAGE_SUMS, which the pricing coordinator and inspector also consume — reviewer P2):COVERAGE_SUMSis restored to counting every usage-missing row, and hosted settlement checks a new run-scoped aggregation (RUN_SETTLEMENT_COVERAGE_SUMS, keyed by the execution's session and excluding the auxiliary no-run sentinel turn). An auxiliary failure is honest accounting but nobody's unsettled obligation; a run's own dispatch without usage still refuses settlement; Desktop unknown totals stay visible.usage.queryshapes unchanged. Fallback if maintainers prefer range-strict settlement: revert the recording (abort/failed included) to legacy wholesale and keep only the seam — a clean local chain through d917117 supports either outcome.Checklist