Skip to content

fix(eval): correct trajectory token/cost accounting + open with the user step#737

Merged
ericleepi314 merged 2 commits into
mainfrom
fix/trajectory-token-accounting
Jul 22, 2026
Merged

fix(eval): correct trajectory token/cost accounting + open with the user step#737
ericleepi314 merged 2 commits into
mainfrom
fix/trajectory-token-accounting

Conversation

@ericleepi314

Copy link
Copy Markdown
Collaborator

Summary

Comparing the chess-best-move ATIF trajectory.json (clawcodex vs harbor's built-in claude-code agent) surfaced two gaps in the clawcodex trajectory. Eval-tooling only (eval/harbor/).

1. Token/cost accounting was wrong. The adapter set the trajectory final_metrics AND the leaderboard columns (n_input_tokens/n_cache_tokens/n_output_tokens/cost_usd) from the stream-json result.usage — which clawcodex builds for live-context measurement (input_tokens = running sum of NON-cached input; cumulative cache tokens dropped, only a last_* snapshot survives). On a prompt-cached opus run this reported total_prompt_tokens=6 vs claude-code's 412649, cost None. Fix: read the authoritative billing totals from the persisted session's cost block model_usage (input + cache_read + cache_creation summed per turn — the exact convention claude-code reports), for both the trajectory and the leaderboard. Stream-json usage stays the fallback only when no session cost block was synced.

2. No opening user step. claude-code's trajectory opens with the task instruction; clawcodex's stream-json (reconstructed) path had none. Fix: run() captures the instruction and the fallback path prepends it as step 1 (rich/conversation path already carries it — no double-inject). The backfill script recovers the instruction from the Harbor task cache so past jobs get it too.

Test plan

  • Live smoke (fresh fix-git, deepseek): trajectory total_prompt_tokens 165812→1082036 (the 916224 cache-read tokens now counted), cost None→0.0306; cross-checks exactly against the synced session model_usage; leaderboard columns == final_metrics; reward 1.0; rich path step1=user, 39/39 narrated+timestamps
  • Re-backfilled the 2026-07-19 opus job: 89/89 valid ATIF (was 86; the 3 extra are watchdog-crash trials now getting a 1-step "attempted" trace)
  • Billing convention verified against claude_code.py:_build_metrics (prompt=input+cache_read+cache_creation; cached=cache_read); no double-count (model_usage is per-turn-summed)
  • Critic review → APPROVE (nits folded in: decoupled session message/billing selection, docstring, vestigial param)

Known remaining gap (documented, follow-up): per-step token metrics — claude-code records per-message usage in its session log; clawcodex's persisted conversation doesn't, so trajectory steps carry aggregate metrics only. Requires a production persistence change.

🤖 Generated with Claude Code

ericleepi314 and others added 2 commits July 22, 2026 00:33
…ser step

Comparing the chess-best-move trajectories (clawcodex vs claude-code) in
the Harbor viewer surfaced two gaps in the clawcodex trajectory:

1. TOKEN ACCOUNTING WAS WRONG: total_prompt_tokens=6 vs claude-code's
   412649; cost=None. Root cause: the adapter read token totals from the
   stream-json `result.usage`, which clawcodex builds for live-CONTEXT
   measurement — `input_tokens` is the running sum of NON-cached input and
   it drops cumulative cache tokens (only a `last_*` snapshot survives). On
   a prompt-cached opus run that under-reports by ~5 orders of magnitude.
   Fix: read the authoritative BILLING totals from the persisted session's
   cost block `model_usage` (input + cache_read + cache_creation, summed
   per turn — the same convention claude-code reports), for BOTH the
   trajectory final_metrics AND the leaderboard token/cost columns
   (context.n_*_tokens / cost_usd). Stream-json usage stays the fallback
   only when no session cost block was synced. Verified end-to-end: a
   fresh deepseek trial went 208196 -> 1280580 prompt tokens (the 1.07M
   cache-read tokens are now counted) with cost 0.0377 (was None).

2. NO OPENING USER STEP in the stream-json (reconstructed) path:
   claude-code's trajectory opens with the task instruction; clawcodex's
   didn't. Fix: capture the instruction in run() and prepend it as step 1
   in the fallback path (the rich/conversation path already carries it, so
   no double-inject). The backfill script recovers the instruction from
   the Harbor task cache (config.json task.ref -> instruction.md), so past
   jobs get it too — including 3 watchdog-crash trials that previously had
   no trajectory at all (now a 1-step "attempted" trace).

Re-backfilled the 2026-07-19 opus job: 89/89 valid ATIF (was 86).
No src/ changes — adapter + backfill only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ion, drop vestigial arg, fix docstring

Critic-tok APPROVE follow-ups (all non-gating):
- _load_session_data now selects messages and billing INDEPENDENTLY (each
  by most messages), so a conversation-bearing session without a cost
  block still contributes its narration (was: skipped entirely). Unreachable
  today since Session.save writes the cost block unconditionally, but
  removes the latent footgun.
- Drop the vestigial _instruction_step(_n) parameter.
- backfill docstring: crashed trials now get a 1-step "attempted" trace
  (recovered instruction), not "no trajectory".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ericleepi314
ericleepi314 merged commit 9eddf22 into main Jul 22, 2026
2 checks passed
@ericleepi314
ericleepi314 deleted the fix/trajectory-token-accounting branch July 22, 2026 07:59
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.

1 participant