agent

package
v0.4.9 Latest Latest
Warning

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

Go to latest
Published: Mar 2, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package agent provides interfaces and types for integrating with coding agents. It abstracts agent-specific behavior (hooks, log parsing, session storage) so that the same Strategy implementations can work with any coding agent.

Index

Constants

View Source
const (
	// MaxChunkSize is the maximum size for a single transcript chunk.
	// GitHub has a 100MB limit per blob, so we use 50MB to be safe.
	MaxChunkSize = 50 * 1024 * 1024 // 50MB

	// ChunkSuffix is the format for chunk file suffixes (e.g., ".001", ".002")
	ChunkSuffix = ".%03d"
)
View Source
const (
	AgentNameClaudeCode     types.AgentName = "claude-code"
	AgentNameCursor         types.AgentName = "cursor"
	AgentNameGemini         types.AgentName = "gemini"
	AgentNameOpenCode       types.AgentName = "opencode"
	AgentNameFactoryAIDroid types.AgentName = "factoryai-droid"
)

Agent name constants (registry keys)

View Source
const (
	AgentTypeClaudeCode     types.AgentType = "Claude Code"
	AgentTypeCursor         types.AgentType = "Cursor"
	AgentTypeGemini         types.AgentType = "Gemini CLI"
	AgentTypeOpenCode       types.AgentType = "OpenCode"
	AgentTypeFactoryAIDroid types.AgentType = "Factory AI Droid"
	AgentTypeUnknown        types.AgentType = "Agent" // Fallback for backwards compatibility
)

Agent type constants (type identifiers stored in metadata/trailers)

View Source
const DefaultAgentName types.AgentName = AgentNameClaudeCode

DefaultAgentName is the registry key for the default agent.

Variables

This section is empty.

Functions

func AllProtectedDirs added in v0.4.3

func AllProtectedDirs() []string

AllProtectedDirs returns the union of ProtectedDirs from all registered agents.

func ChunkFileName

func ChunkFileName(baseName string, index int) string

ChunkFileName returns the filename for a chunk at the given index. Index 0 returns the base filename, index 1+ returns with chunk suffix.

func ChunkJSONL

func ChunkJSONL(content []byte, maxSize int) ([][]byte, error)

ChunkJSONL splits JSONL content at line boundaries. This is the default chunking for agents using JSONL format (like Claude Code).

func ChunkTranscript

func ChunkTranscript(ctx context.Context, content []byte, agentType types.AgentType) ([][]byte, error)

ChunkTranscript splits a transcript into chunks using the appropriate agent. If agentType is empty or the agent is not found, falls back to JSONL (line-based) chunking.

func DetectAgentTypeFromContent

func DetectAgentTypeFromContent(content []byte) types.AgentType

DetectAgentTypeFromContent detects the agent type from transcript content. Returns AgentTypeGemini if it appears to be Gemini JSON format, empty AgentType otherwise. This is used when the agent type is unknown but we need to chunk/reassemble correctly.

func List

func List() []types.AgentName

List returns all registered agent names in sorted order.

func ParseChunkIndex

func ParseChunkIndex(filename, baseName string) int

ParseChunkIndex extracts the chunk index from a filename. Returns 0 for the base file (no suffix), or the chunk number for suffixed files. Returns -1 if the filename doesn't match the expected pattern.

func ReadAndParseHookInput added in v0.4.6

func ReadAndParseHookInput[T any](stdin io.Reader) (*T, error)

ReadAndParseHookInput reads all bytes from stdin and unmarshals JSON into the given type. This is a shared helper for agent ParseHookEvent implementations.

func ReassembleJSONL

func ReassembleJSONL(chunks [][]byte) []byte

ReassembleJSONL concatenates JSONL chunks with newlines.

func ReassembleTranscript

func ReassembleTranscript(chunks [][]byte, agentType types.AgentType) ([]byte, error)

ReassembleTranscript combines chunks back into a single transcript. If agentType is empty or the agent is not found, falls back to JSONL (line-based) reassembly.

func Register

func Register(name types.AgentName, factory Factory)

Register adds an agent factory to the registry. Called from init() in each agent implementation.

func SortChunkFiles

func SortChunkFiles(files []string, baseName string) []string

SortChunkFiles sorts chunk filenames in order (base file first, then numbered chunks).

func StringList added in v0.4.8

func StringList() []string

Types

type Agent

