handoff

package
v1.7.0 Latest Latest
Warning

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

Go to latest
Published: Feb 2, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package handoff provides the canonical YAML handoff format for context preservation. Handoffs are compact (~400 tokens vs ~2000 for markdown) representations of session state that can be passed between agent sessions for continuity.

Index

Constants

View Source
const (
	// StatusComplete indicates the session completed all planned work.
	StatusComplete = "complete"
	// StatusPartial indicates the session completed some work but not all.
	StatusPartial = "partial"
	// StatusBlocked indicates the session is blocked and cannot proceed.
	StatusBlocked = "blocked"
)

Status values for handoff status field.

View Source
const (
	// OutcomeSucceeded indicates all goals were achieved.
	OutcomeSucceeded = "SUCCEEDED"
	// OutcomePartialPlus indicates most goals achieved, some work remains.
	OutcomePartialPlus = "PARTIAL_PLUS"
	// OutcomePartialMinus indicates some goals achieved but significant work remains.
	OutcomePartialMinus = "PARTIAL_MINUS"
	// OutcomeFailed indicates the session failed to achieve its goals.
	OutcomeFailed = "FAILED"
)

Outcome values for handoff outcome field. These provide more granular detail than status.

View Source
const (
	AgentTypeClaude = "cc"
	AgentTypeCodex  = "cod"
	AgentTypeGemini = "gmi"
)

AgentType values for identifying agent types.

View Source
const DefaultMaxPerDir = 50

DefaultMaxPerDir is the default number of handoffs to keep before rotation.

View Source
const HandoffVersion = "1.0"

HandoffVersion tracks the format version for backwards compatibility and migrations.

Variables

ValidAgentTypes is the set of valid agent type values.

ValidOutcomes is the set of valid outcome values.

ValidStatuses is the set of valid status values.

Functions

func MarshalYAML

func MarshalYAML(h *Handoff) ([]byte, error)

MarshalYAML serializes a handoff to YAML bytes.

Types

type FileChanges

type FileChanges struct {
	Created  []string `yaml:"created,omitempty"`
	Modified []string `yaml:"modified,omitempty"`
	Deleted  []string `yaml:"deleted,omitempty"`
}

FileChanges tracks file modifications during a session.

type GenerateHandoffOptions

type GenerateHandoffOptions struct {
	// SessionName identifies this session (required)
	SessionName string

	// AgentName is the agent's Agent Mail identity (optional, enables Agent Mail integration)
	AgentName string

	// AgentType is the agent type (cc, cod, gmi)
	AgentType string

	// PaneID is the tmux pane ID (optional)
	PaneID string

	// ProjectKey is the project path for Agent Mail (defaults to projectDir)
	ProjectKey string

	// TokensUsed is the current token usage
	TokensUsed int

	// TokensMax is the maximum token budget
	TokensMax int

	// Goal is the explicit goal (if known, skips analysis)
	Goal string

	// Now is the explicit next action (if known, skips analysis)
	Now string

	// Output is optional agent output to analyze
	Output []byte

	// IncludeBeads enables BV integration (default: true if bv available)
	IncludeBeads *bool

	// IncludeAgentMail enables Agent Mail integration (default: true if agentmail available)
	IncludeAgentMail *bool

	// BVClient is an optional pre-configured BV client
	BVClient *bv.BVClient

	// AgentMailClient is an optional pre-configured Agent Mail client
	AgentMailClient *agentmail.Client

	// TransferTTLSeconds refreshes reservation TTL when preparing transfer instructions.
	TransferTTLSeconds int

	// TransferGraceSeconds adds a retry grace period during transfer (seconds).
	TransferGraceSeconds int
}

GenerateHandoffOptions configures handoff generation.

type Generator

type Generator struct {
	// contains filtered or unexported fields
}

Generator creates handoff content from various sources.

func NewGenerator

func NewGenerator(projectDir string) *Generator

NewGenerator creates a Generator for the given project directory.

