Skip to content

Commit a5cf26d

Browse files
Sophclaude
andcommitted
fix(checkpoint): carry subagent tokens through to committed checkpoints
Committed checkpoints reported "subagent_tokens": null even for sessions that ran many subagents — 0 of 30 sampled real checkpoints carried one. Two independent drops: 1. Condensation recomputes token usage from the transcript with subagentsDir="", which by contract leaves SubagentTokens nil, and that recomputed value replaced what SaveStep had produced. The session-wide cumulative survived on state.TokenUsage (so `entire status` still showed subagent usage), but the value written to checkpoint metadata lost it. 2. The store's aggregateTokenUsage, which sums a checkpoint's sessions into the root metadata.json, copied only the five scalar fields — so even a per-session metadata carrying SubagentTokens produced a null total at the root. For (1), fill from state.CheckpointTokenUsage.SubagentTokens: SaveStep already rescoped it to this window (cumulative minus SubagentTokensBaseline), so checkpoints stay summable instead of each re-reporting the session total, and we avoid re-reading subagent transcripts that may already be cleaned up. The fill copies rather than mutates — state.TokenUsage can alias that struct after applyBackfilledSessionTokenUsage adopts it (Copilot CLI), and mutating would overwrite the cumulative with a window delta, making resetCheckpointWindow snapshot a too-small baseline. It also runs after that helper, which must see the recomputed usage without the fill. For (2), recurse into SubagentTokens. Summing is correct at that level: it aggregates across the sessions of one checkpoint, and each session's value is already checkpoint-scoped. (The replace-don't-add rule applies to accumulating steps within a session, where each step re-reports a cumulative snapshot.) Replaces the two `//TODO: why do we not use here subagents dir?` markers with the actual reason. Known gap, now documented: a mid-turn commit that condenses before any SaveStep in the window has no CheckpointTokenUsage to draw on and still records no subagent tokens. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 01KZP6NJ9ZC864YR854AQ84Q29
1 parent b9638c0 commit a5cf26d

5 files changed