type Agent interface {

	// Name returns the agent registry key (e.g., "claude-code", "gemini")
	Name() types.AgentName

	// Type returns the agent type identifier (e.g., "Claude Code", "Gemini CLI")
	// This is stored in metadata and trailers.
	Type() types.AgentType

	// Description returns a human-readable description for UI
	Description() string

	// IsPreview returns whether the agent integration is in preview or stable
	IsPreview() bool

	// DetectPresence checks if this agent is configured in the repository
	DetectPresence(ctx context.Context) (bool, error)

	// ProtectedDirs returns repo-root-relative directories that should never be
	// modified or deleted during rewind or other destructive operations.
	// Examples: [".claude"] for Claude, [".gemini"] for Gemini.
	ProtectedDirs() []string

	// ReadTranscript reads the raw transcript bytes for a session.
	ReadTranscript(sessionRef string) ([]byte, error)

	// ChunkTranscript splits a transcript into chunks if it exceeds maxSize.
	// Returns a slice of chunks. If the transcript fits in one chunk, returns single-element slice.
	// The chunking is format-aware: JSONL splits at line boundaries, JSON splits message arrays.
	ChunkTranscript(ctx context.Context, content []byte, maxSize int) ([][]byte, error)

	// ReassembleTranscript combines chunks back into a single transcript.
	// Handles format-specific reassembly (JSONL concatenation, JSON message merging).
	ReassembleTranscript(chunks [][]byte) ([]byte, error)

	// GetSessionID extracts session ID from hook input.
	GetSessionID(input *HookInput) string

	// GetSessionDir returns where agent stores session data for this repo.
	GetSessionDir(repoPath string) (string, error)

	// ResolveSessionFile returns the path to the session transcript file.
	ResolveSessionFile(sessionDir, agentSessionID string) string

	// ReadSession reads session data from agent's storage.
	ReadSession(input *HookInput) (*AgentSession, error)

	// WriteSession writes session data for resumption.
	WriteSession(ctx context.Context, session *AgentSession) error

	// FormatResumeCommand returns command to resume a session.
	FormatResumeCommand(sessionID string) string
}

Agent defines the interface for interacting with a coding agent. Each agent implementation (Claude Code, Cursor, Aider, etc.) converts its native format to the normalized types defined in this package.

The interface is organized into three groups:

  • Identity (5 methods): Name, Type, Description, DetectPresence, ProtectedDirs
  • Transcript Storage (3 methods): ReadTranscript, ChunkTranscript, ReassembleTranscript
  • Legacy (6 methods): Will be moved to optional interfaces or removed in a future phase

func Default

func Default() Agent

Default returns the default agent. Returns nil if the default agent is not registered.

func Detect

func Detect(ctx context.Context) (Agent, error)

Detect attempts to auto-detect which agent is being used. Iterates registered agents in sorted name order for deterministic results. Returns the first agent whose DetectPresence reports true.

func DetectAll added in v0.4.6

func DetectAll(ctx context.Context) []Agent

DetectAll returns all agents whose DetectPresence reports true. Agents are checked in sorted name order (via List()) for deterministic results. Returns an empty slice when no agent is detected.

func Get

func Get(name types.AgentName) (Agent, error)

func GetByAgentType

func GetByAgentType(agentType types.AgentType) (Agent, error)

GetByAgentType retrieves an agent by its type identifier.

Note: This uses a linear search that instantiates agents until a match is found. This is acceptable because:

  • Agent count is small (~2-20 agents)
  • Agent factories are lightweight (empty struct allocation)
  • Called infrequently (commit hooks, rewind, debug commands - not hot paths)
  • Cost is ~400ns worst case vs milliseconds for I/O operations

Only optimize if agent count exceeds 100 or profiling shows this as a bottleneck.

type AgentSession

type AgentSession struct {
	SessionID  string
	AgentName  types.AgentName
	RepoPath   string
	SessionRef string // Path/reference to session in agent's storage
	StartTime  time.Time

	// NativeData holds the session content in the agent's native format.
	// Only the originating agent can interpret this data.
	// Examples:
	//   - Claude Code: raw JSONL bytes
	//   - Cursor: serialized SQLite rows
	//   - Aider: Markdown content
	NativeData []byte

	// Computed fields - populated by the agent when reading
	ModifiedFiles []string
	NewFiles      []string
	DeletedFiles  []string

	// Optional normalized entries - agents may populate this if needed
	// for operations that benefit from structured access
	Entries []SessionEntry
}