func NewGeneratorWithLogger

func NewGeneratorWithLogger(projectDir string, logger *slog.Logger) *Generator

NewGeneratorWithLogger creates a Generator with a custom logger.

func (*Generator) EnrichWithGitState

func (g *Generator) EnrichWithGitState(h *Handoff) error

EnrichWithGitState adds git information to handoff.

func (*Generator) GenerateAutoHandoff

func (g *Generator) GenerateAutoHandoff(sessionName, agentType, paneID string, output []byte, tokensUsed, tokensMax int) (*Handoff, error)

GenerateAutoHandoff creates an auto-generated handoff suitable for pre-compact hooks. It combines output analysis with git state for a complete picture.

func (*Generator) GenerateFromOutput

func (g *Generator) GenerateFromOutput(sessionName string, output []byte) (*Handoff, error)

GenerateFromOutput creates a handoff by analyzing agent output text.

func (*Generator) GenerateFromTranscript

func (g *Generator) GenerateFromTranscript(sessionName, transcriptPath string) (*Handoff, error)

GenerateFromTranscript creates handoff from Claude Code transcript. Transcript path: ~/.claude/projects/.../session.jsonl

func (*Generator) GenerateHandoff

func (g *Generator) GenerateHandoff(ctx context.Context, opts GenerateHandoffOptions) (*Handoff, error)

GenerateHandoff creates a complete handoff with BV and Agent Mail integration. This is the main entry point for handoff generation, gathering:

  • Git state (uncommitted changes, branch, recent commits)
  • Active beads from BV (in-progress tasks assigned to this agent)
  • Agent Mail state (inbox messages, file reservations)

All integrations are optional and fail gracefully if unavailable.

func (*Generator) ProjectDir

func (g *Generator) ProjectDir() string

ProjectDir returns the project directory for this generator.

type Handoff

type Handoff struct {
	// Metadata
	Version   string    `yaml:"version"`    // Format version for migrations
	Session   string    `yaml:"session"`    // Session identifier
	Date      string    `yaml:"date"`       // Date in YYYY-MM-DD format
	CreatedAt time.Time `yaml:"created_at"` // Precise creation timestamp
	UpdatedAt time.Time `yaml:"updated_at"` // Last update timestamp

	// Status tracking
	Status  string `yaml:"status"`  // complete|partial|blocked
	Outcome string `yaml:"outcome"` // SUCCEEDED|PARTIAL_PLUS|PARTIAL_MINUS|FAILED

	// Core fields (REQUIRED for status line)
	Goal string `yaml:"goal"` // What this session accomplished - REQUIRED
	Now  string `yaml:"now"`  // What next session should do first - REQUIRED
	Test string `yaml:"test"` // Command to verify this work

	// Work tracking
	DoneThisSession []TaskRecord `yaml:"done_this_session,omitempty"`

	// Context for future self
	Blockers  []string          `yaml:"blockers,omitempty"`
	Questions []string          `yaml:"questions,omitempty"`
	Decisions map[string]string `yaml:"decisions,omitempty"`
	Findings  map[string]string `yaml:"findings,omitempty"`

	// What worked and what didn't
	Worked []string `yaml:"worked,omitempty"`
	Failed []string `yaml:"failed,omitempty"`

	// Next steps
	Next []string `yaml:"next,omitempty"`

	// File tracking
	Files FileChanges `yaml:"files,omitempty"`

	// Integration fields - populated during recovery
	ActiveBeads      []string `yaml:"active_beads,omitempty"`       // From BV
	AgentMailThreads []string `yaml:"agent_mail_threads,omitempty"` // From Agent Mail
	CMMemories       []string `yaml:"cm_memories,omitempty"`        // From CM

	// Agent info for multi-agent sessions
	AgentID   string `yaml:"agent_id,omitempty"`
	AgentType string `yaml:"agent_type,omitempty"` // cc, cod, gmi
	PaneID    string `yaml:"pane_id,omitempty"`

	// Token context at time of handoff
	TokensUsed int     `yaml:"tokens_used,omitempty"`
	TokensMax  int     `yaml:"tokens_max,omitempty"`
	TokensPct  float64 `yaml:"tokens_pct,omitempty"`

	// File reservation transfer instructions (optional)
	ReservationTransfer *ReservationTransfer `yaml:"reservation_transfer,omitempty"`
}

Handoff represents a complete context handoff between sessions. The Goal and Now fields are REQUIRED and used by the status line integration.

func New

func New(session string) *Handoff

New creates a new Handoff with defaults populated.

func (*Handoff) AddBlocker

func (h *Handoff) AddBlocker(blocker string) *Handoff

AddBlocker adds a blocker to the list.

func (*Handoff) AddDecision

func (h *Handoff) AddDecision(key, value string) *Handoff

AddDecision records a key decision.

func (*Handoff) AddFinding

func (h *Handoff) AddFinding(key, value string) *Handoff

AddFinding records an important discovery.

func (*Handoff) AddTask

func (h *Handoff) AddTask(task string, files ...string) *Handoff

AddTask adds a completed task record.

func (*Handoff) HasChanges

func (h *Handoff) HasChanges() bool

HasChanges returns true if any file changes were recorded.

func (*Handoff) IsBlocked

func (h *Handoff) IsBlocked() bool

IsBlocked returns true if the handoff is marked as blocked.

func (*Handoff) IsComplete

func (h *Handoff) IsComplete() bool

IsComplete returns true if the handoff is marked as complete.

func (*Handoff) IsValid

func (h *Handoff) IsValid() bool

IsValid returns true if the handoff passes validation.

func (*Handoff) MarkCreated

func (h *Handoff) MarkCreated(files ...string) *Handoff

MarkCreated adds a file to the created list.

func (*Handoff) MarkDeleted

func (h *Handoff) MarkDeleted(files ...string) *Handoff

MarkDeleted adds a file to the deleted list.

func (*Handoff) MarkModified

func (h *Handoff) MarkModified(files ...string) *Handoff

MarkModified adds a file to the modified list.

func (*Handoff) MustValidate

func (h *Handoff) MustValidate()

MustValidate panics if validation fails. Use for testing or when validation failure should be impossible (e.g., programmatically constructed handoffs).

func (*Handoff) SetAgentInfo

func (h *Handoff) SetAgentInfo(agentID, agentType, paneID string) *Handoff

SetAgentInfo sets the agent-related fields.

func (*Handoff) SetDefaults

func (h *Handoff) SetDefaults()

SetDefaults populates default values for optional fields. This should be called before serialization to ensure consistent output.

func (*Handoff) SetTokenInfo

func (h *Handoff) SetTokenInfo(used, max int) *Handoff

SetTokenInfo sets the token context fields.

func (*Handoff) TotalFileChanges

func (h *Handoff) TotalFileChanges() int

TotalFileChanges returns the total number of file changes.

func (*Handoff) Validate

func (h *Handoff) Validate() ValidationErrors

Validate checks the handoff for required fields and correct values. Returns ALL validation errors (not just the first) for comprehensive logging. Use IsValid() for a simple boolean check.

func (*Handoff) ValidateAndSetDefaults

func (h *Handoff) ValidateAndSetDefaults() ValidationErrors

ValidateAndSetDefaults combines SetDefaults and Validate for convenience. Returns validation errors after setting defaults.

func (*Handoff) ValidateMinimal

func (h *Handoff) ValidateMinimal() error

ValidateMinimal performs a minimal validation check for just the required fields. Use this for quick checks where full validation is not needed.

func (*Handoff) WithGoalAndNow

func (h *Handoff) WithGoalAndNow(goal, now string) *Handoff

WithGoalAndNow is a convenience method for setting the required fields.

func (*Handoff) WithStatus

func (h *Handoff) WithStatus(status, outcome string) *Handoff

WithStatus sets the status and outcome fields.