Lines changed: 328 additions & 5 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ The manual-commit strategy (`manual_commit*.go`) does not modify the active bran
676676
- Builds git trees in-memory using go-git plumbing APIs
677677
- Rewind restores files from shadow branch commit tree (does not use `git reset`)
678678
- **Location-independent transcript resolution** - transcript paths are always computed dynamically from the current repo location (via `agent.GetSessionDir` + `agent.ResolveSessionFile`), never stored in checkpoint metadata. This ensures restore/rewind works after repo relocation or across machines.
679-
- **Token usage scoping** - `SessionState.TokenUsage` is the session-wide total used by `entire status`; `SessionState.CheckpointTokenUsage` is the pending checkpoint delta since the last condensation. Checkpoint metadata must stay scoped to `CheckpointTranscriptStart` or the pending checkpoint delta. Cursor tokens come only from stop-hook payloads, while Copilot CLI can also backfill full-session totals from `session.shutdown`.
679+
- **Token usage scoping** - `SessionState.TokenUsage` is the session-wide total used by `entire status`; `SessionState.CheckpointTokenUsage` is the pending checkpoint delta since the last condensation. Checkpoint metadata must stay scoped to `CheckpointTranscriptStart` or the pending checkpoint delta. Cursor tokens come only from stop-hook payloads, while Copilot CLI can also backfill full-session totals from `session.shutdown`. Subagent tokens need two extra steps to reach a committed checkpoint, because condensation recomputes usage with `subagentsDir=""` (which leaves `SubagentTokens` nil by contract): `withCheckpointSubagentTokens` fills the already-rescoped window total from `state.CheckpointTokenUsage` onto the metadata value — copying, not mutating, since `state.TokenUsage` can alias it for Copilot CLI, and running *after* `applyBackfilledSessionTokenUsage`, which must see the recomputed usage without it — and the store's `aggregateTokenUsage` sums `SubagentTokens` across a checkpoint's sessions for the root `metadata.json`. Summing is right there (each session's value is already checkpoint-scoped), unlike the replace-don't-add rule for steps *within* a session. Remaining gap: a mid-turn commit that condenses before any `SaveStep` in the window has no `CheckpointTokenUsage` to draw on and records no subagent tokens.
680680
- Tracks session state in `.git/entire-sessions/` (shared across worktrees)
681681
- **Shadow branch migration** - if user does stash/pull/rebase (HEAD changes without commit), shadow branch is automatically moved to new base commit
682682
- **Orphaned branch cleanup** - if a shadow branch exists without a corresponding session state file, it is automatically reset when a new session starts
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package checkpoint
2+
3+
import (
4+
"testing"
5+
6+
"github.com/entireio/cli/cmd/entire/cli/agent"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
// TestAggregateTokenUsage_SumsSubagentTokens pins the nested SubagentTokens into
11+
// the root CheckpointSummary. The aggregation copied only the five scalar fields,
12+
// so a checkpoint's root metadata.json reported "subagent_tokens": null even when
13+
// its per-session metadata carried one.
14+
//
15+
// Summing is correct here: this aggregates across the *sessions* of one checkpoint,
16+
// and each session's SubagentTokens is already that session's checkpoint-scoped
17+
// total. (The replace-don't-add rule applies to accumulating steps within a
18+
// session, where each step re-reports a cumulative snapshot.)
19+
func TestAggregateTokenUsage_SumsSubagentTokens(t *testing.T) {
20+
t.Parallel()
21+
22+
t.Run("single session", func(t *testing.T) {
23+
t.Parallel()
24+
got := aggregateTokenUsage(nil, &agent.TokenUsage{
25+
InputTokens: 300, OutputTokens: 150, APICallCount: 2,
26+
SubagentTokens: &agent.TokenUsage{InputTokens: 500, OutputTokens: 250, APICallCount: 5},
27+
})
28+
require.NotNil(t, got.SubagentTokens)
29+
require.Equal(t, 500, got.SubagentTokens.InputTokens)
30+
require.Equal(t, 250, got.SubagentTokens.OutputTokens)
31+
require.Equal(t, 5, got.SubagentTokens.APICallCount)
32+
})
33+
34+
t.Run("two sessions both with subagents", func(t *testing.T) {
35+
t.Parallel()
36+
a := &agent.TokenUsage{
37+
InputTokens: 300, OutputTokens: 150,
38+
SubagentTokens: &agent.TokenUsage{InputTokens: 500, OutputTokens: 250, APICallCount: 5},
39+
}
40+
b := &agent.TokenUsage{
41+
InputTokens: 100, OutputTokens: 40,
42+
SubagentTokens: &agent.TokenUsage{InputTokens: 20, OutputTokens: 10, APICallCount: 1},
43+
}
44+
got := aggregateTokenUsage(a, b)
45+
require.Equal(t, 400, got.InputTokens)
46+
require.Equal(t, 190, got.OutputTokens)
47+
require.NotNil(t, got.SubagentTokens)
48+
require.Equal(t, 520, got.SubagentTokens.InputTokens)
49+
require.Equal(t, 260, got.SubagentTokens.OutputTokens)
50+
require.Equal(t, 6, got.SubagentTokens.APICallCount)
51+
})
52+
53+
t.Run("only one session has subagents", func(t *testing.T) {
54+
t.Parallel()
55+
got := aggregateTokenUsage(
56+
&agent.TokenUsage{InputTokens: 300},
57+
&agent.TokenUsage{InputTokens: 100, SubagentTokens: &agent.TokenUsage{InputTokens: 20}},
58+
)
59+
require.NotNil(t, got.SubagentTokens)
60+
require.Equal(t, 20, got.SubagentTokens.InputTokens)
61+
})
62+
63+
t.Run("no subagents stays nil", func(t *testing.T) {
64+
t.Parallel()
65+
got := aggregateTokenUsage(&agent.TokenUsage{InputTokens: 300}, &agent.TokenUsage{InputTokens: 100})
66+
require.Nil(t, got.SubagentTokens, "must not synthesize an empty subagent total")
67+
})
68+
69+
t.Run("both nil", func(t *testing.T) {
70+
t.Parallel()
71+
require.Nil(t, aggregateTokenUsage(nil, nil))
72+
})
73+
}

cmd/entire/cli/checkpoint/persistent.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,8 +1002,17 @@ func (s *treeWriter) readSummaryFromBlob(hash plumbing.Hash) (*CheckpointSummary
10021002
return readJSONFromBlob[CheckpointSummary](s.repo, hash)
10031003
}
10041004

1005-
// aggregateTokenUsage sums two TokenUsage structs.
1006-
// Returns nil if both inputs are nil.
1005+
// aggregateTokenUsage sums two TokenUsage structs, including their nested
1006+
// SubagentTokens. Returns nil if both inputs are nil.
1007+
//
1008+
// This sums across the *sessions* of one checkpoint, where each session's
1009+
// SubagentTokens is that session's checkpoint-scoped total, so adding them is
1010+
// correct. (The replace-don't-add rule for SubagentTokens applies to accumulating
1011+
// steps *within* a session, where each step re-reports a cumulative snapshot —
1012+
// see accumulateTokenUsage in the strategy package.)
1013+
//
1014+
// Dropping the nested total here left the root metadata.json reporting
1015+
// "subagent_tokens": null even when the per-session metadata carried one.
10071016
func aggregateTokenUsage(a, b *agent.TokenUsage) *agent.TokenUsage {
10081017
if a == nil && b == nil {
10091018
return nil
@@ -1023,9 +1032,17 @@ func aggregateTokenUsage(a, b *agent.TokenUsage) *agent.TokenUsage {
10231032
result.OutputTokens += b.OutputTokens
10241033
result.APICallCount += b.APICallCount
10251034
}
1035+
result.SubagentTokens = aggregateTokenUsage(subagentTokensOf(a), subagentTokensOf(b))
10261036
return result
10271037
}
10281038

1039+
func subagentTokensOf(usage *agent.TokenUsage) *agent.TokenUsage {
1040+
if usage == nil {
1041+
return nil
1042+
}
1043+
return usage.SubagentTokens
1044+
}
1045+
10291046
// SanitizeTranscriptForAgentType strips non-portable agent state from a transcript
10301047
// about to be stored (see agent.TranscriptSanitizer). It exists for callers that work
10311048
// from a types.AgentType rather than a live agent.Agent: the store itself, as a
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package strategy
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"os"
7+
"path/filepath"
8+
"testing"
9+
10+
"github.com/entireio/cli/cmd/entire/cli/agent"
11+
"github.com/entireio/cli/cmd/entire/cli/checkpoint"
12+
"github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
13+
"github.com/entireio/cli/cmd/entire/cli/paths"
14+
"github.com/go-git/go-git/v6"
15+
"github.com/go-git/go-git/v6/plumbing"
16+
"github.com/stretchr/testify/require"
17+
)
18+
19+
// TestCondenseSession_CommittedCheckpointCarriesSubagentTokens pins the
20+
// checkpoint-scoped subagent total onto the committed checkpoint's metadata.
21+
//
22+
// Condensation recomputes token usage from the transcript with subagentsDir="",
23+
// which by contract leaves SubagentTokens nil, and that recomputed value overwrote
24+
// the checkpoint-scoped total SaveStep had already rescoped into
25+
// state.CheckpointTokenUsage. Result: committed checkpoints reported
26+
// "subagent_tokens": null even for sessions that ran many subagents (0 of 30
27+
// sampled real checkpoints carried one).
28+
func TestCondenseSession_CommittedCheckpointCarriesSubagentTokens(t *testing.T) {
29+
dir := setupGitRepo(t)
30+
t.Chdir(dir)
31+
32+
repo, err := git.PlainOpen(dir)
33+
require.NoError(t, err)
34+
35+
ctx := context.Background()
36+
s := &ManualCommitStrategy{}
37+
sessionID := "2026-08-10-condense-subagent-tokens"
38+
39+
metadataDir := ".entire/metadata/" + sessionID
40+
metadataDirAbs := filepath.Join(dir, metadataDir)
41+
require.NoError(t, os.MkdirAll(metadataDirAbs, 0o755))
42+
43+
// The assistant line carries real usage, so the transcript recompute fires and
44+
// produces main-agent tokens with no SubagentTokens — the overwrite this test
45+
// guards against.
46+
transcript := `{"type":"human","message":{"content":"delegate to a subagent"}}
47+
{"type":"assistant","uuid":"a1","message":{"id":"m1","usage":{"input_tokens":300,"output_tokens":150}}}
48+
`
49+
require.NoError(t, os.WriteFile(filepath.Join(metadataDirAbs, paths.TranscriptFileName), []byte(transcript), 0o644))
50+
require.NoError(t, os.WriteFile(filepath.Join(dir, "test.txt"), []byte("agent-modified"), 0o644))
51+
52+
require.NoError(t, s.SaveStep(ctx, StepContext{
53+
SessionID: sessionID,
54+
MetadataDir: metadataDir,
55+
MetadataDirAbs: metadataDirAbs,
56+
ModifiedFiles: []string{"test.txt"},
57+
CommitMessage: "checkpoint 1",
58+
AuthorName: "Test",
59+
AuthorEmail: "test@test.com",
60+
AgentType: agent.AgentTypeClaudeCode,
61+
TokenUsage: &agent.TokenUsage{
62+
InputTokens: 100, OutputTokens: 50, APICallCount: 1,
63+
SubagentTokens: &agent.TokenUsage{InputTokens: 500, OutputTokens: 250, APICallCount: 5},
64+
},
65+
}))
66+
67+
state, err := s.loadSessionState(ctx, sessionID)
68+
require.NoError(t, err)
69+
require.NotNil(t, state.CheckpointTokenUsage.SubagentTokens,
70+
"precondition: SaveStep records the checkpoint-scoped subagent total")
71+
72+
checkpointID := id.MustCheckpointID("aabbccdd3344")
73+
result, err := s.CondenseSession(ctx, repo, checkpointID, state, nil)
74+
require.NoError(t, err)
75+
require.False(t, result.Skipped, "condensation must not skip when files are touched")
76+
77+
summary := readCheckpointSummary(t, repo, checkpointID)
78+
require.NotNil(t, summary.TokenUsage, "committed checkpoint must carry token usage")
79+
require.NotNil(t, summary.TokenUsage.SubagentTokens,
80+
"committed checkpoint must carry the checkpoint-scoped subagent total")
81+
require.Equal(t, 500, summary.TokenUsage.SubagentTokens.InputTokens)
82+
require.Equal(t, 250, summary.TokenUsage.SubagentTokens.OutputTokens)
83+
require.Equal(t, 5, summary.TokenUsage.SubagentTokens.APICallCount)
84+
85+
// The session-wide cumulative must survive the fill untouched. The fill copies
86+
// rather than mutates precisely because state.TokenUsage can alias the
87+
// checkpoint usage (applyBackfilledSessionTokenUsage adopts it for Copilot CLI);
88+
// mutating in place would overwrite the cumulative with this window's delta and
89+
// make resetCheckpointWindow snapshot a too-small baseline for the next window.
90+
require.NotNil(t, state.TokenUsage.SubagentTokens,
91+
"session-wide cumulative subagent total must survive condensation")
92+
require.Equal(t, 500, state.TokenUsage.SubagentTokens.InputTokens)
93+
require.Equal(t, 250, state.TokenUsage.SubagentTokens.OutputTokens)
94+
}
95+
96+
// TestCondenseSession_SubagentTokensStayScopedToCheckpointWindow covers the second
97+
// checkpoint of a session: the committed value must be that window's delta, not the
98+
// session-wide cumulative, so summing checkpoints does not over-count.
99+
func TestCondenseSession_SubagentTokensStayScopedToCheckpointWindow(t *testing.T) {
100+
dir := setupGitRepo(t)
101+
t.Chdir(dir)
102+
103+
repo, err := git.PlainOpen(dir)
104+
require.NoError(t, err)
105+
106+
ctx := context.Background()
107+
s := &ManualCommitStrategy{}
108+
sessionID := "2026-08-10-condense-subagent-window"
109+
110+
metadataDir := ".entire/metadata/" + sessionID
111+
metadataDirAbs := filepath.Join(dir, metadataDir)
112+
require.NoError(t, os.MkdirAll(metadataDirAbs, 0o755))
113+
transcript := `{"type":"human","message":{"content":"delegate again"}}
114+
{"type":"assistant","uuid":"a1","message":{"id":"m1","usage":{"input_tokens":300,"output_tokens":150}}}
115+
`
116+
require.NoError(t, os.WriteFile(filepath.Join(metadataDirAbs, paths.TranscriptFileName), []byte(transcript), 0o644))
117+
118+
saveStep := func(commitMsg, content string, subagent *agent.TokenUsage) {
119+
require.NoError(t, os.WriteFile(filepath.Join(dir, "test.txt"), []byte(content), 0o644))
120+
require.NoError(t, s.SaveStep(ctx, StepContext{
121+
SessionID: sessionID,
122+
MetadataDir: metadataDir,
123+
MetadataDirAbs: metadataDirAbs,
124+
ModifiedFiles: []string{"test.txt"},
125+
CommitMessage: commitMsg,
126+
AuthorName: "Test",
127+
AuthorEmail: "test@test.com",
128+
AgentType: agent.AgentTypeClaudeCode,
129+
TokenUsage: &agent.TokenUsage{
130+
InputTokens: 100, OutputTokens: 50, APICallCount: 1,
131+
SubagentTokens: subagent,
132+
},
133+
}))
134+
}
135+
136+
// Checkpoint 1: subagent cumulative 500/250. Condense through
137+
// CondenseSessionByID so the real reset path runs and snapshots the baseline —
138+
// CondenseSession alone does not reset the window (its callers do).
139+
saveStep("checkpoint 1", "v2", &agent.TokenUsage{InputTokens: 500, OutputTokens: 250, APICallCount: 5})
140+
require.NoError(t, s.CondenseSessionByID(ctx, sessionID))
141+
142+
stateReset, err := s.loadSessionState(ctx, sessionID)
143+
require.NoError(t, err)
144+
require.NotNil(t, stateReset.SubagentTokensBaseline, "precondition: reset snapshots the baseline")
145+
require.Equal(t, 500, stateReset.SubagentTokensBaseline.InputTokens)
146+
147+
// Checkpoint 2: the subagent grew to 620/310 cumulative — a 120/60 delta.
148+
saveStep("checkpoint 2", "v3", &agent.TokenUsage{InputTokens: 620, OutputTokens: 310, APICallCount: 6})
149+
state2, err := s.loadSessionState(ctx, sessionID)
150+
require.NoError(t, err)
151+
secondID := id.MustCheckpointID("aabbccdd7788")
152+
_, err = s.CondenseSession(ctx, repo, secondID, state2, nil)
153+
require.NoError(t, err)
154+
155+
summary := readCheckpointSummary(t, repo, secondID)
156+
require.NotNil(t, summary.TokenUsage)
157+
require.NotNil(t, summary.TokenUsage.SubagentTokens)
158+
require.Equal(t, 120, summary.TokenUsage.SubagentTokens.InputTokens,
159+
"second checkpoint must carry its window's delta, not the session cumulative")
160+
require.Equal(t, 60, summary.TokenUsage.SubagentTokens.OutputTokens)
161+
}
162+
163+
// readCheckpointSummary reads a committed checkpoint's root CheckpointSummary off
164+
// the metadata branch.
165+
func readCheckpointSummary(t *testing.T, repo *git.Repository, checkpointID id.CheckpointID) checkpoint.CheckpointSummary {
166+
t.Helper()
167+
168+
ref, err := repo.Reference(plumbing.NewBranchReferenceName(paths.MetadataBranchName), true)
169+
require.NoError(t, err)
170+
commit, err := repo.CommitObject(ref.Hash())
171+
require.NoError(t, err)
172+
tree, err := commit.Tree()
173+
require.NoError(t, err)
174+
checkpointTree, err := tree.Tree(checkpointID.Path())
175+
require.NoError(t, err)
176+
rootMeta, err := checkpointTree.File(paths.MetadataFileName)
177+
require.NoError(t, err)
178+
rootBytes, err := rootMeta.Contents()
179+
require.NoError(t, err)
180+
181+
var summary checkpoint.CheckpointSummary
182+
require.NoError(t, json.Unmarshal([]byte(rootBytes), &summary))
183+
return summary
184+
}

cmd/entire/cli/strategy/manual_commit_condensation.go

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,11 @@ func (s *ManualCommitStrategy) CondenseSession(ctx context.Context, repo *git.Re
306306
sessionData.TokenUsage = accumulateTokenUsage(nil, state.CheckpointTokenUsage)
307307
}
308308

309+
// Restore the checkpoint-scoped subagent total onto the value written to
310+
// metadata. This must run after applyBackfilledSessionTokenUsage, which needs
311+
// to see the recomputed usage without it (see that function).
312+
sessionData.TokenUsage = withCheckpointSubagentTokens(sessionData.TokenUsage, state.CheckpointTokenUsage)
313+
309314
// Backfill the model from the transcript for agents that don't report it via
310315
// hooks (e.g., Pi records message.model but its hook events carry no model
311316
// field). Only fills when the model is otherwise unknown — hook-reported
@@ -764,6 +769,44 @@ func hasTokenUsageData(usage *agent.TokenUsage) bool {
764769
return hasTokenUsageData(usage.SubagentTokens)
765770
}
766771

772+
// withCheckpointSubagentTokens returns usage carrying the checkpoint-scoped
773+
// subagent total from checkpointUsage, filling it in only when usage has none of
774+
// its own.
775+
//
776+
// Condensation recomputes token usage from the transcript with subagentsDir=""
777+
// (see extractSessionData), which by contract leaves SubagentTokens nil, and that
778+
// recomputed value replaces what SaveStep produced. Without this fill, committed
779+
// checkpoints reported "subagent_tokens": null even for sessions that ran many
780+
// subagents, while `entire status` still showed them — the session-wide cumulative
781+
// survives on state.TokenUsage via applyBackfilledSessionTokenUsage.
782+
//
783+
// state.CheckpointTokenUsage.SubagentTokens is the right source: SaveStep already
784+
// rescoped it to this window (cumulative minus SubagentTokensBaseline), so
785+
// checkpoints stay summable instead of each re-reporting the session total. That
786+
// also avoids re-reading subagent transcripts here, which would return a cumulative
787+
// snapshot needing the same rescoping — and would fail outright once the agent has
788+
// cleaned the transcripts up.
789+
//
790+
// Returns a copy and never mutates either input: applyBackfilledSessionTokenUsage
791+
// can adopt the checkpoint usage as state.TokenUsage (Copilot CLI), so mutating in
792+
// place would overwrite the session-wide cumulative with a window delta and make
793+
// resetCheckpointWindow snapshot a too-small baseline.
794+
//
795+
// Known gap: a mid-turn commit that condenses before any SaveStep in the window has
796+
// no state.CheckpointTokenUsage to draw on, so that checkpoint still records no
797+
// subagent tokens.
798+
func withCheckpointSubagentTokens(usage, checkpointUsage *agent.TokenUsage) *agent.TokenUsage {
799+
if usage == nil || usage.SubagentTokens != nil {
800+
return usage
801+
}
802+
if checkpointUsage == nil || checkpointUsage.SubagentTokens == nil {
803+
return usage
804+
}
805+
filled := *usage
806+
filled.SubagentTokens = checkpointUsage.SubagentTokens
807+
return &filled
808+
}
809+
767810
// applyBackfilledSessionTokenUsage overwrites state.TokenUsage with the
768811
// transcript-recomputed session total (see sessionStateBackfillTokenUsage) when
769812
// one is available, preserving the cumulative subagent total across the backfill.
@@ -1082,7 +1125,12 @@ func (s *ManualCommitStrategy) extractSessionData(ctx context.Context, repo *git
10821125
// extract them from offset 0; consumers can filter by checkpoint_transcript_start
10831126
// if they only render the checkpoint-scoped slice.
10841127
if len(data.Transcript) > 0 {
1085-
data.TokenUsage = agent.CalculateTokenUsage(ctx, ag, data.Transcript, checkpointTranscriptStart, "") //TODO: why do we not use here subagents dir?
1128+
// subagentsDir="" on purpose: re-reading subagent transcripts here yields a
1129+
// cumulative-since-session-start snapshot that would still need rescoping
1130+
// against SubagentTokensBaseline, and it finds nothing once the agent has
1131+
// cleaned them up. CondenseSession fills the already-rescoped window total
1132+
// in instead — see withCheckpointSubagentTokens.
1133+
data.TokenUsage = agent.CalculateTokenUsage(ctx, ag, data.Transcript, checkpointTranscriptStart, "")
10861134
data.SkillEvents = agent.ExtractSkillEvents(ctx, ag, data.Transcript, 0)
10871135
}
10881136

@@ -1124,7 +1172,8 @@ func (s *ManualCommitStrategy) extractSessionDataFromLiveTranscript(ctx context.
11241172
// extract them from offset 0; consumers can filter by checkpoint_transcript_start
11251173
// if they only render the checkpoint-scoped slice.
11261174
if len(data.Transcript) > 0 {
1127-
data.TokenUsage = agent.CalculateTokenUsage(ctx, ag, data.Transcript, state.CheckpointTranscriptStart, "") //TODO: why do we not use here subagents dir?
1175+
// subagentsDir="" for the same reason as extractSessionData above.
1176+
data.TokenUsage = agent.CalculateTokenUsage(ctx, ag, data.Transcript, state.CheckpointTranscriptStart, "")
11281177
data.SkillEvents = agent.ExtractSkillEvents(ctx, ag, data.Transcript, 0)
11291178
}
11301179

0 commit comments

Comments
 (0)