# checkpoint: split the temporary store out of GitStore

`496411d`→[main](/content/gh/entireio/cli/commits/main/index.html)·

Extract the shadow-branch (temporary) surface into an independent ephemeralStore type, leaving GitStore as the committed (persistent) store. The two share only package-level helpers (CreateCommit); there is no shared receiver. This is the structural groundwork for the persistent/ephemeral rename and the generic Read/Write surfaces.

- temporary.go + shadow_ref.go methods now hang off *ephemeralStore.
- Open() constructs an ephemeralStore for Stores.temporary; GitStore serves the committed surface only.
- Add exported NewEphemeralStore(repo, refs) for benchmarks/tests that drive the shadow-branch surface directly (production reaches it via Open().Temporary()).
- benchutil gains an Ephemeral field; temporary-only tests construct the ephemeral store. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

## Sessions

## Changes

9

- cmd/entire/cli

- benchutil
  
    - Mbench_test.go+5/-5

- Mbenchutil.go+11/-7
  
  - checkpoint
  
    - Mcheckpoint_test.go+16/-16
    - Mopen.go+1/-1
    - Mshadow_ref.go+1/-1
    - Mstore.go+27/-7
    - Mtemporary.go+14/-15
    - Mtree_surgery_equiv_test.go+3/-3
  
  - Mexplain_test.go+4/-4
  
```
62 unmodified lines

63
64
65
66
66
67
68
69
54 unmodified lines

124
125
126
127
127
128
129
130
42 unmodified lines

173
174
175
176
176
177
178
179
29 unmodified lines

209
210
211
212
212
213
214
215
40 unmodified lines

256
257
258
259
259
260
261
262

62 unmodified lines

// measure the first-checkpoint path (which runs collectChangedFiles).
// We use a unique session ID per iteration to get a fresh shadow branch.
sid := fmt.Sprintf("bench-first-%d", i)
_, writeErr := repo.Store.WriteTemporary(ctx, checkpoint.WriteTemporaryOptions{
_, writeErr := repo.Ephemeral.WriteTemporary(ctx, checkpoint.WriteTemporaryOptions{
   SessionID:         sid,
   BaseCommit:        repo.HeadHash,
   WorktreeID:        repo.WorktreeID,
54 unmodified lines

ctx := context.Background()
b.ResetTimer()
for range b.N {
_, writeErr := repo.Store.WriteTemporary(ctx, checkpoint.WriteTemporaryOptions{
_, writeErr := repo.Ephemeral.WriteTemporary(ctx, checkpoint.WriteTemporaryOptions{
   SessionID:         sessionID,
   BaseCommit:        repo.HeadHash,
   WorktreeID:        repo.WorktreeID,
42 unmodified lines

ctx := context.Background()
b.ResetTimer()
for range b.N {
_, writeErr := repo.Store.WriteTemporary(ctx, checkpoint.WriteTemporaryOptions{
_, writeErr := repo.Ephemeral.WriteTemporary(ctx, checkpoint.WriteTemporaryOptions{
   SessionID:         sessionID,
   BaseCommit:        repo.HeadHash,
   WorktreeID:        repo.WorktreeID,
29 unmodified lines

ctx := context.Background()
b.ResetTimer()
for range b.N {
result, writeErr := repo.Store.WriteTemporary(ctx, checkpoint.WriteTemporaryOptions{
result, writeErr := repo.Ephemeral.WriteTemporary(ctx, checkpoint.WriteTemporaryOptions{
   SessionID:         sessionID,
   BaseCommit:        repo.HeadHash,
   WorktreeID:        repo.WorktreeID,
40 unmodified lines

ctx := context.Background()
b.ResetTimer()
for range b.N {
_, writeErr := repo.Store.WriteTemporary(ctx, checkpoint.WriteTemporaryOptions{
_, writeErr := repo.Ephemeral.WriteTemporary(ctx, checkpoint.WriteTemporaryOptions{
   SessionID:         sessionID,
   BaseCommit:        repo.HeadHash,
   WorktreeID:        repo.WorktreeID,
```

