Skip to content

Fix restarted Codex subagent usage accounting#2208

Merged
steipete merged 3 commits into
steipete:mainfrom
harjothkhara:codex/fix-2193-restarted-fork-usage
Jul 16, 2026
Merged

Fix restarted Codex subagent usage accounting#2208
steipete merged 3 commits into
steipete:mainfrom
harjothkhara:codex/fix-2193-restarted-fork-usage

Conversation

@harjothkhara

@harjothkhara harjothkhara commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • recognize explicit Codex source.subagent provenance in both fast and fallback metadata parsers
  • treat spawned subagent counters as independent instead of subtracting the parent's unrelated cumulative baseline
  • key each rollout by its per-thread id, not the multi-agent tree's shared session_id
  • preserve inherited-baseline subtraction for ordinary forks and invalidate stale parser caches

Refs #2193.

Why

The reporter's follow-up showed that CodexBar found and scanned all rollouts. The undercount happened later: forked_from_id made CodexBar subtract the parent's cumulative total from a subagent whose counter restarted at zero.

Codex protocol metadata marks spawned children with source.subagent. Those counters are independent. A bare parent_thread_id remains an ordinary fork because it can carry a continuing parent counter. Current multi-agent metadata also uses one shared session_id for the tree and a unique id for each thread, so rollout identity must prefer id across both supported metadata locations.

Verification

  • swift test --filter CostUsageScannerBreakdownTests — 87 tests passed
  • swift test --filter CostUsageScannerTests — passed
  • make check — 0 SwiftLint violations; parser hash, formatting, shell, docs, and repository checks passed
  • autoreview — clean after fixing shared-tree identity and mixed metadata layout findings
  • regression uses parent and child records with the same session_id but distinct root-level id values

Behavior proof

An isolated production-style parent fixture reports 60,000,000 input / 48,000,000 cached / 3,000,000 output tokens. Its restarted subagent ends at 62,200 / 51,000 / 3,200. The combined report now returns 60,062,200 / 48,051,000 / 3,003,200, including the child exactly once.

The negative control remains intact: an ordinary continuing fork at 1,001 / 900 / 101 against a 1,000 / 900 / 100 parent baseline reports only 1 / 0 / 1.

AI assistance

Codex assisted with investigation, implementation, tests, review, and proof preparation.

@harjothkhara
harjothkhara force-pushed the codex/fix-2193-restarted-fork-usage branch from 0dbfc16 to 4104822 Compare July 16, 2026 02:32
@harjothkhara harjothkhara changed the title Fix restarted Codex subagent usage accounting Fix restarted Codex fork usage accounting Jul 16, 2026
@harjothkhara
harjothkhara force-pushed the codex/fix-2193-restarted-fork-usage branch 3 times, most recently from 7d1cba7 to 9986f43 Compare July 16, 2026 03:16
@harjothkhara
harjothkhara force-pushed the codex/fix-2193-restarted-fork-usage branch from 9986f43 to 507bd7e Compare July 16, 2026 03:49
@harjothkhara harjothkhara changed the title Fix restarted Codex fork usage accounting Fix restarted Codex subagent usage accounting Jul 16, 2026
@harjothkhara
harjothkhara marked this pull request as ready for review July 16, 2026 04:25
@steipete
steipete merged commit 8489602 into steipete:main Jul 16, 2026
7 checks passed
@steipete

Copy link
Copy Markdown
Owner

Maintainer closeout on 59a18fcc4e3729560f2e02ee701ef5202b1fd631:

  • Confirmed the fix uses explicit Codex source.subagent provenance and preserves parent-baseline subtraction for ordinary forks.
  • make check: passed, including current parser hash, SwiftFormat, and SwiftLint with 0 violations.
  • swift test --filter CostUsageScannerBreakdownTests: 87/87 passed.
  • Synthetic black-box CodexBarCLI cost proof: a restarted subagent contributed its full +50 input / +10 cache-read / +5 output; an ordinary continuing fork contributed only that same post-fork delta; a warm-cache repeat was identical. No credentials, browser data, or Keychain access used.
  • Hardened session identity selection so multi-agent rollouts use their distinct thread id, not their shared tree session_id; both fast and fallback parsers remain aligned.
  • Structured autoreview: original branch and the final identity-hardening commit both clean, no accepted/actionable findings.
  • Added the missing contributor credit for @harjothkhara to the changelog entry.
  • Exact-head CI: all required Linux, macOS, lint, aggregate, and security checks passed.

Merged as 8489602f84ca278a4cb6944a97cca009b4412a87. Thanks @harjothkhara and @qiuruiyu!

@hhh2210

hhh2210 commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

This merge regresses the copied-parent-prefix source.subagent layout already documented in #2108.

I froze 2,010 local JSONL files and ran the same CodexBarCLI cost scan with empty isolated caches. Only the scanner revision changed:

Revision Jul 16 tokens Unknown
pre-#2208 e419a88d 289,150,407 4,100,938
#2208 8489602f 4,059,888,540 886,306,282
after #2118 2acf34cd 4,059,888,540 886,306,282

A warm repeat on the #2208 cache was identical. #2118 cannot repair this because #2208's subagent branch bypasses the parent resolver.

One representative child starts with a leaf session_meta marked source.subagent, followed by the parent session_meta and 1,845 copied token_count rows stamped at the spawn second. That prefix grows from 32,584 to 298,905,426 tokens. Only 20 later rows belong to the child and grow the counter to 300,571,631.

A conservative same-spawn-second check across 38 Jul 16 subagent rollouts finds 18 non-empty copied prefixes totaling 3,527,023,205 tokens. Their later growth totals 387,607,038 tokens. The Unknown spike has the same cause: copied parent rows arrive before the leaf turn_context supplies its model.

The unconditional branch in CostUsageScanner.swift that treats every source.subagent file as an independent counter is the problem. source.subagent is provenance, not proof of counter semantics. The synthetic fixture in #2208 covers reset-only children, while #2108 already documents the other real protocol shape.

The follow-up needs phase-aware accounting:

Please do not ship the current scanner state. Unless a patch is already in flight, I will take the follow-up and provide a real-shape fixture plus frozen-corpus proof.

Refs #2108 and #2037.

@hhh2210

hhh2210 commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Follow-up implemented in #2228.

The patch keeps #2208's reset-only behavior, but moves the decision to a rollout-shape classifier. A subagent with only its leaf metadata owns an independent counter; a subagent with embedded ancestor metadata is a copied-prefix rollout. For the copied shape, the scanner either starts at the verified local leaf marker or retains the parent-baseline fallback.

That removes the unconditional source.subagent assumption without reverting the valid #2193 fix. On the frozen 2,012-file corpus, Jul 16 falls from 4,086,288,236 tokens / 886,306,282 Unknown on current main to 231,475,556 / 0 Unknown. Cold, warm, and forced scans are identical.

The PR also keeps #1042's producer-hash contract: parser semantics rotate the generated hash; codex-vN remains reserved for incompatible cache schema/layout changes.

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.

3 participants