checkpoint

package
v1.11.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 11, 2026 License: MIT Imports: 14 Imported by: 0

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

View Source
const PreviewMaxBytes = 200

PreviewMaxBytes caps the persisted first-user-prompt preview shown in the /rewind picker. Matches session.PreviewMaxBytes intent.

View Source
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

func NewManager(workdir, sessionID string, ret Retention, logger *slog.Logger) *Manager

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

func (m *Manager) Begin(cutLen, fullCompactCount int, prompt string)

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

func (m *Manager) CaptureBefore(absPath string)

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) List

func (m *Manager) List() []*Record

List returns this session's checkpoints, newest first.

func (*Manager) Load

func (m *Manager) Load(seq int) (*Record, error)

Load returns the checkpoint with the given seq, or an error if absent.

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

func (m *Manager) SetRetention(ret Retention)

SetRetention updates the prune policy live (the /config form may change it mid-session).

func (*Manager) SetSession

func (m *Manager) SetSession(sessionID string)

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.

func (*Record) FileCount

func (r *Record) FileCount() int

FileCount returns how many files the checkpoint captured.

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).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL