{"record":{"id":"7eb328c75c5ba03e","repo":"JuliusBrussee/caveman","slug":"cave-transform-trace-basis-mixed","errorCode":"cave_transform_trace_basis_mixed","errorMessage":"cave_transform_trace_basis_mixed","messagePattern":"cave_transform_trace_basis_mixed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/code.ts","lineNumber":785,"sourceCode":"  // missing runtime does.\n  if (result.mode === \"observe-only\") degrade(session);\n  const record: TurnRecord = {\n    input,\n    text: result.text,\n    toolCalls: result.toolCalls,\n    bill: turnBill(session.turns.length + 1, result),\n    degraded: startedOptimized && session.mode === \"observe-only\",\n  };\n  session.turns.push(record);\n  return record;\n}\n\nfunction turnBill(turn: number, result: RunResult): TurnBill {\n  const applied = result.transformTrace.filter((item) => item.outcome === \"applied\");\n  // A token delta is only meaningful within one basis; refuse to blend\n  // byte-derived and engine-counted figures into a single reduction.\n  const bases = new Set(applied.map((item) => item.tokensBasis));\n  if (bases.size > 1) throw new Error(\"cave_transform_trace_basis_mixed\");\n  const tokensBasis: TransformTrace[\"tokensBasis\"] = applied[0]?.tokensBasis ?? \"byte_derived\";\n  const before = applied.reduce((sum, item) => sum + item.beforeTokens, 0);\n  const after = applied.reduce((sum, item) => sum + item.afterTokens, 0);\n  return {\n    turn,\n    mode: result.mode,\n    provider: result.provider,\n    model: result.model,\n    contextBill: result.contextBill,\n    transformedTokensBefore: before,\n    transformedTokensAfter: after,\n    // The reduction is the delta of what was ACTUALLY SENT, so a barely-\n    // compressible turn whose wrapper cost more than it saved reports zero\n    // rather than a phantom positive; the raw before/after stay exposed.\n    tokensSavedInferred: Math.max(0, before - after),\n    tokensBasis,\n    transformIDs: result.transformIDs,\n    transformFailures: result.transformFailures,","sourceCodeStart":767,"sourceCodeEnd":803,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/766dce6b1394ebb56a3090748d5a0240a5aefb36/packages/agent/src/code.ts#L767-L803","documentation":"A turn bill sums before/after token counts over the transforms applied that turn, and a token delta is only meaningful within one measurement basis — `byte_derived` (local estimate) or `engine_counted` (loopback engine's own count). `turnBill` refuses to blend the two into a single reduction figure and throws this invariant error instead of publishing a dishonest number.","triggerScenarios":"A single run's `RunResult.transformTrace` contains applied entries with different `tokensBasis` values — typically part of the turn's transforms got engine token counts while another transform route (or a transform-failure recovery path) reported byte-derived figures. Surfaces from `runCodingTurn` when the turn record is billed.","commonSituations":"Engine/loopback runtime becomes available or drops mid-session so routes flip between counted and estimated; version skew between gateway and engine; usually indicates a framework bug worth reporting rather than a user config mistake.","solutions":["Keep engine configuration uniform for the whole session: either run with the loopback engine the entire time or fully cold (observe-only), not a mix.","Restart the session (`startCodingSession` again) after changing engine availability so the route pins one basis.","If it reproduces with a stable configuration, capture `RunResult.transformTrace` and file an issue — per-turn basis uniformity is an internal contract."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"function hasMixedBases(result: RunResult): boolean {\n  const bases = new Set(\n    result.transformTrace\n      .filter((item) => item.outcome === \"applied\")\n      .map((item) => item.tokensBasis),\n  );\n  return bases.size > 1;\n}","typeGuard":null,"tryCatchPattern":"try {\n  const record = await runCodingTurn(session, input);\n} catch (error) {\n  if (error instanceof Error && error.message === \"cave_transform_trace_basis_mixed\") {\n    // billing invariant broke: keep the session, mark the token bill unavailable\n    session.notices.push(\"token bill unavailable: mixed token bases this turn\");\n    return;\n  }\n  throw error;\n}","preventionTips":["Do not start/stop the loopback engine between turns of one session.","Treat a mixed-basis turn as a framework bug: capture transformTrace and report it."],"tags":["tokens","accounting","invariant","transform-trace"],"backgroundTag":"token-accounting-mismatch","analyzedSha":"766dce6b1394ebb56a3090748d5a0240a5aefb36","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}