AgentSession represents a coding session's data. Each agent stores data in its native format (JSONL, SQLite, Markdown, etc.) and only the originating agent can read/write it.

Design: Sessions are NOT interoperable between agents. A session created by Claude Code can only be read/written by Claude Code. This simplifies the implementation as we don't need format conversion.

func (*AgentSession) FindToolResultUUID

func (s *AgentSession) FindToolResultUUID(toolUseID string) (string, bool)

FindToolResultUUID finds the UUID of the entry containing the tool result for the given tool use ID. Returns the UUID and true if found.

func (*AgentSession) GetLastAssistantResponse

func (s *AgentSession) GetLastAssistantResponse() string

GetLastAssistantResponse returns the last assistant message content

func (*AgentSession) GetLastUserPrompt

func (s *AgentSession) GetLastUserPrompt() string

GetLastUserPrompt returns the last user message content

func (*AgentSession) TruncateAtUUID

func (s *AgentSession) TruncateAtUUID(uuid string) *AgentSession

TruncateAtUUID returns a new session truncated at the given UUID (inclusive)

type EntryType

type EntryType string

EntryType categorizes session entries

const (
	EntryUser      EntryType = "user"
	EntryAssistant EntryType = "assistant"
	EntryTool      EntryType = "tool"
	EntrySystem    EntryType = "system"
)

type Event added in v0.4.6

type Event struct {
	// Type is the kind of lifecycle event.
	Type EventType

	// SessionID identifies the agent session.
	SessionID string

	// PreviousSessionID is non-empty when this event represents a session continuation
	// or handoff (e.g., Claude starting a new session ID after exiting plan mode).
	PreviousSessionID string

	// SessionRef is an agent-specific reference to the transcript (typically a file path).
	SessionRef string

	// Prompt is the user's prompt text (populated on TurnStart events).
	Prompt string

	// Timestamp is when the event occurred.
	Timestamp time.Time

	// ToolUseID identifies the tool invocation (for SubagentStart/SubagentEnd events).
	ToolUseID string

	// SubagentID identifies the subagent instance (for SubagentEnd events).
	SubagentID string

	// ToolInput is the raw tool input JSON (for subagent type/description extraction).
	// Used when both SubagentType and TaskDescription are empty (agents that don't provide
	// these fields directly parse them from ToolInput).
	ToolInput json.RawMessage

	// SubagentType is the kind of subagent (for SubagentStart/SubagentEnd events).
	// Used with TaskDescription instead of ToolInput
	SubagentType    string
	TaskDescription string

	// ResponseMessage is an optional message to display to the user via the agent.
	ResponseMessage string

	// Metadata holds agent-specific state that the framework stores and makes available
	// on subsequent events. Examples: Pi's activeLeafId, Cursor's is_background_agent.
	Metadata map[string]string
}

Event is a normalized lifecycle event produced by an agent's ParseHookEvent method. The framework dispatcher uses these events to drive checkpoint/session lifecycle actions.

type EventType added in v0.4.6

type EventType int

EventType represents a normalized lifecycle event from any agent. Agents translate their native hooks into these event types via ParseHookEvent.

const (
	// SessionStart indicates the agent session has begun.
	SessionStart EventType = iota + 1

	// TurnStart indicates the user submitted a prompt and the agent is about to work.
	TurnStart

	// TurnEnd indicates the agent finished responding to a prompt.
	TurnEnd

	// Compaction indicates the agent is about to compress its context window.
	// This triggers the same save logic as TurnEnd but also resets the transcript offset.
	Compaction

	// SessionEnd indicates the session has been terminated.
	SessionEnd

	// SubagentStart indicates a subagent (task) has been spawned.
	SubagentStart

	// SubagentEnd indicates a subagent (task) has completed.
	SubagentEnd
)

func (EventType) String added in v0.4.6

func (e EventType) String() string

String returns a human-readable name for the event type.

type Factory

type Factory func() Agent

Factory creates a new agent instance

type FileWatcher

type FileWatcher interface {
	Agent

	// GetWatchPaths returns paths to watch for session changes
	GetWatchPaths() ([]string, error)

	// OnFileChange handles a detected file change and returns session info
	OnFileChange(path string) (*SessionChange, error)
}

FileWatcher is implemented by agents that use file-based detection. Agents like Aider that don't support hooks can use file watching to detect session activity.

type HookInput

type HookInput struct {
	HookType  HookType
	SessionID string
	// SessionRef is an agent-specific session reference (file path, db key, etc.)
	SessionRef string
	Timestamp  time.Time

	// UserPrompt is the user's prompt text (from UserPromptSubmit hooks)
	UserPrompt string

	// Tool-specific fields (PreToolUse/PostToolUse)
	ToolName     string
	ToolUseID    string
	ToolInput    []byte // Raw JSON
	ToolResponse []byte // Raw JSON (PostToolUse only)

	// RawData preserves agent-specific data for extension
	RawData map[string]interface{}
}

HookInput contains normalized data from hook callbacks

type HookResponseWriter added in v0.4.9

type HookResponseWriter interface {
	Agent

	// WriteHookResponse outputs a message to the user via the agent's hook response protocol.
	WriteHookResponse(message string) error
}

HookResponseWriter is implemented by agents that support structured hook responses. Agents that implement this can output messages (e.g., banners) to the user via the agent's response protocol. For example, Claude Code outputs JSON with a systemMessage field to stdout. Agents that don't implement this will silently skip hook response output.

type HookSupport

type HookSupport interface {
	Agent

	// HookNames returns the hook verbs this agent supports.
	// These become subcommands under `entire hooks <agent>`.
	// e.g., ["stop", "user-prompt-submit", "session-start", "session-end"]
	HookNames() []string

	// ParseHookEvent translates an agent-native hook into a normalized lifecycle Event.
	// Returns nil if the hook has no lifecycle significance (e.g., pass-through hooks).
	// This is the core contribution surface for new agent implementations.
	ParseHookEvent(ctx context.Context, hookName string, stdin io.Reader) (*Event, error)

	// InstallHooks installs agent-specific hooks.
	// If localDev is true, hooks point to local development build.
	// If force is true, removes existing Entire hooks before installing.
	// Returns the number of hooks installed.
	InstallHooks(ctx context.Context, localDev bool, force bool) (int, error)

	// UninstallHooks removes installed hooks
	UninstallHooks(ctx context.Context) error

	// AreHooksInstalled checks if hooks are currently installed
	AreHooksInstalled(ctx context.Context) bool
}

HookSupport is implemented by agents with lifecycle hooks. This optional interface allows agents like Claude Code and Cursor to install and manage hooks that notify Entire of agent events.

The interface is organized into two groups:

  • Hook Mapping (2 methods): HookNames, ParseHookEvent
  • Hook Management (3 methods): InstallHooks, UninstallHooks, AreHooksInstalled

type HookType

type HookType string

HookType represents agent lifecycle events

const (
	HookSessionStart     HookType = "session_start"
	HookSessionEnd       HookType = "session_end"
	HookUserPromptSubmit HookType = "user_prompt_submit"
	HookStop             HookType = "stop"
	HookPreToolUse       HookType = "pre_tool_use"
	HookPostToolUse      HookType = "post_tool_use"
)

type SessionChange

type SessionChange struct {
	SessionID  string
	SessionRef string
	EventType  HookType
	Timestamp  time.Time
}

SessionChange represents detected session activity (for FileWatcher)

type SessionEntry

type SessionEntry struct {
	UUID      string
	Type      EntryType
	Timestamp time.Time
	Content   string

	// Tool-specific fields
	ToolName      string
	ToolInput     interface{}
	ToolOutput    interface{}
	FilesAffected []string
}

SessionEntry represents a single entry in the session

type SubagentAwareExtractor added in v0.4.6

type SubagentAwareExtractor interface {
	Agent

	// ExtractAllModifiedFiles extracts files modified by both the main agent and any spawned subagents.
	// The subagentsDir parameter specifies where subagent transcripts are stored.
	// Returns a deduplicated list of all modified file paths.
	ExtractAllModifiedFiles(transcriptData []byte, fromOffset int, subagentsDir string) ([]string, error)

	// CalculateTotalTokenUsage computes token usage including all spawned subagents.
	// The subagentsDir parameter specifies where subagent transcripts are stored.
	CalculateTotalTokenUsage(transcriptData []byte, fromOffset int, subagentsDir string) (*TokenUsage, error)
}

SubagentAwareExtractor provides methods for extracting files and tokens including subagents. Agents that support spawning subagents (like Claude Code's Task tool) should implement this to ensure subagent contributions are included in checkpoints.

type TokenCalculator added in v0.4.6

type TokenCalculator interface {
	Agent

	// CalculateTokenUsage computes token usage from the transcript starting at the given offset.
	CalculateTokenUsage(transcriptData []byte, fromOffset int) (*TokenUsage, error)
}

TokenCalculator provides token usage calculation for a session. The framework calls this during step save and checkpoint if implemented.

type TokenUsage

type TokenUsage struct {
	// InputTokens is the number of input tokens (fresh, not from cache)
	InputTokens int `json:"input_tokens"`
	// CacheCreationTokens is tokens written to cache (billable at cache write rate)
	CacheCreationTokens int `json:"cache_creation_tokens"`
	// CacheReadTokens is tokens read from cache (discounted rate)
	CacheReadTokens int `json:"cache_read_tokens"`
	// OutputTokens is the number of output tokens generated
	OutputTokens int `json:"output_tokens"`
	// APICallCount is the number of API calls made
	APICallCount int `json:"api_call_count"`
	// SubagentTokens contains token usage from spawned subagents (if any)
	SubagentTokens *TokenUsage `json:"subagent_tokens,omitempty"`
}

TokenUsage represents aggregated token usage for a checkpoint. This is agent-agnostic and can be populated by any agent that tracks token usage.

func CalculateTokenUsage added in v0.4.8

func CalculateTokenUsage(ctx context.Context, ag Agent, transcriptData []byte, transcriptLinesAtStart int, subagentsDir string) *TokenUsage

CalculateTokenUsage calculates token usage from transcript data. Returns nil if the agent doesn't support token calculation or on error. Errors are debug-logged because callers treat nil token usage as "no data available".

type TranscriptAnalyzer

type TranscriptAnalyzer interface {
	Agent

	// GetTranscriptPosition returns the current position (length) of a transcript.
	// For JSONL formats (Claude Code), this is the line count.
	// For JSON formats (Gemini CLI), this is the message count.
	// Returns 0 if the file doesn't exist or is empty.
	GetTranscriptPosition(path string) (int, error)

	// ExtractModifiedFilesFromOffset extracts files modified since a given offset.
	// For JSONL formats (Claude Code), offset is the starting line number.
	// For JSON formats (Gemini CLI), offset is the starting message index.
	// Returns:
	//   - files: list of file paths modified by the agent (from Write/Edit tools)
	//   - currentPosition: the current position (line count or message count)
	//   - error: any error encountered during reading
	ExtractModifiedFilesFromOffset(path string, startOffset int) (files []string, currentPosition int, err error)

	// ExtractPrompts extracts user prompts from the transcript starting at the given offset.
	ExtractPrompts(sessionRef string, fromOffset int) ([]string, error)

	// ExtractSummary extracts a summary of the session from the transcript.
	ExtractSummary(sessionRef string) (string, error)
}

TranscriptAnalyzer provides format-specific transcript parsing. Agents that implement this get richer checkpoints (transcript-derived file lists, prompts, summaries). Agents that don't still participate in the checkpoint lifecycle via git-status-based file detection and raw transcript storage.

type TranscriptPreparer added in v0.4.6

type TranscriptPreparer interface {
	Agent

	// PrepareTranscript ensures the transcript is ready to read.
	// For Claude Code, this waits for the async transcript flush to complete.
	PrepareTranscript(ctx context.Context, sessionRef string) error
}

TranscriptPreparer is called before ReadTranscript to handle agent-specific flush/sync requirements (e.g., Claude Code's async transcript writing). The framework calls PrepareTranscript before ReadTranscript if implemented.

Directories

Path Synopsis
Package claudecode implements the Agent interface for Claude Code.
Package claudecode implements the Agent interface for Claude Code.
Package cursor implements the Agent interface for Cursor.
Package cursor implements the Agent interface for Cursor.
Package factoryaidroid implements the Agent interface for Factory AI Droid.
Package factoryaidroid implements the Agent interface for Factory AI Droid.
Package geminicli implements the Agent interface for Gemini CLI.
Package geminicli implements the Agent interface for Gemini CLI.
Package opencode implements the Agent interface for OpenCode.
Package opencode implements the Agent interface for OpenCode.
Package testutil provides shared test utilities for agent packages.
Package testutil provides shared test utilities for agent packages.

Jump to

Keyboard shortcuts

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