type HandoffMeta

type HandoffMeta struct {
	Path    string
	Session string
	Date    time.Time
	Status  string
	Goal    string // For quick display
	IsAuto  bool
}

HandoffMeta provides summary information about a handoff file.

type Reader

type Reader struct {
	// contains filtered or unexported fields
}

Reader handles reading handoff files from disk with caching.

func NewReader

func NewReader(projectDir string) *Reader

NewReader creates a Reader for the given project directory.

func NewReaderWithOptions

func NewReaderWithOptions(projectDir string, cacheExpiry time.Duration, logger *slog.Logger) *Reader

NewReaderWithOptions creates a Reader with custom options.

func (*Reader) BaseDir

func (r *Reader) BaseDir() string

BaseDir returns the base directory where handoffs are stored.

func (*Reader) ExtractGoalNow

func (r *Reader) ExtractGoalNow(sessionName string) (goal, now string, err error)

ExtractGoalNow extracts just goal and now fields efficiently. For status line use - uses regex and caching for speed.

func (*Reader) FindLatest

func (r *Reader) FindLatest(sessionName string) (*Handoff, string, error)

FindLatest returns the most recent handoff for a session. Returns (nil, "", nil) if no handoffs exist (not an error).

func (*Reader) FindLatestAny

func (r *Reader) FindLatestAny() (*Handoff, string, error)

FindLatestAny returns the most recent handoff across all sessions.

func (*Reader) InvalidateCache

func (r *Reader) InvalidateCache()

InvalidateCache clears the goal/now cache. Call when you know handoffs have been written.

func (*Reader) ListHandoffs

func (r *Reader) ListHandoffs(sessionName string) ([]HandoffMeta, error)

ListHandoffs returns all handoffs for a session, sorted by date descending.

func (*Reader) ListSessions

func (r *Reader) ListSessions() ([]string, error)

ListSessions returns all sessions that have handoffs.

func (*Reader) Read

func (r *Reader) Read(path string) (*Handoff, error)

Read parses a specific handoff file.

type ReservationSnapshot

type ReservationSnapshot struct {
	PathPattern string    `yaml:"path_pattern"`
	Exclusive   bool      `yaml:"exclusive,omitempty"`
	Reason      string    `yaml:"reason,omitempty"`
	ExpiresAt   time.Time `yaml:"expires_at,omitempty"`
}

ReservationSnapshot captures a single reservation for transfer.

type ReservationTransfer

type ReservationTransfer struct {
	FromAgent          string                `yaml:"from_agent,omitempty"`
	ProjectKey         string                `yaml:"project_key,omitempty"`
	TTLSeconds         int                   `yaml:"ttl_seconds,omitempty"`
	GracePeriodSeconds int                   `yaml:"grace_period_seconds,omitempty"`
	CreatedAt          time.Time             `yaml:"created_at,omitempty"`
	Reservations       []ReservationSnapshot `yaml:"reservations,omitempty"`
}

ReservationTransfer describes how to transfer file reservations to a new session.

type ReservationTransferClient

type ReservationTransferClient interface {
	ReservePaths(ctx context.Context, opts agentmail.FileReservationOptions) (*agentmail.ReservationResult, error)
	ReleaseReservations(ctx context.Context, projectKey, agentName string, paths []string, ids []int) error
	RenewReservations(ctx context.Context, opts agentmail.RenewReservationsOptions) (*agentmail.RenewReservationsResult, error)
}

ReservationTransferClient is the subset of Agent Mail client methods needed for transfers.

type ReservationTransferResult

type ReservationTransferResult struct {
	FromAgent      string                          `json:"from_agent"`
	ToAgent        string                          `json:"to_agent"`
	RequestedPaths []string                        `json:"requested_paths"`
	GrantedPaths   []string                        `json:"granted_paths"`
	ReleasedPaths  []string                        `json:"released_paths"`
	Conflicts      []agentmail.ReservationConflict `json:"conflicts,omitempty"`
	RolledBack     bool                            `json:"rolled_back,omitempty"`
	Success        bool                            `json:"success"`
	Error          string                          `json:"error,omitempty"`
}

