Documentation
¶
Overview ¶
Package checkpoint implements evva's per-turn "checkpoint & rewind" store: before each user turn the runtime records a checkpoint, and the first time a turn's fs tools (edit/write) mutate a file, that file's pre-mutation bytes are captured. A later /rewind restores the captured files (code), truncates the conversation to the turn boundary (chat), or both.
On-disk layout, joining the .evva/* family (plans, worktrees):
<workdir>/.evva/checkpoints/<session-id>/
cp-000001.json # one Record per checkpoint (per user turn)
cp-000002.json
blobs/<sha256-hex> # content-addressed before-images (raw file bytes)
Before-images are stored as RAW file bytes (not the edit tool's LF-normalized in-memory copy) so a code-restore reproduces the original file exactly — encoding, BOM, and line endings included — with a plain os.WriteFile. Content-addressing dedupes a file re-touched across turns.
The store mirrors internal/session's durability shape: atomic temp+rename writes, a versioned schema, and List that skips corrupt/too-new files rather than aborting the picker.
Index ¶
- Constants
- type FileRef
- type Manager
- func (m *Manager) Begin(cutLen, fullCompactCount int, prompt string)
- func (m *Manager) CaptureBefore(absPath string)
- func (m *Manager) List() []*Record
- func (m *Manager) Load(seq int) (*Record, error)
- func (m *Manager) RestoreCode(r *Record) RestoreResult
- func (m *Manager) SetRetention(ret Retention)
- func (m *Manager) SetSession(sessionID string)
- type Record
- type RestoreResult
- type Retention
Constants ¶
const PreviewMaxBytes = 200
PreviewMaxBytes caps the persisted first-user-prompt preview shown in the /rewind picker. Matches session.PreviewMaxBytes intent.
const Version = 1
Version is the on-disk schema version for a Record. Bump on a breaking JSON change; List skips files with a higher version (forward-compat) the same way session.List does.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type FileRef ¶
type FileRef struct {
Path string `json:"path"` // absolute path captured
Existed bool `json:"existed"` // false → created this turn → delete on restore
Hash string `json:"hash,omitempty"` // sha256 hex of before-bytes; empty when !Existed
Size int `json:"size"` // before-image byte length (0 when !Existed)
}
FileRef records one file captured in a checkpoint.
Existed=false marks a file the turn CREATED (it had no prior bytes): a code-restore deletes it. Existed=true carries the before-image's content hash; the blob lives at <session>/blobs/<Hash>.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager is the per-agent, session-scoped checkpoint runtime. It owns the "current turn" checkpoint the fs tools capture into and the restore engine the controller drives.
Construction is main-agent-only: subagents and swarm members don't checkpoint (their isolation already bounds blast radius — rewind PRD §5.6). A nil *Manager is never wired as a sink, so the capture path stays inert when the feature is off.
Concurrency: Begin runs on the agent-loop goroutine (the one that won the run flag); CaptureBefore runs on the parallel tool-dispatch goroutines; List/Restore run on the UI goroutine while no Run is in flight. A single mutex serializes all of them.
func NewManager ¶
NewManager builds a checkpoint manager rooted at <workdir>/.evva/checkpoints for the given session. Returns nil when workdir or sessionID is empty (the caller treats nil as "checkpointing disabled" and never wires it as a sink).
func (*Manager) Begin ¶
Begin opens a fresh checkpoint for a user turn: it records the conversation cut-point and compaction epoch, persists an (initially file-less) Record, and makes it the capture target. A turn that never touches a file still leaves this conversation-only checkpoint on disk.
Old checkpoints beyond the retention budget are pruned here (one sweep per turn). Begin must be called only at a real user-turn start (Run, not Continue) so a continued/iter-limit turn keeps capturing into the same checkpoint.
func (*Manager) CaptureBefore ¶
CaptureBefore records absPath's current on-disk state into the active checkpoint, once per path per turn (first call wins — later edits in the same turn keep the earliest before-image). It satisfies fs.CheckpointSink.
Best-effort by contract: every failure path is a logged no-op so a capture problem never blocks or errors the user's edit. A missing file is recorded as "did not exist" so a rewind deletes it; a path inside the checkpoint store itself is ignored.
func (*Manager) RestoreCode ¶
func (m *Manager) RestoreCode(r *Record) RestoreResult
RestoreCode rewrites every captured file back to its before-image and deletes the files the turn created. Each target must resolve inside the workdir or it is refused (a checkpoint can never write outside the project). Errors are collected per file rather than aborting, so one unwritable path doesn't strand the rest.
func (*Manager) SetRetention ¶
SetRetention updates the prune policy live (the /config form may change it mid-session).
func (*Manager) SetSession ¶
SetSession re-scopes the manager to a new session id (after /clear or /resume) and drops the in-progress checkpoint — the old turn belonged to the old session's namespace. A no-op when the id is unchanged.
type Record ¶
type Record struct {
Version int `json:"version"`
SessionID string `json:"session_id"`
Seq int `json:"seq"` // monotonic per session, 1-based
CreatedAt time.Time `json:"created_at"`
PromptPreview string `json:"prompt_preview"`
CutLen int `json:"cut_len"`
FullCompactCount int `json:"full_compact_count"`
Files []FileRef `json:"files"`
}
Record is one checkpoint: the conversation cut-point plus the before-images captured during the turn.
CutLen is len(session.Messages) at turn start — truncating the live history to it rewinds the conversation to just before the turn. FullCompactCount is the session's full-compaction counter at capture time: a chat-restore is only coherent while it still matches the live session (a full compaction rewrites Messages into a single brief, invalidating every prior index — a micro compaction preserves indices and is fine). See the rewind PRD §5.2.
type RestoreResult ¶
type RestoreResult struct {
Restored int // files rewritten from their before-image
Deleted int // files removed (created during the rewound turn)
Errors []string // per-file failures; restore is best-effort and never half-silent
}
RestoreResult summarizes a code-restore for the caller's status line.
type Retention ¶
type Retention struct {
MaxCount int // keep at most this many newest checkpoints (0 = unlimited)
MaxAge time.Duration // drop checkpoints older than this (0 = no age limit)
}
Retention bounds how many / how old checkpoints a session keeps. A zero field means "no limit on this axis"; both zero means unbounded (the store never prunes).