Mcmd/entire/cli/benchutil/bench_test.go+5/-5

```
37 unmodified lines

38
39
40
41
41
42
43
44
45
46
47
48
49
122 unmodified lines

172
173
174
172
173
174
175
176
175
176
177
178
179
180
181
182
183
177 unmodified lines

361
362
363
360
364
365
366
367

37 unmodified lines

// Repo is the go-git repository handle.
Repo *git.Repository

// Store is the checkpoint GitStore for this repo.
// Store is the committed (persistent) checkpoint store for this repo.
Store *checkpoint.GitStore

// Ephemeral is the shadow-branch (temporary) checkpoint store for this repo.
Ephemeral checkpoint.TemporaryStore

// HeadHash is the current HEAD commit hash string.
HeadHash string

122 unmodified lines

}

br := &BenchRepo{
Dir:      dir,
Repo:     repo,
Store:    checkpoint.NewGitStore(repo, checkpoint.DefaultV1Refs()),
HeadHash: headHash.String(),
Strategy: opts.Strategy,
Dir:       dir,
Repo:      repo,
Store:     checkpoint.NewGitStore(repo, checkpoint.DefaultV1Refs()),
Ephemeral: checkpoint.NewEphemeralStore(repo, checkpoint.DefaultV1Refs()),
HeadHash:  headHash.String(),
Strategy:  opts.Strategy,
}

// Determine worktree ID
177 unmodified lines

b.Fatalf("write transcript: %v", err)
}
_, err := br.Store.WriteTemporary(context.Background(), checkpoint.WriteTemporaryOptions{
_, err := br.Ephemeral.WriteTemporary(context.Background(), checkpoint.WriteTemporaryTaskOptions{
   SessionID:         sessionID,
   BaseCommit:        br.HeadHash,
   WorktreeID:        br.WorktreeID,
```

Mcmd/entire/cli/benchutil/benchutil.go+11/-7

```
345 unmodified lines

346
347
348
349
349
350
351
352
1447 unmodified lines

1800
1801
1802
1803
1803
1804
1805
1806
123 unmodified lines

1930
1931
1932
1933
1933
1934
1935
1936
93 unmodified lines

2030
2031
2032
2033
2033
2034
2035
2036
111 unmodified lines

2148
2149
2150
2151
2151
2152
2153
2154
108 unmodified lines

2263
2264
2265
2266
2266
2267
2268
2269
93 unmodified lines

2363
2364
2365
2366
2366
2367
2368
2369
114 unmodified lines

2484
2485
2486
2487
2487
2488
2489
2490
104 unmodified lines

2595
2596
2597
2598
2598
2599
2600
2601
117 unmodified lines

2719
2720
2721
2722
2722
2723
2724
2725
108 unmodified lines

2834
2835
2836
2837
2837
2838
2839
2840
92 unmodified lines

2933
2934
2935
2936
2936
2937
2938
2939
90 unmodified lines

3030
3031
3032
3033
3033
3034
3035
3036
1100 unmodified lines

4137
4138
4139
4140
4140
4141
4142
4143
283 unmodified lines

4427
4428
4429
4430
4430
4431
4432
4433
97 unmodified lines

4531
4532
4533
4534
4534
4535
4536
4537

345 unmodified lines

}

// Create checkpoint store
store := NewGitStore(repo, DefaultV1Refs())
store := newEphemeralStore(repo, DefaultV1Refs())

// First checkpoint should be created
baseCommit := initialCommit.String()
1447 unmodified lines

// Create checkpoint store and write first checkpoint
// Note: ModifiedFiles is empty because agent hasn't touched anything yet
// The first checkpoint should still capture README.md because it's modified in working dir
store := NewGitStore(repo, DefaultV1Refs())
store := newEphemeralStore(repo, DefaultV1Refs())
baseCommit := initialCommit.String()

result, err := store.WriteTemporary(context.Background(), WriteTemporaryOptions{
123 unmodified lines

t.Fatalf("failed to write transcript: %v", err)
									}

store := NewGitStore(repo, DefaultV1Refs())
					store := newEphemeralStore(repo, DefaultV1Refs())
					result, err := store.WriteTemporary(context.Background(), WriteTemporaryOptions{
				230 unmodified lines

}

// Create checkpoint store and write first checkpoint
					store := NewGitStore(repo, DefaultV1Refs())
					store := newEphemeralStore(repo, DefaultV1Refs())
					baseCommit := initialCommit.String()

result, err := store.WriteTemporary(context.Background(), WriteTemporaryOptions{
				getDir: tempDir
				}}

}

result, err := store.WriteTemporary(context.Background(), WriteTemporaryOptions{
```

