checkpoint: extract committed contract to api/checkpoint · Entire
Log in
checkpoint: extract committed contract to api/checkpoint
b1a0aac·
Soph·4w ago·10 files·+706 added/-637 removed
Move the committed-checkpoint contract — the persisted document types (CommittedMetadata, CheckpointSummary, Summary, InitialAttribution, ...), the operation option types (WriteCommittedOptions/UpdateCommittedOptions/ PrecomputedTranscriptBlobs), the reader/writer interfaces, and the Write request union — into a new github.com/entireio/cli/api/checkpoint package.
The contract is now agent-free (it depends only on leaf packages: agent/types, checkpoint/id, redact, and go-git plumbing), so a storage backend can implement it without pulling in the CLI's agent/TUI/git machinery. This is the pluggable surface from #1433.
The git implementation (GitStore, Open, the Stores facade, ref resolution, and the git-only temporary/shadow-branch types) stays in cmd/entire/cli/checkpoint, which imports api/checkpoint and re-exports every moved symbol via aliases (aliases.go). All 65 existing importers compile unchanged — no call-site churn.
Notes: - PrecomputedTranscriptBlobs.isUsable is now exported (IsUsable) since the one caller is in the implementation package, across the new package boundary. - The Write union is now sealed to api/checkpoint: an unhandled WriteRequest can only be introduced there, so Write's default branch is a forward-safety net for future request types (the unknown-request unit test was removed as it can no longer be expressed from the impl package). - The api package imports the checkpoint/id leaf as-is; moving id under api/ can follow later if desired.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
Sessions
e8788a712424View transcript
?\ can you run simplify on each PR?Claude Code·Opus 4.8[1m]·3 steps
Changes
10
api/checkpoint
Adoc.go+11
Aerrors.go+12
Ainterfaces.go+127
Ametadata.go+485
cmd/entire/cli/checkpoint
Aaliases.go+53
Mcheckpoint.go+6/-487
Mcommitted.go+1/-1
Mcommitted_reader_resolve.go+3/-74
Mcommitted_write.go+3/-52
Mcommitted_write_test.go+5/-23
1
2
3
4
5
6
7
8
9
10
11
// Package checkpoint defines the committed-checkpoint storage contract: the
// persisted metadata documents, the option types, the reader/writer
// interfaces, and the Write request union.
//
// It is the pluggable surface from issue #1433: a storage backend implements
// these interfaces and operates on these types without depending on the CLI's
// agent, TUI, or git-implementation packages. The git-backed implementation
// (GitStore, Open, the shadow-branch/temporary machinery) lives in the
// cmd/entire/cli/checkpoint package, which imports this one and re-exports
// these types as aliases so existing CLI call sites are unaffected.
package checkpoint
Aapi/checkpoint/doc.go+11
1
2
3
4
5
6
7
8
9
10
11
12
package checkpoint
import "errors"
// Errors returned by committed checkpoint operations.
var (
// ErrCheckpointNotFound is returned when a checkpoint ID doesn't exist.
ErrCheckpointNotFound = errors.New("checkpoint not found")
// ErrNoTranscript is returned when a checkpoint exists but has no transcript.
ErrNoTranscript = errors.New("no transcript found for checkpoint")
)
Aapi/checkpoint/errors.go+12
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package checkpoint
import (
"context"
"fmt"
"github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
)
// CommittedReader provides read access to committed checkpoint data.
type CommittedReader interface {
ReadCommitted(ctx context.Context, checkpointID id.CheckpointID) (*CheckpointSummary, error)
ReadSessionContent(ctx context.Context, checkpointID id.CheckpointID, sessionIndex int) (*SessionContent, error)
}
// CommittedListReader provides read and list access to committed checkpoint data.
type CommittedListReader interface {
CommittedReader
ListCommitted(ctx context.Context) ([]CommittedInfo, error)
ReadSessionMetadata(ctx context.Context, checkpointID id.CheckpointID, sessionIndex int) (*CommittedMetadata, error)
ReadSessionPrompts(ctx context.Context, checkpointID id.CheckpointID, sessionIndex int) (string, error)
}
// CommittedStore provides the production committed checkpoint storage surface.
// Writes go through the unified Writer.Write(ctx, WriteRequest); the concrete
// per-operation methods (WriteCommitted/UpdateCommitted/...) remain on the
// git implementation as the methods Write dispatches to.
type CommittedStore interface {
CommittedListReader
ReadSessionMetadataAndPrompts(ctx context.Context, checkpointID id.CheckpointID, sessionIndex int) (*SessionContent, error)
Writer
}
// WriteRequest is a single committed-store write command. The set is closed:
// the only implementations are the request types below, sealed via the
// unexported isWriteRequest marker. A store dispatches on the concrete type;
// a mirror/fan-out store forwards the same value to each backend's Write.
//
// One Store.Write(ctx, req) entry point replaces the former four writer
// methods (WriteCommitted / UpdateCommitted / UpdateSummary /
// UpdateCheckpointSummary), so adding a write operation is a new request type
// plus one dispatch case — the Store interface stays unchanged and existing
// backends keep compiling.
type WriteRequest interface {
isWriteRequest()
}
// WriteSession creates or replaces a session document within a checkpoint,
// materializing the checkpoint on its first session. (Former WriteCommitted.)
type WriteSession WriteCommittedOptions
// BackfillTranscript replaces a session's transcript, prompts, and skill
// events at stop time without clobbering sibling fields. (Former UpdateCommitted.)
type BackfillTranscript UpdateCommittedOptions
// BackfillSummary rewrites only the summary of the checkpoint's latest
// session. (Former UpdateSummary.)
type BackfillSummary struct {
CheckpointID id.CheckpointID
Summary *Summary
}
// BackfillAttribution rewrites the checkpoint root's combined attribution.
// (Former UpdateCheckpointSummary.)
type BackfillAttribution struct {
CheckpointID id.CheckpointID
Attribution *InitialAttribution
}
func (WriteSession) isWriteRequest() {}
func (BackfillTranscript) isWriteRequest() {}
func (BackfillSummary) isWriteRequest() {}
func (BackfillAttribution) isWriteRequest() {}
// Writer is the committed-store write surface: a single Write that accepts any
// WriteRequest. It is the natural type for mirror fan-out.
type Writer interface {
Write(ctx context.Context, req WriteRequest) error
}
// ReadCommittedCheckpoint reads a committed checkpoint summary and normalizes
// a nil store response into ErrCheckpointNotFound.
func ReadCommittedCheckpoint(ctx context.Context, reader CommittedReader, checkpointID id.CheckpointID) (*CheckpointSummary, error) {
if err := ctx.Err(); err != nil {
return nil, err //nolint:wrapcheck // Propagating context cancellation
}
summary, err := reader.ReadCommitted(ctx, checkpointID)
if err != nil {
return nil, fmt.Errorf("read committed checkpoint: %w", err)
}
if summary == nil {
return nil, ErrCheckpointNotFound
}
return summary, nil
}
// ReadLatestSessionContent reads the latest session from an already-resolved
// committed reader and summary.
func ReadLatestSessionContent(ctx context.Context, reader CommittedReader, checkpointID id.CheckpointID, summary *CheckpointSummary) (*SessionContent, error) {
if summary == nil || len(summary.Sessions) == 0 {
return nil, ErrCheckpointNotFound
}
latestIndex := len(summary.Sessions) - 1
content, err := reader.ReadSessionContent(ctx, checkpointID, latestIndex)
if err != nil {
return nil, fmt.Errorf("read session %d content: %w", latestIndex, err)
}
return content, nil
}
func ReadRawSessionLogForCheckpoint(ctx context.Context, reader CommittedReader, checkpointID id.CheckpointID) ([]byte, string, error) {
if err := ctx.Err(); err != nil {
return nil, "", err //nolint:wrapcheck // Propagating context cancellation
}
summary, err := ReadCommittedCheckpoint(ctx, reader, checkpointID)
if err != nil {
return nil, "", err
}
content, err := ReadLatestSessionContent(ctx, reader, checkpointID, summary)
if err != nil {
return nil, "", err
}
return content.Transcript, content.Metadata.SessionID, nil
}
Aapi/checkpoint/interfaces.go+127
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
package checkpoint
import (
"encoding/json"
"time"
"github.com/entireio/cli/cmd/entire/cli/agent/types"
"github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
"github.com/entireio/cli/redact"
"github.com/go-git/go-git/v6/plumbing"
)
// WriteCommittedOptions contains options for writing a committed checkpoint.
type WriteCommittedOptions struct {
// CheckpointID is the stable 12-hex-char identifier
CheckpointID id.CheckpointID
// SessionID is the session identifier
SessionID string
// CreatedAt is when the checkpoint was originally created.
// When zero, writers use the current time.
CreatedAt time.Time
// Strategy is the name of the strategy that created this checkpoint
Strategy string
// Branch is the branch name where the checkpoint was created (empty if detached HEAD)
Branch string
// Transcript is the session transcript content (full.jsonl).
// Must be pre-redacted (via redact.JSONLBytes or redact.AlreadyRedacted for trusted sources).
Transcript redact.RedactedBytes
// Prompts contains the raw user prompts from the session. Run through
// redactedJoinedPrompts before persisting — the writer does this
// inside writeSessionToSubdirectory.
Prompts []string
// FilesTouched are files modified during the session
FilesTouched []string
// CheckpointsCount is the displayed "steps" count for this session: the number
// of user prompts attributed to this checkpoint (floored at 1). Despite the
// historical name/JSON tag, it is no longer a count of checkpoints.
CheckpointsCount int
// SaveStepCount is the number of SaveStep-recorded steps (shadow-branch
// commits) for this session. Distinct from CheckpointsCount (the displayed
// prompt count): this is the honest "did real checkpoint work happen" signal
// used to gate combined attribution. 0 means a commit-only / fallback session.
SaveStepCount int
// EphemeralBranch is the shadow branch name (for manual-commit strategy)
EphemeralBranch string
// AuthorName is the name to use for commits
AuthorName string
// AuthorEmail is the email to use for commits
AuthorEmail string
// MetadataDir is a directory containing additional metadata files to copy
// If set, all files in this directory will be copied to the checkpoint path
// This is useful for copying task metadata files, subagent transcripts, etc.
MetadataDir string
// Task checkpoint fields (for task/subagent checkpoints)
IsTask bool // Whether this is a task checkpoint
ToolUseID string // Tool use ID for task checkpoints
// Additional task checkpoint fields for subagent checkpoints
AgentID string // Subagent identifier
CheckpointUUID string // UUID for transcript truncation when rewinding
TranscriptPath string // Path to session transcript file (alternative to in-memory Transcript)
SubagentTranscriptPath string // Path to subagent's transcript file
// Incremental checkpoint fields
IsIncremental bool // Whether this is an incremental checkpoint
IncrementalSequence int // Checkpoint sequence number
IncrementalType string // Tool type that triggered this checkpoint
IncrementalData []byte // Tool input payload for this checkpoint
// Commit message fields (used for task checkpoints)
CommitSubject string // Subject line for the metadata commit (overrides default)
// Agent identifies the agent that created this checkpoint (e.g., "Claude Code", "Cursor")
Agent types.AgentType
// Model is the LLM model used during the session (e.g., "claude-sonnet-4-20250514")
Model string
// TurnID correlates checkpoints from the same agent turn.
TurnID string
// Transcript position at checkpoint start - tracks what was added during this checkpoint
TranscriptIdentifierAtStart string // Last identifier when checkpoint started (UUID for Claude, message ID for Gemini)
CheckpointTranscriptStart int // Transcript line offset at start of this checkpoint's data
// CheckpointTranscriptStart is written to both CommittedMetadata.CheckpointTranscriptStart
// and the deprecated CommittedMetadata.TranscriptLinesAtStart for backward compatibility.
// TokenUsage contains the token usage for this checkpoint
TokenUsage *types.TokenUsage
// SkillEvents records explicit native skill signals observed in this session.
SkillEvents []types.SkillEvent
// SessionMetrics contains hook-provided session metrics (duration, turns, context usage)
SessionMetrics *SessionMetrics
// InitialAttribution is line-level attribution calculated at commit time
// comparing checkpoint tree (agent work) to committed tree (may include human edits)
InitialAttribution *InitialAttribution
// PromptAttributionsJSON is the raw PromptAttributions data, JSON-encoded.
// Persisted for diagnostic purposes — shows exactly which prompt recorded
// which "user" lines, enabling root cause analysis of attribution bugs.
// Uses json.RawMessage to avoid importing session package.
PromptAttributionsJSON json.RawMessage
// CombinedAttribution is holistic attribution across all sessions.
// Used during migration to preserve v1 root summary attribution.
// During normal condensation this is nil (computed post-commit via UpdateCheckpointSummary).
CombinedAttribution *InitialAttribution
// Summary is an optional AI-generated summary for this checkpoint.
// This field may be nil when:
// - summarization is disabled in settings
// - summary generation failed (non-blocking, logged as warning)
// - the transcript was empty or too short to summarize
// - the checkpoint predates the summarization feature
Summary *Summary
// Kind identifies the session purpose (e.g., "agent_review"). Empty for normal sessions.
Kind string
// ReviewSkills is the snapshot of skills used (only meaningful when Kind is a review kind).
// May be empty when a review is attached post-hoc without declared skills.
ReviewSkills []string
// ReviewPrompt is the actual text of the review request (composed prompt
// for spawn, first user prompt for attach). Only meaningful when Kind is
// a review kind.
ReviewPrompt string
// HasReview is set by the caller when this session should mark its
// checkpoint as reviewed. The caller computes this (e.g. via
// session.Kind.IsReview) because checkpoint can't import session
// — the session package imports checkpoint, creating a cycle.
HasReview bool
// InvestigateRunID is the 12-hex-char ID of the parent investigation
// run (only meaningful when Kind is an investigate kind).
InvestigateRunID string
// InvestigateTopic is the human-readable topic the investigation was
// asked to investigate (only meaningful when Kind is an investigate
// kind).
InvestigateTopic string
// HasInvestigation is set by the caller when this session should mark
// its checkpoint as part of an investigation. The caller computes this
// (e.g. via session.Kind.IsInvestigate) because checkpoint can't import
// session — the session package imports checkpoint, creating a cycle.
HasInvestigation bool
}
// UpdateCommittedOptions contains options for updating an existing committed checkpoint.
// Uses replace semantics: the transcript and prompts are fully replaced,
// not appended. At stop time we have the complete session transcript and want every
// checkpoint to contain it identically.
type UpdateCommittedOptions struct {
// CheckpointID identifies the checkpoint to update
CheckpointID id.CheckpointID
// SessionID identifies which session slot to update within the checkpoint
SessionID string
// Transcript is the full session transcript (replaces existing).
// Must be pre-redacted (via redact.JSONLBytes or redact.AlreadyRedacted for trusted sources).
Transcript redact.RedactedBytes
// Prompts contains the raw user prompts (replaces existing).
// See WriteCommittedOptions.Prompts.
Prompts []string
// Agent identifies the agent type (needed for transcript chunking)
Agent types.AgentType
// SkillEvents replaces the session metadata skill_events when non-empty.
SkillEvents []types.SkillEvent
// PrecomputedBlobs, if non-nil, provides chunk blob hashes and the
// content-hash blob hash computed once for this transcript. When set,
// UpdateCommitted skips the per-call ChunkTranscript + zlib work and
// reuses these hashes. Used by finalizeAllTurnCheckpoints to avoid
// re-compressing identical content N times.
PrecomputedBlobs *PrecomputedTranscriptBlobs
}
// PrecomputedTranscriptBlobs holds blob hashes for a transcript that was
// chunked and written to the object store once, for reuse across multiple
// UpdateCommitted calls sharing the same transcript content.
// Callers should avoid constructing this for empty transcripts; agent.ChunkTranscript
// would otherwise produce a single zero-length chunk and a hash for an empty
// blob, which downstream stores would never reference.
type PrecomputedTranscriptBlobs struct {
// ChunkHashes are the blob hashes for each transcript chunk, in order.
// Always non-empty when built via PrecomputeTranscriptBlobs (a non-empty
// transcript chunks to at least one entry; callers should skip precompute
// for empty transcripts).
ChunkHashes []plumbing.Hash
// ContentHashBlob is the blob hash of the "sha256:<hex>" content-hash
// string for the transcript.
ContentHashBlob plumbing.Hash
// ContentHash is the "sha256:<hex>" string itself, so the short-circuit
// path can compare without re-reading the blob.
ContentHash string
}
// IsUsable reports whether the precomputed blobs satisfy the invariants that
// consumers depend on: a non-zero content-hash blob and at least one chunk
// hash. Callers should fall back to the fresh-write path when this is false.
func (p *PrecomputedTranscriptBlobs) IsUsable() bool {
return p != nil && !p.ContentHashBlob.IsZero() && len(p.ChunkHashes) > 0
}
// CommittedInfo contains summary information about a committed checkpoint.
type CommittedInfo struct {
// CheckpointID is the stable 12-hex-char identifier
CheckpointID id.CheckpointID
// SessionID is the session identifier (most recent session for multi-session checkpoints)
SessionID string
// CreatedAt is when the checkpoint was created
CreatedAt time.Time
// CheckpointsCount is the aggregate displayed "steps" count across sessions:
// the sum of per-session prompt-window counts. Despite the historical name,
// it is not a count of checkpoint records.
CheckpointsCount int
// FilesTouched are files modified during all sessions
FilesTouched []string
// Agent identifies the agent that created this checkpoint
Agent types.AgentType
// IsTask indicates if this is a task checkpoint
IsTask bool
// ToolUseID is the tool use ID for task checkpoints
ToolUseID string
// Multi-session support
SessionCount int // Number of sessions (1 if single session)
SessionIDs []string // All session IDs that contributed
}
// SessionContent contains the actual content for a session.
// This is used when reading full session data (transcript, prompts, context)
// as opposed to just the metadata/summary.
type SessionContent struct {
// Metadata contains the session-specific metadata
Metadata CommittedMetadata
// Transcript is the session transcript content
Transcript []byte
// TranscriptBlobHashes are the stored raw transcript blob hashes in chunk
// order. Callers that rewrite the same transcript under a different path can
// reuse these content-addressed blobs instead of storing duplicate blobs.
TranscriptBlobHashes []plumbing.Hash
// Prompts contains user prompts from this session
Prompts string
}
// CommittedMetadata contains the metadata stored in metadata.json for each checkpoint.
type CommittedMetadata struct {
CLIVersion string `json:"cli_version,omitempty"`
CheckpointID id.CheckpointID `json:"checkpoint_id"`
SessionID string `json:"session_id"`
Strategy string `json:"strategy"`
CreatedAt time.Time `json:"created_at"`
Branch string `json:"branch,omitempty"` // Branch where checkpoint was created (empty if detached HEAD)
CheckpointsCount int `json:"checkpoints_count"`
// SaveStepCount is the number of SaveStep-recorded steps for this session.
// Honest "real checkpoint work happened" signal (0 = commit-only/fallback
// session), kept separate from the displayed CheckpointsCount prompt count.
// Added after CheckpointsCount stopped being a reliable did-SaveStep-run signal.
SaveStepCount int `json:"save_step_count,omitempty"`
FilesTouched []string `json:"files_touched"`
// Agent identifies the agent that created this checkpoint (e.g., "Claude Code", "Cursor")
Agent types.AgentType `json:"agent,omitempty"`
// Model is the LLM model used during the session (e.g., "claude-sonnet-4-20250514").
// Always written to metadata (empty string when unknown) so consumers can rely on the field's presence.
Model string `json:"model"`
// TurnID correlates checkpoints from the same agent turn.
// When a turn's work spans multiple commits, each gets its own checkpoint
// but they share the same TurnID for future aggregation/deduplication.
TurnID string `json:"turn_id,omitempty"`
// Task checkpoint fields (only populated for task checkpoints)
IsTask bool `json:"is_task,omitempty"`
ToolUseID string `json:"tool_use_id,omitempty"`
// Transcript position at checkpoint start - tracks what was added during this checkpoint
TranscriptIdentifierAtStart string `json:"transcript_identifier_at_start,omitempty"` // Last identifier when checkpoint started (UUID for Claude, message ID for Gemini)
CheckpointTranscriptStart int `json:"checkpoint_transcript_start,omitempty"` // Transcript line offset at start of this checkpoint's data
// Deprecated: Use CheckpointTranscriptStart instead. Written for backward compatibility with older CLI versions.
TranscriptLinesAtStart int `json:"transcript_lines_at_start,omitempty"`
// Token usage for this checkpoint
TokenUsage *types.TokenUsage `json:"token_usage,omitempty"`
// SkillEvents records explicit native skill signals observed in this session.
// Consumers use these anchors to collapse skill-related raw transcript events.
SkillEventsVersion int `json:"skill_events_version,omitempty"`
SkillEvents []types.SkillEvent `json:"skill_events,omitempty"`
// SessionMetrics contains hook-provided session metrics (duration, turns, context usage).
// Populated for agents that provide these metrics via hooks (e.g., Cursor).
SessionMetrics *SessionMetrics `json:"session_metrics,omitempty"`
// AI-generated summary of the checkpoint
Summary *Summary `json:"summary,omitempty"`
// InitialAttribution is line-level attribution calculated at commit time
InitialAttribution *InitialAttribution `json:"initial_attribution,omitempty"`
// PromptAttributions is the raw per-prompt attribution data used to compute InitialAttribution.
// Diagnostic field — shows which prompt recorded which "user" lines.
PromptAttributions json.RawMessage `json:"prompt_attributions,omitempty"`
// Kind identifies the session purpose (e.g., "agent_review"). Empty for normal sessions.
Kind string `json:"kind,omitempty"`
// ReviewSkills lists the review skills that were run (only set when Kind is a review kind).
// May be empty when a review was attached post-hoc without declared skills.
ReviewSkills []string `json:"review_skills,omitempty"`
// ReviewPrompt is the actual text of the review request (composed prompt
// for spawn, first user prompt for attach). Only set when Kind is a
// review kind.
ReviewPrompt string `json:"review_prompt,omitempty"`
// InvestigateRunID is the 12-hex-char ID of the parent investigation
// run. Only set when Kind is an investigate kind.
InvestigateRunID string `json:"investigate_run_id,omitempty"`
// InvestigateTopic is the human-readable topic the investigation was
// asked to investigate. Only set when Kind is an investigate kind.
InvestigateTopic string `json:"investigate_topic,omitempty"`
}
// GetTranscriptStart returns the transcript line offset at which this checkpoint's data begins.
// Returns 0 for new checkpoints (start from beginning). For data written by older CLI versions,
// falls back to the deprecated TranscriptLinesAtStart field.
func (m CommittedMetadata) GetTranscriptStart() int {
if m.CheckpointTranscriptStart > 0 {
return m.CheckpointTranscriptStart
}
return m.TranscriptLinesAtStart
}
// SessionFilePaths contains the absolute paths to session files from the git tree root.
// Paths include the full checkpoint path prefix (e.g., "/a1/b2c3d4e5f6/1/metadata.json").
// Used in CheckpointSummary.Sessions to map session IDs to their file locations.
type SessionFilePaths struct {
Metadata string `json:"metadata"`
Transcript string `json:"transcript,omitempty"`
ContentHash string `json:"content_hash,omitempty"`
Prompt string `json:"prompt"`
}
// CheckpointSummary is the root-level metadata.json for a checkpoint.
// It contains aggregated statistics from all sessions and a map of session IDs
// to their file paths. Session-specific data (including initial_attribution)
// is stored in the session's subdirectory metadata.json.
//
// Structure on entire/checkpoints/v1 branch:
//
// <checkpoint-id[:2]>/<checkpoint-id[2:]>/
// ├── metadata.json # This CheckpointSummary
// ├── 1/ # First session
// │ ├── metadata.json # Session-specific CommittedMetadata
// │ ├── full.jsonl
// │ ├── prompt.txt
// │ └── content_hash.txt
// ├── 2/ # Second session
// └── 3/ # Third session...
//
//nolint:revive // Named CheckpointSummary to avoid conflict with existing Summary struct
type CheckpointSummary struct {
CLIVersion string `json:"cli_version,omitempty"`
CheckpointID id.CheckpointID `json:"checkpoint_id"`
Strategy string `json:"strategy"`
Branch string `json:"branch,omitempty"`
CheckpointsCount int `json:"checkpoints_count"`
FilesTouched []string `json:"files_touched"`
Sessions []SessionFilePaths `json:"sessions"`
TokenUsage *types.TokenUsage `json:"token_usage,omitempty"`
CombinedAttribution *InitialAttribution `json:"combined_attribution,omitempty"`
// HasReview is the umbrella "any review happened" flag: true when at least
// one session in this checkpoint has a review-kind Kind (currently
// "agent_review"). When new review kinds are introduced they should also
// cause this flag to be set so callers can keep asking "was this reviewed
// in any way?" without caring about the variant.
HasReview bool `json:"has_review,omitempty"`
// HasInvestigation is the umbrella "any investigation happened" flag:
// true when at least one session in this checkpoint has an
// investigate-kind Kind (currently "agent_investigate"). When new
// investigate kinds are introduced they should also cause this flag to
// be set so callers can keep asking "was this investigated in any way?"
// without caring about the variant.
HasInvestigation bool `json:"has_investigation,omitempty"`
}
// SessionMetrics contains hook-provided session metrics from agents that report
// them via lifecycle hooks (e.g., Cursor). These supplement transcript-derived
// metrics for agents whose transcripts lack usage/timing data.
type SessionMetrics struct {
DurationMs int64 `json:"duration_ms,omitempty"`
TurnCount int `json:"turn_count,omitempty"`
ContextTokens int `json:"context_tokens,omitempty"`
ContextWindowSize int `json:"context_window_size,omitempty"`
}
// Summary contains AI-generated summary of a checkpoint.
type Summary struct {
Intent string `json:"intent"` // What user wanted to accomplish
Outcome string `json:"outcome"` // What was achieved
Learnings LearningsSummary `json:"learnings"` // Categorized learnings
Friction []string `json:"friction"` // Problems/annoyances encountered
OpenItems []string `json:"open_items"` // Tech debt, unfinished work
}
// LearningsSummary contains learnings grouped by scope.
type LearningsSummary struct {
Repo []string `json:"repo"` // Codebase-specific patterns/conventions
Code []CodeLearning `json:"code"` // File/module specific findings
Workflow []string `json:"workflow"` // General dev practices
}
// CodeLearning captures a learning tied to a specific code location.
type CodeLearning struct {
Path string `json:"path"` // File path
Line int `json:"line,omitempty"` // Start line number
EndLine int `json:"end_line,omitempty"` // End line for ranges (optional)
Finding string `json:"finding"` // What was learned
}
// InitialAttribution captures line-level attribution metrics at commit time.
// This is a point-in-time snapshot comparing the checkpoint tree (agent work)
// against the committed tree (may include human edits).
//
// Attribution Metrics:
// - TotalCommitted keeps the historical "net additions" view for compatibility
// - TotalLinesChanged measures total committed line changes (adds + modifies + removes)
// - AgentPercentage represents "of the lines changed in this commit, what percentage came from the agent"
// - AgentRemoved tracks committed deletions performed by the agent
type InitialAttribution struct {
CalculatedAt time.Time `json:"calculated_at"`
AgentLines int `json:"agent_lines"` // Lines added by agent that remain in the commit
AgentRemoved int `json:"agent_removed"` // Lines removed by agent that remain removed in the commit
HumanAdded int `json:"human_added"` // Lines added by human (excluding modifications)
HumanModified int `json:"human_modified"` // Lines modified by human (estimate: min(added, removed))
HumanRemoved int `json:"human_removed"` // Lines removed by human (excluding modifications)
TotalCommitted int `json:"total_committed"` // Net additions in commit (legacy additions-focused metric)
TotalLinesChanged int `json:"total_lines_changed"` // Total committed line changes (adds + modifies + removes)
AgentPercentage float64 `json:"agent_percentage"` // (agent_lines + agent_removed) / total_lines_changed * 100
MetricVersion int `json:"metric_version,omitempty"` // 0/absent = legacy (additions-only %), 2 = changed-lines %
}
Aapi/checkpoint/metadata.go+485
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package checkpoint
import apicheckpoint "github.com/entireio/cli/api/checkpoint"
// The committed-checkpoint contract (persisted document types, option types,
// reader/writer interfaces, and the Write request union) lives in the
// api/checkpoint package so storage backends can depend on it without the
// CLI's agent/git machinery. These aliases re-export it under this package so
// existing CLI call sites are unaffected; the git implementation (GitStore,
// Open, the temporary/shadow-branch types) stays here.
type (
// Persisted document types.
CommittedMetadata = apicheckpoint.CommittedMetadata
//nolint:revive // Named CheckpointSummary to avoid conflict with the Summary struct (matches the contract definition).
CheckpointSummary = apicheckpoint.CheckpointSummary
CommittedInfo = apicheckpoint.CommittedInfo
SessionContent = apicheckpoint.SessionContent
SessionFilePaths = apicheckpoint.SessionFilePaths
SessionMetrics = apicheckpoint.SessionMetrics
Summary = apicheckpoint.Summary
LearningsSummary = apicheckpoint.LearningsSummary
CodeLearning = apicheckpoint.CodeLearning
InitialAttribution = apicheckpoint.InitialAttribution
// Operation option types.
WriteCommittedOptions = apicheckpoint.WriteCommittedOptions
UpdateCommittedOptions = apicheckpoint.UpdateCommittedOptions
PrecomputedTranscriptBlobs = apicheckpoint.PrecomputedTranscriptBlobs
// Reader/writer interfaces and the Write request union.
CommittedReader = apicheckpoint.CommittedReader
CommittedListReader = apicheckpoint.CommittedListReader
CommittedStore = apicheckpoint.CommittedStore
Writer = apicheckpoint.Writer
WriteRequest = apicheckpoint.WriteRequest
WriteSession = apicheckpoint.WriteSession
BackfillTranscript = apicheckpoint.BackfillTranscript
BackfillSummary = apicheckpoint.BackfillSummary
BackfillAttribution = apicheckpoint.BackfillAttribution
)
// Sentinel errors (re-exported so errors.Is keeps working across packages).
var (
ErrCheckpointNotFound = apicheckpoint.ErrCheckpointNotFound
ErrNoTranscript = apicheckpoint.ErrNoTranscript
)
// Contract helper functions, re-exported.
var (
ReadCommittedCheckpoint = apicheckpoint.ReadCommittedCheckpoint
ReadLatestSessionContent = apicheckpoint.ReadLatestSessionContent
ReadRawSessionLogForCheckpoint = apicheckpoint.ReadRawSessionLogForCheckpoint
)
Acmd/entire/cli/checkpoint/aliases.go+53
1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
11
12
16
17
18
16
17
19
20
21
22
22
23
24
25
26
27
28
29
30
23
24
25
124 unmodified lines
150
151
152
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
153
154
155
// Package checkpoint provides types and interfaces for checkpoint storage.
// Package checkpoint provides the git-backed checkpoint storage implementation.
//
// A Checkpoint captures a point-in-time within a session, containing either
// full state (Temporary) or metadata with a commit reference (Committed).
//
// The committed-checkpoint contract (persisted document types, option types,
// reader/writer interfaces, the Write request union) lives in the
// api/checkpoint package and is re-exported here via aliases (see aliases.go).
// The temporary/shadow-branch types below are git-only and stay in this package.
//
// See docs/architecture/sessions-and-checkpoints.md for the full domain model.
package checkpoint
import (
"context"
"encoding/json"
"errors"
"time"
"github.com/entireio/cli/cmd/entire/cli/agent/types"
"github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
"github.com/entireio/cli/redact"
"github.com/go-git/go-git/v6/plumbing"
)
// Errors returned by checkpoint operations.
var (
// ErrCheckpointNotFound is returned when a checkpoint ID doesn't exist.
ErrCheckpointNotFound = errors.New("checkpoint not found")
// ErrNoTranscript is returned when a checkpoint exists but has no transcript.
ErrNoTranscript = errors.New("no transcript found for checkpoint")
)
// Checkpoint represents a save point within a session.
type Checkpoint struct {
// ID is the unique checkpoint identifier
124 unmodified lines
Timestamp time.Time
}
// SessionID is the session identifier
SessionID string
// CreatedAt is when the checkpoint was originally created.
// When zero, writers use the current time.
CreatedAt time.Time
// Strategy is the name of the strategy that created this checkpoint
Strategy string
// Branch is the branch name where the checkpoint was created (empty if detached HEAD)
Branch string
// FilesTouched are files modified during the session
FilesTouched []string
// EphemeralBranch is the shadow branch name (for manual-commit strategy)
EphemeralBranch string
// AuthorName is the name to use for commits
AuthorName string
// AuthorEmail is the email to use for commits
AuthorEmail string
// Task checkpoint fields (for task/subagent checkpoints)
IsTask bool // Whether this is a task checkpoint
ToolUseID string // Tool use ID for task checkpoints
// Commit message fields (used for task checkpoints)
CommitSubject string // Subject line for the metadata commit (overrides default)
// Agent identifies the agent that created this checkpoint (e.g., "Claude Code", "Cursor")
Agent types.AgentType
// Model is the LLM model used during the session (e.g., "claude-sonnet-4-20250514")
Model string
// TurnID correlates checkpoints from the same agent turn.
TurnID string
// TokenUsage contains the token usage for this checkpoint
TokenUsage *types.TokenUsage
// SkillEvents records explicit native skill signals observed in this session.
SkillEvents []types.SkillEvent
// SessionMetrics contains hook-provided session metrics (duration, turns, context usage)
SessionMetrics *SessionMetrics
// Kind identifies the session purpose (e.g., "agent_review"). Empty for normal sessions.
Kind string
// InvestigateRunID is the 12-hex-char ID of the parent investigation
// run (only meaningful when Kind is an investigate kind).
InvestigateRunID string
// SessionID identifies which session slot to update within the checkpoint
SessionID string
// Prompts contains the raw user prompts (replaces existing).
// See WriteCommittedOptions.Prompts.
Prompts []string
// Agent identifies the agent type (needed for transcript chunking)
Agent types.AgentType
// SkillEvents replaces the session metadata skill_events when non-empty.
SkillEvents []types.SkillEvent
// ContentHashBlob is the blob hash of the "sha256:<hex>" content-hash
// string for the transcript.
ContentHashBlob plumbing.Hash
// ContentHash is the "sha256:<hex>" string itself, so the short-circuit
// path can compare without re-reading the blob.
ContentHash string
}
// SessionID is the session identifier (most recent session for multi-session checkpoints)
SessionID string
// CreatedAt is when the checkpoint was created
CreatedAt time.Time
// FilesTouched are files modified during all sessions
FilesTouched []string
// Agent identifies the agent that created this checkpoint
Agent types.AgentType
// IsTask indicates if this is a task checkpoint
IsTask bool
// ToolUseID is the tool use ID for task checkpoints
ToolUseID string
// Multi-session support
SessionCount int // Number of sessions (1 if single session)
SessionIDs []string // All session IDs that contributed
}
// Transcript is the session transcript content
Transcript []byte
// Prompts contains user prompts from this session
Prompts string
}
// Agent identifies the agent that created this checkpoint (e.g., "Claude Code", "Cursor")
Agent types.AgentType `json:"agent,omitempty"`
// Task checkpoint fields (only populated for task checkpoints)
IsTask bool `json:"is_task,omitempty"`
ToolUseID string `json:"tool_use_id,omitempty"`
// Token usage for this checkpoint
TokenUsage *types.TokenUsage `json:"token_usage,omitempty"`
// AI-generated summary of the checkpoint
Summary *Summary `json:"summary,omitempty"`
// InitialAttribution is line-level attribution calculated at commit time
InitialAttribution *InitialAttribution `json:"initial_attribution,omitempty"`
// Kind identifies the session purpose (e.g., "agent_review"). Empty for normal sessions.
Kind string `json:"kind,omitempty"`
// Info provides summary information for listing checkpoints.
// This is the generic checkpoint info type.
type Info struct {
Mcmd/entire/cli/checkpoint/checkpoint.go+6/-487
1586 unmodified lines
1587
1588
1589
1590
1590
1591
1592
1593
1586 unmodified lines
// computed once across multiple checkpoints.
func (s *GitStore) replaceTranscript(ctx context.Context, transcript redact.RedactedBytes, agentType types.AgentType, precomputed *PrecomputedTranscriptBlobs, sessionPath string, entries map[string]object.TreeEntry) error {
// Ignore precompute if invariants are violated — fall back to fresh chunking.
if precomputed != nil && !precomputed.isUsable() {
if precomputed != nil && !precomputed.IsUsable() {
precomputed = nil
}
Mcmd/entire/cli/checkpoint/committed.go+1/-1
1 unmodified line
2
3
4
5
5
6
7
8
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
9
10
11
12
13
14
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
1 unmodified line
import (
"context"
"fmt"
"github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
)
// CommittedStore provides the production committed checkpoint storage surface.
// Writes go through the unified Writer.Write(ctx, WriteRequest); the concrete
// per-operation methods (WriteCommitted/UpdateCommitted/...) remain on GitStore
// as the implementation Write dispatches to.
type CommittedStore interface {
CommittedListReader
ReadSessionMetadataAndPrompts(ctx context.Context, checkpointID id.CheckpointID, sessionIndex int) (*SessionContent, error)
Writer
}
// AuthorReader provides optional checkpoint author lookup.
// AuthorReader provides optional checkpoint author lookup. It stays in the
// implementation package because GetCheckpointAuthor is a git-log operation
// and Author is an implementation type, not part of the storage contract.
type AuthorReader interface {
GetCheckpointAuthor(ctx context.Context, checkpointID id.CheckpointID) (Author, error)
}
summary, err := ReadCommittedCheckpoint(ctx, reader, checkpointID)
if err != nil {
return nil, "", err
}
Mcmd/entire/cli/checkpoint/committed\_reader\_resolve.go+3/-74
2 unmodified lines
3 4 5 6 7 6 7 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 8 60 9 10 11 12 13 14
2 unmodified lines
import ( "context" "fmt"
"github.com/entireio/cli/cmd/entire/cli/checkpoint/id" )
// WriteRequest is a single committed-store write command. The set is closed: // the only implementations are the request types in this file, sealed via the // unexported isWriteRequest marker. A store dispatches on the concrete type; // a mirror/fan-out store forwards the same value to each backend's Write. // // This replaces the four separate writer methods (WriteCommitted / // UpdateCommitted / UpdateSummary / UpdateCheckpointSummary) with one // Store.Write(ctx, req) entry point, so adding a write operation is a new // request type plus one dispatch case — the Store interface stays unchanged // and existing backends keep compiling. type WriteRequest interface { isWriteRequest() }
// WriteSession creates or replaces a session document within a checkpoint, // materializing the checkpoint on its first session. This is condensation's // write. (Maps to the former WriteCommitted.) type WriteSession WriteCommittedOptions
// BackfillTranscript replaces a session's transcript, prompts, and skill // events at stop time without clobbering sibling fields. (Maps to the former // UpdateCommitted.) type BackfillTranscript UpdateCommittedOptions
// BackfillSummary rewrites only the summary of the checkpoint's latest // session. (Maps to the former UpdateSummary.) type BackfillSummary struct { CheckpointID id.CheckpointID Summary *Summary }
// BackfillAttribution rewrites the checkpoint root's combined attribution. // (Maps to the former UpdateCheckpointSummary.) type BackfillAttribution struct { CheckpointID id.CheckpointID Attribution *InitialAttribution }
// Write dispatches a committed write request to the matching git operation. // Unknown request types are a programmer error, surfaced rather than ignored. // The request types and the Writer interface are defined in the api/checkpoint // contract (re-exported here via aliases). Unknown request types are a // programmer error, surfaced rather than ignored. func (s *GitStore) Write(ctx context.Context, req WriteRequest) error { switch r := req.(type) { case WriteSession:
Mcmd/entire/cli/checkpoint/committed\_write.go+3/-52
2 unmodified lines
3 4 5 6 6 7 8 9 10 11 13 14 15 16 17 18 12 13 14 15 16 17 18 19 65 unmodified lines
85 86 87 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 88 89 90
2 unmodified lines
import ( "context" "errors" "strings" "testing"
"github.com/entireio/cli/cmd/entire/cli/checkpoint/id" "github.com/entireio/cli/redact" )
// unknownWriteRequest is a WriteRequest the dispatcher does not handle. It is // sealed into the union via the unexported marker (only possible in-package), // which lets the test exercise the default branch. type unknownWriteRequest struct{}
func (unknownWriteRequest) isWriteRequest() {} // Note: the Write dispatcher's default ("unsupported request") branch is no // longer reachable from this package — the WriteRequest union is sealed to the // api/checkpoint contract package, so an unhandled request type can only be // introduced there (a forward-safety net for future request types). The // per-request dispatch below is the meaningful coverage.
// TestWrite_DispatchesEachRequest verifies that Store.Write routes each request // type to the corresponding git operation, observing the effect of each. 65 unmodified lines
} }
// TestWrite_UnknownRequestErrors verifies the dispatcher surfaces an // unhandled request type rather than silently ignoring it. func TestWrite_UnknownRequestErrors(t *testing.T) { t.Parallel() repo, _ := setupBranchTestRepo(t) store := NewGitStore(repo, DefaultV1Refs())
err := store.Write(context.Background(), unknownWriteRequest{}) if err == nil { t.Fatal("Write(unknownWriteRequest) should error") } if !strings.Contains(err.Error(), "unsupported write request") { t.Errorf("error = %v, want mention of unsupported write request", err) } }
// TestWrite_BackfillSummaryNotFound verifies error propagation through dispatch. func TestWrite_BackfillSummaryNotFound(t *testing.T) { t.Parallel()
Mcmd/entire/cli/checkpoint/committed\_write\_test.go+5/-23