ReservationTransferResult reports transfer outcomes for debugging and recovery.

func TransferReservations

TransferReservations moves reservations from one agent to another. It releases the old reservations, attempts to reserve for the new agent, and rolls back on conflicts where possible to approximate atomicity.

type TaskRecord

type TaskRecord struct {
	Task  string   `yaml:"task"`
	Files []string `yaml:"files,omitempty"`
}

TaskRecord represents a completed task with associated file changes.

type TransferReservationsOptions

type TransferReservationsOptions struct {
	ProjectKey   string
	FromAgent    string
	ToAgent      string
	Reservations []ReservationSnapshot

	// TTLSeconds refreshes the reservation TTL on transfer (0 uses default).
	TTLSeconds int
	// GracePeriod waits and retries once on conflict to allow release propagation.
	GracePeriod time.Duration

	Logger *slog.Logger
}

TransferReservationsOptions configures a reservation transfer.

type ValidationError

type ValidationError struct {
	Field   string      // The field that failed validation
	Message string      // Human-readable error message
	Value   interface{} // The invalid value (for logging)
}

ValidationError represents a validation error with structured fields.

func (ValidationError) Error

func (e ValidationError) Error() string

Error implements the error interface.

type ValidationErrors

type ValidationErrors []ValidationError

ValidationErrors is a collection of validation errors.

func (ValidationErrors) Error

func (errs ValidationErrors) Error() string

Error implements the error interface for multiple errors.

func (ValidationErrors) FieldNames

func (errs ValidationErrors) FieldNames() []string

FieldNames returns the list of fields with errors.

func (ValidationErrors) ForField

func (errs ValidationErrors) ForField(field string) ValidationErrors

ForField returns all errors for a specific field.

func (ValidationErrors) HasErrors

func (errs ValidationErrors) HasErrors() bool

HasErrors returns true if there are any validation errors.

type Writer

type Writer struct {
	// contains filtered or unexported fields
}

Writer handles writing handoff files to disk with atomic writes and rotation.

func NewWriter

func NewWriter(projectDir string) *Writer

NewWriter creates a Writer for the given project directory.

func NewWriterWithOptions

func NewWriterWithOptions(projectDir string, maxPerDir int, logger *slog.Logger) *Writer

NewWriterWithOptions creates a Writer with custom options.

func (*Writer) Archive

func (w *Writer) Archive(path string) error

Archive moves a specific handoff to the .archive directory.

func (*Writer) BaseDir

func (w *Writer) BaseDir() string

BaseDir returns the base directory where handoffs are stored.

func (*Writer) CleanArchive

func (w *Writer) CleanArchive(sessionName string, olderThan time.Duration) (int, error)

CleanArchive removes archived handoffs older than the given duration.

func (*Writer) Delete

func (w *Writer) Delete(path string) error

Delete removes a specific handoff file. Use with caution - typically handoffs should be archived, not deleted.

func (*Writer) EnsureDir

func (w *Writer) EnsureDir(sessionName string) error

EnsureDir creates the handoff directory for a session if needed.

func (*Writer) Write

func (w *Writer) Write(h *Handoff, description string) (string, error)

Write saves a handoff to the appropriate directory using atomic write. The description is sanitized to kebab-case for use in the filename. Returns the path to the written file.

func (*Writer) WriteAuto

func (w *Writer) WriteAuto(h *Handoff) (string, error)

WriteAuto saves an auto-generated handoff with timestamp naming. Auto handoffs use the format: auto-handoff-{ISO8601-timestamp}.yaml

func (*Writer) WriteToPath

func (w *Writer) WriteToPath(h *Handoff, path string) error

WriteToPath writes a handoff directly to the specified path. Unlike Write(), this doesn't auto-generate the filename or session directory.

Jump to

Keyboard shortcuts

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