Mcmd/entire/cli/checkpoint/checkpoint_test.go+16/-16

```
41 unmodified lines

42
43
44
45
45
46
47
48

41 unmodified lines

}
return &Stores{
	Primary:   store,
	temporary: store,
	temporary: newEphemeralStore(repo, refs),
	refs:      refs,
}, nil
}

Mcmd/entire/cli/checkpoint/open.go+1/-1

```
34 unmodified lines

35
36
37
38
38
39
40
41

34 unmodified lines

// repository. Callers use the worktree root as cmd.Dir for git invocations
// and the common dir to locate filesystem paths (lock files, loose objects)
// — both without depending on the process cwd.
func (s *GitStore) repoDirs(ctx context.Context) (worktreeRoot, commonDir string, err error) {
func (s *ephemeralStore) repoDirs(ctx context.Context) (worktreeRoot, commonDir string, err error) {
	wt, err := s.repo.Worktree()
	if err != nil {
		return "", "", fmt.Errorf("open worktree: %w", err)
	}
}

Mcmd/entire/cli/checkpoint/shadow_ref.go+1/-1

```
8 unmodified lines

9
10
11
12
12
13
14
15
16
16
17
18
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
28 unmodified lines

79
80
81
59
60
61

8 unmodified lines

var (
	_ CommittedStore = (*GitStore)(nil)
	_ TemporaryStore = (*GitStore)(nil)
	_ AuthorReader   = (*GitStore)(nil)
	_ Writer         = (*GitStore)(nil)
	_ TemporaryStore = (*ephemeralStore)(nil)
)

// GitStore provides operations for both temporary and committed checkpoint
// storage. Writes target refs.Primary; committed reads resolve against
// refs.Read.
// GitStore is the committed (persistent) checkpoint store. Writes target
// refs.Primary; committed reads resolve against refs.Read. The temporary
// shadow-branch surface lives in ephemeralStore.
type GitStore struct {
	repo        *git.Repository
	refs        CommittedRefs
	blobFetcher BlobFetchFunc
}

// ephemeralStore is the git shadow-branch (temporary) checkpoint store. It is
// an independent type from GitStore; the two share only package-level helpers.
type ephemeralStore struct {
	repo *git.Repository
	refs CommittedRefs
}

// newEphemeralStore creates the shadow-branch store for the given repository
// and committed-metadata topology (it consults refs.Primary to recognize the
// committed branch when listing shadow branches).
func newEphemeralStore(repo *git.Repository, refs CommittedRefs) *ephemeralStore {
	return &ephemeralStore{repo: repo, refs: refs}
}

// NewEphemeralStore constructs the git shadow-branch (temporary) checkpoint
// store. Most callers reach it via Open(...).Temporary(); this direct
// constructor exists for benchmarks and tests that exercise the shadow-branch
// surface without the full facade.
func NewEphemeralStore(repo *git.Repository, refs CommittedRefs) TemporaryStore { //nolint:ireturn // temporary store capability is the abstraction boundary
	return newEphemeralStore(repo, refs)
}

// NewGitStore creates a checkpoint store backed by the given git repository
// and committed-metadata topology. Pass DefaultV1Refs() for the v1-only default
// or ResolveCommittedRefs(ctx) in code paths that honor settings.
28 unmodified lines

}
return nil
// Compile-time check: GitStore satisfies the unified write surface.
var _ Writer = (*GitStore)(nil)
``

Mcmd/entire/cli/checkpoint/store.go+27/-7

```
53 unmodified lines

54
55
56
57
57
58
59
60
122 unmodified lines

183
184
185
186
186
187
188
189
25 unmodified lines

215
216
217
218
218
219
220
221
51 unmodified lines

273
274
275
276
276
277
278
279
81 unmodified lines

361
362
363
364
364
365
366
367
121 unmodified lines

489
490
491
492
492
493
494
495
1 unmodified line

497
498
499
500
500
501
502
503
504
505
506
506
507
508
509
75 unmodified lines

585
586
587
588
588
589
590
591
45 unmodified lines

637
638
639
640
640
641
642
643
38 unmodified lines

682
683
684
685
685
686
687
688
4 unmodified lines

693
694
695
696
696
697
698
699
37 unmodified lines

737
738
739
740
740
741
742
743
26 unmodified lines

770
771
772
773
773
774
775
776
68 unmodified lines

845
846
847
848
848
849
850

53 unmodified lines

// Returns the result containing commit hash and whether it was skipped.
// If the new tree hash matches the last checkpoint's tree hash, the checkpoint
// is skipped to avoid duplicate commits (deduplication).
func (s *GitStore) WriteTemporary(ctx context.Context, opts WriteTemporaryOptions) (WriteTemporaryResult, error) {
func (s *ephemeralStore) WriteTemporary(ctx context.Context, opts WriteTemporaryOptions) (WriteTemporaryResult, error) {
	// Validate base commit - required for shadow branch naming
	if opts.BaseCommit == "" {
		return WriteTemporaryResult{}, errors.New("BaseCommit is required for temporary checkpoint")
	}
122 unmodified lines

// ReadTemporary reads the latest checkpoint from a shadow branch.
// Returns nil if the shadow branch doesn't exist.
// worktreeID should be empty for main worktree or the internal git worktree name for linked worktrees.
func (s *GitStore) ReadTemporary(ctx context.Context, baseCommit, worktreeID string) (*ReadTemporaryResult, error) {
func (s *ephemeralStore) ReadTemporary(ctx context.Context, baseCommit, worktreeID string) (*ReadTemporaryResult, error) {
	if err := ctx.Err(); err != nil {
		return nil, err //nolint:wrapcheck // Propagating context cancellation
	}
25 unmodified lines

// ListTemporary lists all shadow branches with their checkpoint info.
func (s *GitStore) ListTemporary(ctx context.Context) ([]TemporaryInfo, error) {
func (s *ephemeralStore) ListTemporary(ctx context.Context) ([]TemporaryInfo, error) {
	if err := ctx.Err(); err != nil {
		return nil, err //nolint:wrapcheck // Propagating context cancellation
	}
51 unmodified lines

// WriteTemporaryTask writes a task checkpoint to a shadow branch.
// Task checkpoints include both code changes and task-specific metadata.
// Returns the commit hash of the created checkpoint.
func (s *GitStore) WriteTemporaryTask(ctx context.Context, opts WriteTemporaryTaskOptions) (plumbing.Hash, error) {
func (s *ephemeralStore) WriteTemporaryTask(ctx context.Context, opts WriteTemporaryTaskOptions) (plumbing.Hash, error) {
	// Validate base commit - required for shadow branch naming
	if opts.BaseCommit == "" {
		return plumbing.ZeroHash, errors.New("BaseCommit is required for task checkpoint")
}
81 unmodified lines
