You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
AttemptUsage.tokens/AttemptMeterTotals.tokens (packages/gittensory-engine/src/miner/attempt-metering.ts, iterate-loop.ts) is hardcoded to 0 on every iteration — an intentional, documented placeholder (#5395's own comment: "tokens stays an honest 0, no driver reports a real token count today"). This is a real gap, not a design decision to keep: real token data already exists and is simply not being read.
The Agent SDK's own SDKResultMessage (both SDKResultSuccess and SDKResultError variants, node_modules/@anthropic-ai/claude-agent-sdk/sdk.d.ts) carries a real usage: NonNullableUsage field with non-nullable input_tokens/output_tokens. agent-sdk-driver.ts already reads resultMessage.total_cost_usd/resultMessage.num_turns off this SAME object — it just never reads resultMessage.usage.
src/selfhost/ai.ts's extractCliUsage (used by ORB's own self-host AI review path) already has a proven, tested, best-effort token extractor for Claude/Codex CLI JSON/JSONL stdout (INPUT_TOKEN_KEYS/OUTPUT_TOKEN_KEYS/TOTAL_TOKEN_KEYS, scanning record.usage/record.token_usage/record.tokenUsage/record.usage_metadata). packages/gittensory-engine/src/miner/cli-subprocess-driver.ts's own comment explicitly says it "mirrors src/selfhost/ai.ts's extractCliUsage/COST_KEYS... but narrowed to just the cost field this driver's CodingAgentDriverResult surfaces -- tokens and model aren't part of that shape" — the narrowing was a deliberate scope cut at the time, not because the data doesn't exist.
This gap is directly load-bearing for Miner Wave 5 (Rent-a-Loop): Stake/payment → compute-time pricing #4790 (Stake/payment → compute-time pricing) and Per-tenant rate limits & quotas #4796 (Per-tenant rate limits & quotas) will need real usage data, not an honest zero, to price and throttle a paying tenant's actual consumption.
Dependencies
None — independently shippable. Builds on #5637 (merged) purely as a downstream consumer (the tokensUsed field it already persists).
Requirements
Add a usage?: { inputTokens?: number; outputTokens?: number; totalTokens?: number } | undefined field (or equivalent) to CodingAgentDriverResult (packages/gittensory-engine/src/miner/coding-agent-driver.ts), mirroring the existing costUsd field's own "absent (not zero) when unavailable, never fabricated" convention.
agent-sdk-driver.ts: read resultMessage.usage.input_tokens/.output_tokens (both the SDKResultSuccess and SDKResultError variants) alongside the existing total_cost_usd/num_turns reads, and populate the new usage field.
cli-subprocess-driver.ts: port (not reinvent) src/selfhost/ai.ts's extractCliUsage token-extraction logic (the INPUT_TOKEN_KEYS/OUTPUT_TOKEN_KEYS/TOTAL_TOKEN_KEYS constants and the nested-record scan) to extract real tokens from Claude/Codex CLI JSON/JSONL stdout, alongside the existing cost extraction. A missing/malformed field means "no token signal," exactly like the existing cost extraction — never a fabricated 0.
iterate-loop.ts: replace the hardcoded tokens: 0 in the per-iteration AttemptUsage increment with the real value from driverResult.usage?.totalTokens ?? 0 (mirroring exactly how costUsd/turns/wallClockMs are already threaded through per Decide fate of attempt-metering.ts: wire real per-attempt budget enforcement, or remove it #5395) — a pure data-accuracy change, no change to the loop's continue/handoff/abandon decision logic, thresholds, or the Governor chokepoint's own control flow.
attempt-cli.js: pass the real accumulated token total (from the loop result) into the attempt_outcome_summary event's tokensUsed field instead of always omitting it.
maxTokens/tokens-axis budget enforcement (attempt-metering.ts's evaluateAttemptBudget) stays OUT of scope for this issue — wiring a real number into that axis is a separate, deliberate policy decision (whether/how to enforce a token ceiling) and not implied by simply reporting the number accurately.
Deliverables / Acceptance Criteria
CodingAgentDriverResult carries real, driver-reported token usage when available
agent-sdk-driver.ts extracts real input_tokens/output_tokens from the SDK's own result message
cli-subprocess-driver.ts extracts real tokens from Claude/Codex CLI JSON output, mirroring src/selfhost/ai.ts's proven extraction logic
iterate-loop.ts's per-iteration meter totals report real tokens, not a hardcoded 0, with zero change to any continue/handoff/abandon decision
attempt_outcome_summary events persist the real token total
Every driver that genuinely reports no token signal continues to report an honest absence (null/undefined), never a fabricated number
Test Coverage Requirements
This PR must ship with full test coverage for every changed line and branch — the repo's Codecov patch gate requires 99%+ coverage and the house standard is to aim for 100%, including both sides of every conditional/nullish-coalescing branch introduced. Required: (1) agent-sdk-driver.ts tests for both the success and error result-message variants reporting real tokens, and the case where usage is absent/malformed; (2) cli-subprocess-driver.ts tests mirroring src/selfhost/ai.ts's own extractCliUsage test matrix (multiple key spellings, nested usage/token_usage objects, malformed/non-JSON stdout); (3) an iterate-loop.ts regression test asserting the accumulated finalMeterTotals.tokens reflects real per-iteration values across multiple iterations, AND that the loop's continue/handoff/abandon decision is byte-identical to before this change for the same inputs (proves this is data-accuracy-only, no control-flow drift); (4) an attempt-cli.js test asserting the real token total reaches the persisted attempt_outcome_summary event.
Expected Outcome
packages/gittensory-miner/lib/attempt-log.js's tokens_used column, and the grafana/dashboards/miner-usage.json "Total tokens" panel it feeds (#5185), report real per-provider token consumption instead of an honest but uninformative 0 — and Wave 5's pricing/rate-limit work has real usage data to build on instead of needing to invent its own extraction from scratch.
Context
AttemptUsage.tokens/AttemptMeterTotals.tokens(packages/gittensory-engine/src/miner/attempt-metering.ts,iterate-loop.ts) is hardcoded to0on every iteration — an intentional, documented placeholder (#5395's own comment: "tokens stays an honest 0, no driver reports a real token count today"). This is a real gap, not a design decision to keep: real token data already exists and is simply not being read.SDKResultMessage(bothSDKResultSuccessandSDKResultErrorvariants,node_modules/@anthropic-ai/claude-agent-sdk/sdk.d.ts) carries a realusage: NonNullableUsagefield with non-nullableinput_tokens/output_tokens.agent-sdk-driver.tsalready readsresultMessage.total_cost_usd/resultMessage.num_turnsoff this SAME object — it just never readsresultMessage.usage.src/selfhost/ai.ts'sextractCliUsage(used by ORB's own self-host AI review path) already has a proven, tested, best-effort token extractor for Claude/Codex CLI JSON/JSONL stdout (INPUT_TOKEN_KEYS/OUTPUT_TOKEN_KEYS/TOTAL_TOKEN_KEYS, scanningrecord.usage/record.token_usage/record.tokenUsage/record.usage_metadata).packages/gittensory-engine/src/miner/cli-subprocess-driver.ts's own comment explicitly says it "mirrors src/selfhost/ai.ts's extractCliUsage/COST_KEYS... but narrowed to just the cost field this driver's CodingAgentDriverResult surfaces -- tokens and model aren't part of that shape" — the narrowing was a deliberate scope cut at the time, not because the data doesn't exist.tokensUsedas a first-class, nullable column on the attempt-log'sattempt_outcome_summaryevent and the redacted AMS reporting export (Grafana-visible via Add a Grafana dashboard for coding-agent-driver usage, cost, and attempt outcomes #5185'sminer-usage.json) — it's currently always written asundefined/nullbecause nothing upstream produces a real value.Dependencies
None — independently shippable. Builds on #5637 (merged) purely as a downstream consumer (the
tokensUsedfield it already persists).Requirements
usage?: { inputTokens?: number; outputTokens?: number; totalTokens?: number } | undefinedfield (or equivalent) toCodingAgentDriverResult(packages/gittensory-engine/src/miner/coding-agent-driver.ts), mirroring the existingcostUsdfield's own "absent (not zero) when unavailable, never fabricated" convention.agent-sdk-driver.ts: readresultMessage.usage.input_tokens/.output_tokens(both theSDKResultSuccessandSDKResultErrorvariants) alongside the existingtotal_cost_usd/num_turnsreads, and populate the newusagefield.cli-subprocess-driver.ts: port (not reinvent)src/selfhost/ai.ts'sextractCliUsagetoken-extraction logic (theINPUT_TOKEN_KEYS/OUTPUT_TOKEN_KEYS/TOTAL_TOKEN_KEYSconstants and the nested-record scan) to extract real tokens from Claude/Codex CLI JSON/JSONL stdout, alongside the existing cost extraction. A missing/malformed field means "no token signal," exactly like the existing cost extraction — never a fabricated 0.iterate-loop.ts: replace the hardcodedtokens: 0in the per-iterationAttemptUsageincrement with the real value fromdriverResult.usage?.totalTokens ?? 0(mirroring exactly howcostUsd/turns/wallClockMsare already threaded through per Decide fate of attempt-metering.ts: wire real per-attempt budget enforcement, or remove it #5395) — a pure data-accuracy change, no change to the loop's continue/handoff/abandon decision logic, thresholds, or the Governor chokepoint's own control flow.attempt-cli.js: pass the real accumulated token total (from the loop result) into theattempt_outcome_summaryevent'stokensUsedfield instead of always omitting it.maxTokens/tokens-axis budget enforcement (attempt-metering.ts'sevaluateAttemptBudget) stays OUT of scope for this issue — wiring a real number into that axis is a separate, deliberate policy decision (whether/how to enforce a token ceiling) and not implied by simply reporting the number accurately.Deliverables / Acceptance Criteria
CodingAgentDriverResultcarries real, driver-reported token usage when availableagent-sdk-driver.tsextracts realinput_tokens/output_tokensfrom the SDK's own result messagecli-subprocess-driver.tsextracts real tokens from Claude/Codex CLI JSON output, mirroringsrc/selfhost/ai.ts's proven extraction logiciterate-loop.ts's per-iteration meter totals report real tokens, not a hardcoded 0, with zero change to any continue/handoff/abandon decisionattempt_outcome_summaryevents persist the real token totalTest Coverage Requirements
This PR must ship with full test coverage for every changed line and branch — the repo's Codecov patch gate requires 99%+ coverage and the house standard is to aim for 100%, including both sides of every conditional/nullish-coalescing branch introduced. Required: (1)
agent-sdk-driver.tstests for both the success and error result-message variants reporting real tokens, and the case whereusageis absent/malformed; (2)cli-subprocess-driver.tstests mirroringsrc/selfhost/ai.ts's ownextractCliUsagetest matrix (multiple key spellings, nestedusage/token_usageobjects, malformed/non-JSON stdout); (3) aniterate-loop.tsregression test asserting the accumulatedfinalMeterTotals.tokensreflects real per-iteration values across multiple iterations, AND that the loop'scontinue/handoff/abandondecision is byte-identical to before this change for the same inputs (proves this is data-accuracy-only, no control-flow drift); (4) anattempt-cli.jstest asserting the real token total reaches the persistedattempt_outcome_summaryevent.Expected Outcome
packages/gittensory-miner/lib/attempt-log.js'stokens_usedcolumn, and thegrafana/dashboards/miner-usage.json"Total tokens" panel it feeds (#5185), report real per-provider token consumption instead of an honest but uninformative 0 — and Wave 5's pricing/rate-limit work has real usage data to build on instead of needing to invent its own extraction from scratch.Links & Resources
packages/gittensory-engine/src/miner/coding-agent-driver.ts(CodingAgentDriverResult)packages/gittensory-engine/src/miner/agent-sdk-driver.ts(Agent SDK result-message reading)packages/gittensory-engine/src/miner/cli-subprocess-driver.ts(CLI stdout parsing)packages/gittensory-engine/src/miner/iterate-loop.ts/attempt-metering.ts(per-iteration accumulation)src/selfhost/ai.ts'sextractCliUsage(the extraction logic being ported)node_modules/@anthropic-ai/claude-agent-sdk/sdk.d.ts'sSDKResultMessage/NonNullableUsagetypes