fix(strategy): preserve cumulative subagent baseline across condensation backfill · Entire
fix(strategy): preserve cumulative subagent baseline across condensation backfill
b6d4281→main·
suhaanthayyil·3d ago·2 files·+45 added/-5 removed
During condensation, sessionStateBackfillTokenUsage overwrote state.TokenUsage with a value recomputed from the transcript with subagentsDir="", which drops SubagentTokens. resetCheckpointWindow then captured the next window's baseline from state.TokenUsage.SubagentTokens — now nil — so the next checkpoint re-reported the full cumulative subagent total. This fired on every real condensation (Claude Code transcripts always carry usage lines, so the backfill's InputTokens>0 branch always ran).
Preserve the authoritative cumulative subagent total (accumulated by SaveStep) across the backfill, folded onto a copy so it is never mixed into the checkpoint-scoped value written to metadata. The regression test now uses a usage-bearing transcript so it exercises the real backfill path instead of passing vacuously.
Changes
2
cmd/entire/cli/strategy
Mmanual_commit_condensation.go+35/-4
Msubagent_token_dedup_test.go+10/-1
262 unmodified lines
263
264
265
266
267
268
269
266
267
268
269
270
271
272
273
460 unmodified lines
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
262 unmodified lines
// Backfill session state token usage from the freshly-extracted transcript.
// Copilot CLI writes session.shutdown after the hooks return, so by condensation
// time we can recover the authoritative full-session total from the transcript
// while keeping checkpoint metadata scoped to CheckpointTranscriptStart.
if backfillUsage := sessionStateBackfillTokenUsage(ctx, ag, state.AgentType, sessionData.Transcript, sessionData.TokenUsage); backfillUsage != nil {
state.TokenUsage = backfillUsage
}
// while keeping checkpoint metadata scoped to CheckpointTranscriptStart. The
// recompute drops SubagentTokens (subagentsDir=""); the helper preserves the
// cumulative subagent total across the backfill so resetCheckpointWindow's
// baseline does not regress to nil (finding 019f5ebf-a57e).
applyBackfilledSessionTokenUsage(ctx, ag, state, sessionData.Transcript, sessionData.TokenUsage)
if !hasTokenUsageData(sessionData.TokenUsage) && hasTokenUsageData(state.CheckpointTokenUsage) {
sessionData.TokenUsage = accumulateTokenUsage(nil, state.CheckpointTokenUsage)
460 unmodified lines
return hasTokenUsageData(usage.SubagentTokens)
}
// applyBackfilledSessionTokenUsage overwrites state.TokenUsage with the
// transcript-recomputed session total (see sessionStateBackfillTokenUsage) when
// one is available, preserving the cumulative subagent total across the backfill.
//
// The recompute runs with subagentsDir="" (see extractSessionData), so the
// backfilled usage never carries SubagentTokens, whereas state.TokenUsage holds
// the authoritative cumulative subagent total accumulated by SaveStep.
// resetCheckpointWindow captures the next window's baseline from
// state.TokenUsage.SubagentTokens after CondenseSession returns, so letting the
// backfill drop it would make the baseline nil and the next checkpoint re-report
// the full cumulative subagent total. The cumulative is folded onto a copy so it
// is never mixed into checkpointUsage, which is the checkpoint-scoped value
// written to metadata.
func applyBackfilledSessionTokenUsage(ctx context.Context, ag agent.Agent, state *SessionState, transcript []byte, checkpointUsage *agent.TokenUsage) {
backfillUsage := sessionStateBackfillTokenUsage(ctx, ag, state.AgentType, transcript, checkpointUsage)
if backfillUsage == nil {
return
}
var priorSubagentTokens *agent.TokenUsage
if state.TokenUsage != nil {
priorSubagentTokens = state.TokenUsage.SubagentTokens
}
if backfillUsage.SubagentTokens == nil && priorSubagentTokens != nil {
preserved := *backfillUsage
preserved.SubagentTokens = priorSubagentTokens
backfillUsage = &preserved
}
state.TokenUsage = backfillUsage
}
// sessionStateBackfillTokenUsage returns the best session-level token usage to
// persist in session state after condensation.
func sessionStateBackfillTokenUsage(ctx context.Context, ag agent.Agent, agentType types.AgentType, transcript []byte, checkpointUsage *agent.TokenUsage) *agent.TokenUsage {
Mcmd/entire/cli/strategy/manual_commit_condensation.go+35/-4
441 unmodified lines
442
443
444
445
446
447
448
449
450
451
452
453
454
446
455
456
457
458
441 unmodified lines
metadataDir := ".entire/metadata/" + sessionID
metadataDirAbs := filepath.Join(dir, metadataDir)
require.NoError(t, os.MkdirAll(metadataDirAbs, 0o755))
// The assistant line carries real usage data (message.id + usage). Real
// Claude Code transcripts always do, which makes sessionStateBackfillTokenUsage
// fire during condensation (its InputTokens > 0 branch) and overwrite
// state.TokenUsage with the transcript-recomputed value — which is computed
// with subagentsDir="" and therefore drops SubagentTokens. This is what makes
// this test guard the REAL condensation path: without preserving the
// cumulative subagent total across the backfill, resetCheckpointWindow would
// snapshot a nil baseline and the next checkpoint would re-report the full
// cumulative subagent total (finding 019f5ebf-a57e).
transcript := `{"type":"human","message":{"content":"do the thing"}}
{"type":"assistant","message":{"content":"working on it"}}
{"type":"assistant","uuid":"a1","message":{"id":"m1","usage":{"input_tokens":300,"output_tokens":150}}}
`
require.NoError(t, os.WriteFile(filepath.Join(metadataDirAbs, paths.TranscriptFileName), []byte(transcript), 0o644))
Mcmd/entire/cli/strategy/subagent_token_dedup_test.go+10/-1