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
- Variables
- func AllProtectedDirs() []string
- func AllProtectedFiles() []string
- func ChunkFileName(baseName string, index int) string
- func ChunkJSONL(content []byte, maxSize int) ([][]byte, error)
- func ChunkTranscript(ctx context.Context, content []byte, agentType types.AgentType) ([][]byte, error)
- func DetectAgentTypeFromContent(content []byte) types.AgentType
- func DropStaleManagedHooks[E any](entries []E, commandOf func(E) string, want []string) ([]E, bool)
- func HasMetadataDenyRule(rawPermissions map[string]json.RawMessage) bool
- func HasRetiredMetadataDenyRule(ctx context.Context, ag Agent) bool
- func IsManagedHookCommand(command string) bool
- func IsSummaryCLIAvailable(name types.AgentName) bool
- func List() []types.AgentName
- func MissingEntireWarning(format WarningFormat) string
- func NewForegroundCommand(_ context.Context, binary string, args ...string) (*exec.Cmd, error)
- func NewResumeForegroundCommand(ctx context.Context, name types.AgentName, sessionID string) (*exec.Cmd, bool, error)
- func ParseChunkIndex(filename, baseName string) int
- func ReadAndParseHookInput[T any](stdin io.Reader) (*T, error)
- func ReadHookInputRaw(stdin io.Reader) (json.RawMessage, error)
- func ReadHookInputRawLimited(stdin io.Reader, limit int64) (json.RawMessage, error)
- func ReadTranscriptFile(filePath string) ([]byte, error)
- func ReassembleJSONL(chunks [][]byte) []byte
- func ReassembleTranscript(chunks [][]byte, agentType types.AgentType) ([]byte, error)
- func Register(name types.AgentName, factory Factory)
- func RemoveMetadataDenyRule(rawPermissions map[string]json.RawMessage) (bool, error)
- func RenderAdditionalContextHookOutput(hookEventName, text string) ([]byte, error)
- func RepairRetiredMetadataDenyRule(ctx context.Context, ag Agent) (bool, error)
- func RunIsolatedTextGeneratorCLI(ctx context.Context, runner TextCommandRunner, binary, displayName string, ...) (string, string, int, error)
- func SanitizeTranscriptForStorage(ag Agent, data []byte) []byte
- func ScanToolInvocations(ag Agent, transcriptData []byte, hints [][]byte, ...) (found, supported bool)
- func SetWindowsHookProbeForTesting(goos string, works func(ctx context.Context, command string) bool) func()
- func SnapshotRegistryForTesting() func()
- func SortChunkFiles(files []string, baseName string) []string
- func StatTranscriptFile(filePath string) (os.FileInfo, error)
- func StdinLooksInteractive(r io.Reader) bool
- func StringList() []string
- func StripGitEnv(env []string) []string
- func SummaryCLIBinaryName(name types.AgentName) string
- func UseWindowsProductionHooks(ctx context.Context) bool
- func WrapProductionJSONWarningHookCommand(command string, format WarningFormat) string
- func WrapProductionJSONWarningHookCommandForOS(command string, format WarningFormat, useWindows bool) string
- func WrapProductionPlainTextWarningHookCommand(command string, format WarningFormat) string
- func WrapProductionSilentHookCommand(command string) string
- func WrapProductionSilentHookCommandForOS(command string, useWindows bool) string
- func WrapWindowsProductionJSONWarningHookCommand(command string, format WarningFormat) string
- func WrapWindowsProductionPlainTextWarningHookCommand(command string, format WarningFormat) string
- func WrapWindowsProductionSilentHookCommand(command string) string
- func WriteSessionFile(ag SessionLocator, s *AgentSession, data []byte, perm os.FileMode) error
- type Agent
- type AgentSession
- type CapabilityDeclarer
- type CompactedTranscript
- type CompactedTranscriptAsset
- type ContextInjection
- type ContextInjector
- type DeclaredCaps
- type DiscoveredSkill
- type EffectiveHookDiagnostics
- type Event
- type EventType
- type Factory
- type FileWatcher
- type ForegroundCommandSpec
- type GenerationProgress
- type HookConfigFile
- func (f *HookConfigFile) Exists() bool
- func (f *HookConfigFile) GeneratedState(marker, render string) HookConfigState
- func (f *HookConfigFile) Path() string
- func (f *HookConfigFile) Read() ([]byte, error)
- func (f *HookConfigFile) Remove() error
- func (f *HookConfigFile) Root() (*os.Root, string)
- func (f *HookConfigFile) Write(data []byte, perm os.FileMode) error
- type HookConfigState
- type HookFreshness
- type HookInput
- type HookResponseWriter
- type HookSupport
- type HookType
- type Launcher
- type ModelExtractor
- type ModelInfo
- type ModelLister
- type PermissionConfigOwner
- type ProgressFn
- type ProgressPhase
- type PromptExtractor
- type ProtectedFilesProvider
- type RestoredSessionPathResolver
- type SessionBaseDirProvider
- type SessionChange
- type SessionEndBudgeter
- type SessionLocator
- type SessionStore
- func (s *SessionStore) Dir() string
- func (s *SessionStore) Exists(name string) bool
- func (s *SessionStore) Name(p string) (string, error)
- func (s *SessionStore) ReadFile(name string) ([]byte, error)
- func (s *SessionStore) SessionFile(agentSessionID string) (name, absPath string, err error)
- func (s *SessionStore) WriteFile(name string, data []byte, perm os.FileMode) error
- type SidecarImageProvider
- type SkillDiscoverer
- type SkillEvent
- func AppendPromptSlashCommandSkillEvent(events []SkillEvent, agentName, prompt string, timestamp time.Time) []SkillEvent
- func ExtractSkillEvents(ctx context.Context, ag Agent, transcriptData []byte, fromOffset int) []SkillEvent
- func SkillEventFromPromptSlashCommand(agentName, prompt string, timestamp time.Time) (SkillEvent, bool)
- type SkillEventCollapse
- type SkillEventExtractor
- type SkillEventSkill
- type SkillEventSource
- type SkillEventTranscriptAnchor
- type StreamingTextGenerator
- type SubagentAwareExtractor
- type SubagentSessionLink
- type SubagentSessionResolver
- type TestOnly
- type TextCommandRunner
- type TextGenerationError
- type TextGenerator
- type TokenCalculator
- type TokenUsage
- type ToolInvocation
- type ToolInvocationScanner
- type TranscriptAnalyzer
- type TranscriptCompactor
- type TranscriptFetcher
- type TranscriptPreparer
- type TranscriptSanitizer
- type WarningFormat
Constants ¶
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" )
const ( AgentNameClaudeCode types.AgentName = "claude-code" AgentNameCodex types.AgentName = "codex" AgentNameCopilotCLI types.AgentName = "copilot-cli" AgentNameCursor types.AgentName = "cursor" AgentNameFactoryAIDroid types.AgentName = "factoryai-droid" AgentNameGemini types.AgentName = "gemini" AgentNameOpenCode types.AgentName = "opencode" AgentNamePi types.AgentName = "pi" )
Agent name constants (registry keys)
const ( AgentTypeClaudeCode types.AgentType = "Claude Code" AgentTypeCodex types.AgentType = "Codex" AgentTypeCopilotCLI types.AgentType = "Copilot CLI" AgentTypeCursor types.AgentType = "Cursor" AgentTypeFactoryAIDroid types.AgentType = "Factory AI Droid" AgentTypeGemini types.AgentType = "Gemini CLI" AgentTypeOpenCode types.AgentType = "OpenCode" AgentTypePi types.AgentType = "Pi" AgentTypeUnknown types.AgentType = "Unknown" )
Agent type constants (type identifiers stored in metadata/trailers)
const ( SkillEventTypePromptInvocation = types.SkillEventTypePromptInvocation SkillEventTypeToolInvocation = types.SkillEventTypeToolInvocation SkillSignalPiInputSlashCommand = types.SkillSignalPiInputSlashCommand SkillSignalPromptSlashCommand = types.SkillSignalPromptSlashCommand SkillSignalClaudeSkillToolUse = types.SkillSignalClaudeSkillToolUse SkillConfidenceExplicit = types.SkillConfidenceExplicit SkillCollapseTargetUserMessage = types.SkillCollapseTargetUserMessage SkillCollapseTargetToolPair = types.SkillCollapseTargetToolPair )
The skill-event types and their constants live in the leaf agent/types package so the checkpoint contract can construct and reference skill events without importing the full agent package. These aliases keep existing agent.SkillEvent* references working.
const DefaultAgentName types.AgentName = AgentNameClaudeCode
DefaultAgentName is the registry key for the default agent.
const MetadataDenyRule = "Read(./.entire/metadata/**)"
MetadataDenyRule is the Read deny rule Entire used to install into agent permission configs to keep an agent from reading `.entire/metadata`.
It is no longer installed, and `entire enable` and `entire doctor` remove it. The constant survives because removing it is now the migration, so the exact string still has to be recognisable — and because it is what identifies the rule as ours: a rule byte-identical to this one was written by Entire, so deleting it takes nothing the user chose.
Why it went, in order of weight:
- A `deny` rule is a hard block, not a hint. Rules resolve deny → ask → allow, first match wins, and a deny rule cannot carry allowlist exceptions, so there is no way to soften it. Claude Code applies Read deny rules to file-reading Bash commands too, so a recursive grep from the repo root — or a command that merely names the path — is refused and has to be approved by hand. That defeats unattended/auto permission modes for a whole class of ordinary commands.
- It guarded a staging buffer, not a store. `.entire/metadata/<session>` holds a `full.jsonl` that is rewritten from the agent's own transcript on every Stop and deleted once the session is condensed. The durable copy lives in the checkpoint tree, which no permission rule covers: the same transcript is readable with `git show entire/checkpoints/v1:...`.
- It was mostly redundant. `.entire/.gitignore` already ignores `metadata/`, and Claude Code's Grep skips gitignored files, so the accidental-bulk-read case that motivated the rule was already covered. Glob does not respect gitignore, but Glob returns names, not content.
Deliberately NOT replaced with a PreToolUse hook on Read: that fires a subprocess for every file the agent reads, on a hook path that is already the dominant cost of a turn.
Variables ¶
var ErrOutsideSessionStore = errors.New("path is outside the agent's session directory")
ErrOutsideSessionStore reports a session file that does not lie inside the agent's session directory. Callers match it with errors.Is.
Functions ¶
func AllProtectedDirs ¶ added in v0.4.3
func AllProtectedDirs() []string
AllProtectedDirs returns the union of ProtectedDirs from all registered agents.
func AllProtectedFiles ¶ added in v0.5.6
func AllProtectedFiles() []string
AllProtectedFiles returns the union of ProtectedFiles from all registered agents.
func ChunkFileName ¶
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 ¶
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 ¶
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 DropStaleManagedHooks ¶ added in v0.10.1
DropStaleManagedHooks removes Entire-owned entries whose command is not one of want, leaving foreign entries and the wanted commands untouched. commandOf reads the command string off an entry, which is the only per-agent knowledge this needs — agents whose config stores it under a different field (e.g. Copilot CLI's `bash`) just return that field. want is a set because one hook list can legitimately carry several Entire commands.
It reports whether anything was dropped, because the caller must persist the config even when no hook was *added*: a config can hold both a stale and a current hook at once, and skipping the write would leave the stale one on disk.
Every agent must run this on every install, not only under --force. Without it a hook written by an older version survives alongside the freshly added one and keeps firing — which for the removed local-dev mode means a script inside the working tree still executes on every agent turn. This lives here rather than in each agent because it was independently re-derived six times and two of those copies got it wrong, and because `dupl` cannot see duplication across packages.
func HasMetadataDenyRule ¶ added in v0.10.5
func HasMetadataDenyRule(rawPermissions map[string]json.RawMessage) bool
HasMetadataDenyRule reports whether rawPermissions still carries the retired metadata deny rule. Used by the diagnostics that tell a user why they are being asked to approve ordinary commands; it does not mutate anything.
func HasRetiredMetadataDenyRule ¶ added in v0.10.5
HasRetiredMetadataDenyRule reports whether ag's config still carries the retired deny rule. It is read-only and answers false for every reason that is not a positive sighting — agent doesn't own a permissions config, file absent, unreadable, or unparseable — because its only callers are diagnostics, and telling a user their config is broken on the strength of a failed read is worse than staying quiet.
func IsManagedHookCommand ¶ added in v0.5.6
IsManagedHookCommand reports whether command is one Entire wrote: the current form or any shape an older version wrote, either bare or inside one of Entire's production wrapper forms.
The recognized set is owned here rather than passed in. Each agent previously declared its own copy, which meant six near-identical literals that a new agent could silently omit — and omitting the legacy entries is precisely what leaves an old hook installed forever.
func IsSummaryCLIAvailable ¶ added in v0.5.6
IsSummaryCLIAvailable reports whether the CLI binary for a summary-capable agent is on PATH. This is distinct from DetectPresence, which checks repo-level agent configuration — a repo configured with Claude Code for development can still use Codex or Gemini for summary generation as long as the binary is installed.
func MissingEntireWarning ¶ added in v0.5.6
func MissingEntireWarning(format WarningFormat) string
func NewForegroundCommand ¶ added in v0.7.8
NewForegroundCommand builds an exec.Cmd wired to the caller's terminal. Agent launchers use this for commands the user should interact with directly.
func NewResumeForegroundCommand ¶ added in v0.7.8
func NewResumeForegroundCommand(ctx context.Context, name types.AgentName, sessionID string) (*exec.Cmd, bool, error)
NewResumeForegroundCommand builds a foreground command for resuming a session, when the agent has a launchable resume command. ok=false means callers should print FormatResumeCommand for the user instead.
func ParseChunkIndex ¶
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
ReadAndParseHookInput decodes a single JSON hook payload from stdin into the given type. This is a shared helper for agent ParseHookEvent implementations.
It deliberately does NOT use io.ReadAll, which waits for stdin to reach EOF. Agents drive hooks by piping a JSON payload to the hook process, but some keep the write end of that pipe open for the hook's lifetime rather than closing it after writing — notably on Windows/Git Bash, where a full payload arrives but EOF never does. io.ReadAll then blocked indefinitely and the hook (e.g. gemini session-start) hung forever (issue #1398). A streaming json.Decoder returns as soon as one complete JSON value has been read, independent of when — or whether — stdin is closed.
func ReadHookInputRaw ¶ added in v0.9.0
func ReadHookInputRaw(stdin io.Reader) (json.RawMessage, error)
ReadHookInputRaw returns the raw bytes of a single JSON hook payload read from stdin, without waiting for EOF. It is the shared primitive behind every agent's hook-input read (issue #1398); callers that need custom parsing (e.g. key-name fallbacks, or forwarding the bytes to a subprocess) use this directly, while the common case uses ReadAndParseHookInput.
func ReadHookInputRawLimited ¶ added in v0.9.0
ReadHookInputRawLimited is ReadHookInputRaw with a ceiling of limit bytes on the JSON value (limit < 0 means unlimited). It is used at the external/plugin boundary to bound an untrusted payload — without reintroducing the EOF-wait hang, since the streaming decoder still returns on the first complete value.
func ReadTranscriptFile ¶ added in v0.10.4
ReadTranscriptFile reads a transcript path while preserving the .entire boundary for agents whose stable transcript cache lives there (OpenCode and Pi). Agent-owned transcript stores remain explicit external paths and retain their existing behavior.
func ReassembleJSONL ¶
ReassembleJSONL concatenates JSONL chunks with newlines.
func ReassembleTranscript ¶
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 ¶
Register adds an agent factory to the registry. Called from init() in each agent implementation.
func RemoveMetadataDenyRule ¶ added in v0.10.5
func RemoveMetadataDenyRule(rawPermissions map[string]json.RawMessage) (bool, error)
RemoveMetadataDenyRule drops the retired metadata deny rule from rawPermissions, mutating it in place, and reports whether anything changed.
Only the exact MetadataDenyRule string is removed, so every other deny rule — including one a user wrote — is preserved. A `deny` array left empty is deleted rather than written back as [].
Emptying the surrounding `permissions` object is the CALLER's to clean up: this function is handed only that object and cannot delete its own key. Every caller does — the two InstallHooks write paths, both UninstallHooks, and RepairRetiredMetadataDenyRule — so a config Entire fully owned ends up with no permissions block rather than an empty one.
A `deny` value that will not parse as a string array is left untouched: it is not ours, and rewriting what we cannot read is worse than leaving it.
func RenderAdditionalContextHookOutput ¶ added in v0.7.7
RenderAdditionalContextHookOutput renders the Claude-Code-style hook output that injects text into the model's context window:
{"hookSpecificOutput":{"hookEventName":<event>,"additionalContext":<text>}}
Claude Code, Codex (which hosts Claude-compatible hooks) and Gemini CLI all consume this shape on their prompt-submit hook (UserPromptSubmit / BeforeAgent) and merge additionalContext into the model context. Returns (nil, nil) for empty text so callers can write nothing.
func RepairRetiredMetadataDenyRule ¶ added in v0.10.5
RepairRetiredMetadataDenyRule removes the retired deny rule from ag's config and reports whether the file was changed.
Unlike the detector above, this one returns its errors, and `entire doctor` calls it directly rather than behind the detector — reading and parsing the same file twice bought nothing, since `changed` already reports whether the rule was there.
An absent config is not an error: the agent was simply never set up here.
func RunIsolatedTextGeneratorCLI ¶ added in v0.5.6
func RunIsolatedTextGeneratorCLI(ctx context.Context, runner TextCommandRunner, binary, displayName string, args []string, stdin string) (string, string, int, error)
RunIsolatedTextGeneratorCLI executes a text-generation CLI in an isolated temp directory with all GIT_* environment variables removed. This avoids recursive hook triggers and repo side effects while preserving provider-specific flags.
Returns (result, capturedStderr, stdoutByteCount, err). capturedStderr and stdoutByteCount are populated even on error so callers can wrap them into a *agent.TextGenerationError for timeout diagnostics.
func SanitizeTranscriptForStorage ¶ added in v0.10.0
SanitizeTranscriptForStorage applies the agent's storage sanitizer when it has one and returns data unchanged otherwise. Every path that stores a transcript copy should call this BEFORE redaction — see TranscriptSanitizer for why. A no-op for agents without the capability and idempotent for those with it, so it is safe to call on any transcript from any path.
func ScanToolInvocations ¶ added in v0.10.3
func ScanToolInvocations(ag Agent, transcriptData []byte, hints [][]byte, visit func(ToolInvocation) bool) (found, supported bool)
ScanToolInvocations walks ag's recorded tool invocations, returning whether visit accepted one and whether the agent's transcript format can be walked at all.
supported is false when ag is nil, the transcript is empty, or the agent does not implement ToolInvocationScanner. Callers MUST NOT collapse (found=false, supported=false) into "did not invoke" — seeing nothing because there was nothing to see is a different fact from seeing nothing because we cannot look, and a metric that conflates them reports a confident number over a population it silently cannot measure.
func SetWindowsHookProbeForTesting ¶ added in v0.8.0
func SetWindowsHookProbeForTesting(goos string, works func(ctx context.Context, command string) bool) func()
SetWindowsHookProbeForTesting overrides the OS and sh-wrapper probe used by UseWindowsProductionHooks and returns a restore function. Test-only.
func SnapshotRegistryForTesting ¶ added in v0.10.3
func SnapshotRegistryForTesting() func()
SnapshotRegistryForTesting captures the registry and returns a func that restores it. External-agent discovery registers plugins into the process-global registry, so a test that triggers it otherwise leaks entries into every later test that walks List() — which then execs a binary that the leaking test's TempDir cleanup has already deleted. Test-only.
func SortChunkFiles ¶
SortChunkFiles sorts chunk filenames in order (base file first, then numbered chunks).
func StatTranscriptFile ¶ added in v0.10.4
StatTranscriptFile is the metadata-only counterpart to ReadTranscriptFile. Lstat is intentional under .entire: a dangling or redirected transcript link is not the cached file whose existence the caller is testing.
func StdinLooksInteractive ¶ added in v0.9.0
StdinLooksInteractive reports whether r is an interactive terminal, i.e. no piped hook payload is on its way. Hook readers use it to bail out promptly instead of blocking on a read that will never complete (issue #1398).
func StringList ¶ added in v0.4.8
func StringList() []string
StringList returns user-facing agent names, excluding test-only agents.
func StripGitEnv ¶ added in v0.5.6
func SummaryCLIBinaryName ¶ added in v0.9.0
SummaryCLIBinaryName returns the CLI binary name for a summary-capable agent (e.g. "claude" for ClaudeCode, "agent" for Cursor). Returns "" for agents that are not summary-capable; callers should treat that as "we don't know" rather than guessing.
func UseWindowsProductionHooks ¶ added in v0.8.0
UseWindowsProductionHooks reports whether an agent should install the native Windows (cmd.exe) production hook wrappers instead of the sh-based ones. It is true only on Windows when the sh-based wrapper does not actually run on this host. This lives in the shared agent layer so every agent that wraps hooks for production inherits the same Windows fallback decision rather than re-implementing the probe and selection (codex is the first adopter; the other agents can switch to these helpers without new logic).
The probe runs once per InstallHooks call (not memoized): InstallHooks is invoked once per `entire enable`, and not caching is what lets a host that gains or loses a working sh migrate its hooks on the next install. The 2s timeout is deliberately generous so a momentarily slow sh isn't misread as absent; if it ever is, the next install simply re-migrates — the outcome is self-correcting, never wedged.
func WrapProductionJSONWarningHookCommand ¶ added in v0.5.6
func WrapProductionJSONWarningHookCommand(command string, format WarningFormat) string
WrapProductionJSONWarningHookCommand emits a JSON hook response with a systemMessage field on stdout when the Entire CLI is missing from PATH.
func WrapProductionJSONWarningHookCommandForOS ¶ added in v0.8.0
func WrapProductionJSONWarningHookCommandForOS(command string, format WarningFormat, useWindows bool) string
WrapProductionJSONWarningHookCommandForOS picks the sh-based or native Windows JSON-warning wrapper based on useWindows.
func WrapProductionPlainTextWarningHookCommand ¶ added in v0.5.6
func WrapProductionPlainTextWarningHookCommand(command string, format WarningFormat) string
WrapProductionPlainTextWarningHookCommand emits the warning as plain text to stdout when the Entire CLI is missing from PATH.
func WrapProductionSilentHookCommand ¶ added in v0.5.6
WrapProductionSilentHookCommand exits successfully without output when the Entire CLI is missing from PATH.
func WrapProductionSilentHookCommandForOS ¶ added in v0.8.0
WrapProductionSilentHookCommandForOS picks the sh-based or native Windows silent wrapper based on useWindows (typically from UseWindowsProductionHooks).
func WrapWindowsProductionJSONWarningHookCommand ¶ added in v0.8.0
func WrapWindowsProductionJSONWarningHookCommand(command string, format WarningFormat) string
WrapWindowsProductionJSONWarningHookCommand emits a JSON hook response with a systemMessage field on stdout when the Entire CLI is missing from PATH. It avoids sh so Codex hooks still work from native Windows shells. Codex already runs hook commands through cmd.exe /C, so this JSON-bearing command uses that shell directly instead of adding a second quote-parsing layer.
func WrapWindowsProductionPlainTextWarningHookCommand ¶ added in v0.8.0
func WrapWindowsProductionPlainTextWarningHookCommand(command string, format WarningFormat) string
WrapWindowsProductionPlainTextWarningHookCommand is the direct-shell fallback for WrapWindowsProductionJSONWarningHookCommand when JSON marshaling fails.
func WrapWindowsProductionSilentHookCommand ¶ added in v0.8.0
WrapWindowsProductionSilentHookCommand exits successfully without output when the Entire CLI is missing from PATH. It avoids sh so Codex hooks still work from native Windows shells.
func WriteSessionFile ¶ added in v0.10.4
func WriteSessionFile(ag SessionLocator, s *AgentSession, data []byte, perm os.FileMode) error
WriteSessionFile writes data to s.SessionRef through ag's own session store, creating parent directories.
It exists because every agent's WriteSession was the same four lines — os.MkdirAll of the parent, then os.WriteFile of an absolute SessionRef — eight times over, each one taking the path on trust. Routing them through the store makes the containment one decision instead of eight, and the parent-directory create stops being something each agent has to remember.
The store is anchored on s.RepoPath when it is set, which is the agent's real session directory for that repo. When it is not (callers that only carry a path, such as a restore driven from checkpoint metadata), the SessionRef's own directory anchors it: that contains nothing by itself, but it keeps the write on the same code path, and SessionRef there came from Entire's own resolution rather than from an agent payload.
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 Entire must never
// record as session changes or capture into a checkpoint.
// 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.
//
// SECURITY CONTRACT: agentSessionID is used to build a filesystem path and
// some implementations use it as a directory component or (Codex/Pi) return
// it verbatim when absolute. Callers that source agentSessionID from
// untrusted data (e.g. checkpoint metadata on the shared
// entire/checkpoints/v1 branch, hook input) MUST validate it with
// validation.ValidateSessionID first. The resume/log-restore paths do
// this at their choke points (transcript.resolveTranscriptPath and
// strategy.RestoreLogsOnly); do not call this with unvalidated input.
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 AgentForTranscriptPath ¶ added in v0.6.0
AgentForTranscriptPath returns the registered agent whose session directory for repoPath contains the given transcript path. Used to disambiguate which agent owns a session when multiple agents' hooks fire for the same session ID — a Cursor transcript path uniquely identifies a Cursor session even when Claude Code's hook is the one firing.
Returns (nil, false) if transcriptPath is empty, no agent claims it, or any registry lookup fails. Match is by directory prefix (with a separator) so "/x/.claude/projects/abc.jsonl" doesn't accidentally match an agent rooted at "/x/.claude/projects/ab".
func Default ¶
func Default() Agent
Default returns the default agent. Returns nil if the default agent is not registered.
func DetectAll ¶ added in v0.4.6
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 GetByAgentType ¶
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, resume, 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
}
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.
type CapabilityDeclarer ¶ added in v0.5.0
type CapabilityDeclarer interface {
DeclaredCapabilities() DeclaredCaps
}
CapabilityDeclarer is implemented by agents that declare their capabilities at registration time (e.g., external plugin agents). The As* helper functions below use this interface to gate capability access: an agent must both implement the optional interface AND declare the capability as true.
Built-in agents (Claude Code, Gemini CLI, etc.) do NOT implement this interface. For those agents, the As* helpers fall through to a direct type assertion, preserving existing behavior.
type CompactedTranscript ¶ added in v0.5.6
type CompactedTranscript struct {
Transcript []byte
Assets []CompactedTranscriptAsset
}
CompactedTranscript contains the result of transcript compaction into Entire Transcript Format. Assets are accepted in the protocol shape for forward compatibility but may not yet be persisted by all call sites.
type CompactedTranscriptAsset ¶ added in v0.5.6
CompactedTranscriptAsset is binary data extracted during transcript compaction.
type ContextInjection ¶ added in v0.7.7
type ContextInjection struct {
Text string
}
ContextInjection carries text that Entire asks an agent to place into the model's context window for the current session. An empty Text means there is nothing to inject.
type ContextInjector ¶ added in v0.7.7
type ContextInjector interface {
Agent
// InjectionEvent is the lifecycle event at which this agent emits an
// injection payload. The dispatcher only calls RenderContextInjection on
// matching events.
InjectionEvent() EventType
// RenderContextInjection returns the bytes to write to the hook's stdout to
// inject inj into the model, in the agent's native format. Returning an
// empty slice (or nil) means "write nothing".
RenderContextInjection(inj ContextInjection) ([]byte, error)
}
ContextInjector is implemented by agents that can place additional context into the *model* at a specific lifecycle event — for example Pi's before_agent_start (TurnStart) message injection or OpenCode's experimental.chat.system.transform.
This is deliberately distinct from HookResponseWriter: a hook response shows a banner to the *user*, whereas a ContextInjection reaches the *model*. An agent may implement both.
The agent declares which lifecycle event it injects at (InjectionEvent) and renders the native payload its transport understands (RenderContextInjection). For extension-backed agents (Pi, OpenCode) that payload is written to the hook's stdout and the embedded extension applies it via the agent's native injection API.
func AsContextInjector ¶ added in v0.7.7
func AsContextInjector(ag Agent) (ContextInjector, bool)
AsContextInjector returns ag as a ContextInjector when it implements the interface. Mirrors AsHookResponseWriter so callers don't type-assert inline.
type DeclaredCaps ¶ added in v0.5.0
type DeclaredCaps struct {
Hooks bool `json:"hooks"`
TranscriptAnalyzer bool `json:"transcript_analyzer"`
TranscriptPreparer bool `json:"transcript_preparer"`
TokenCalculator bool `json:"token_calculator"`
CompactTranscript bool `json:"compact_transcript"`
TextGenerator bool `json:"text_generator"`
StreamingTextGenerator bool `json:"streaming_text_generator"`
HookResponseWriter bool `json:"hook_response_writer"`
SubagentAwareExtractor bool `json:"subagent_aware_extractor"`
}
DeclaredCaps enumerates the optional interfaces an agent claims to support. JSON tags match the external agent protocol schema so external.InfoResponse can deserialize directly into this type.
Not every optional interface appears here: built-in-only capabilities that have no external-protocol equivalent (SessionBaseDirProvider, ModelExtractor, SkillEventExtractor, TranscriptSanitizer, TranscriptFetcher) are intentionally excluded — their As* helpers resolve by type assertion alone (see builtinCapability), with no DeclaredCaps gate.
type DiscoveredSkill ¶ added in v0.6.1
DiscoveredSkill describes one review-adjacent skill found on disk by a SkillDiscoverer. Name is the agent-native invocation form (e.g. a slash-prefixed command); Description is scraped from on-disk metadata if available; SourcePath is kept for debug logging and is not shown to the user.
type EffectiveHookDiagnostics ¶ added in v0.10.3
type EffectiveHookDiagnostics interface {
Agent
OwnsEffectiveHookDiagnostics()
}
EffectiveHookDiagnostics marks agents whose effective hook state is reported by an agent-owned diagnostic surface rather than generic freshness output.
func AsEffectiveHookDiagnostics ¶ added in v0.10.3
func AsEffectiveHookDiagnostics(ag Agent) (EffectiveHookDiagnostics, bool)
AsEffectiveHookDiagnostics returns the agent as EffectiveHookDiagnostics if it owns diagnostics for its effective hook configuration.
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
// Model is the LLM model identifier (e.g., "claude-sonnet-4-20250514").
// Populated on SessionStart (Claude Code), ModelUpdate (Gemini CLI BeforeModel),
// and TurnStart/TurnEnd events when the agent provides model info.
Model 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
// Final is true only for events that represent true completion of a
// subagent (Claude Code's SubagentStop), never for the launch-time
// PostToolUse SubagentEnd, which fires at the background launch stub
// seconds after launch. Downstream lifecycle branching keys off this flag,
// not any payload sentinel. Final is the disambiguator for agents with a
// two-signal model (a launch-time stub plus a separate completion hook,
// like Claude Code's background tasks); agents whose single subagent-end
// event already fires at true completion must leave it false so the
// existing pipeline handles them unchanged.
Final bool
// SubagentTranscriptPath is the agent-declared path to the subagent's own
// transcript (SubagentEnd). Set it whenever the hook payload names the file;
// Codex and Cursor both send agent_transcript_path, as does Claude Code's
// SubagentStop.
//
// When empty the framework probes the layout Claude Code and Factory AI Droid
// share (cli.ResolveAgentTranscriptPath). For any other agent that probe finds
// nothing and yields "" silently, so the only symptom is a task checkpoint with
// no subagent transcript plus file extraction falling back to the main
// transcript — where a subagent's edits never appear. Declaring the path is how
// an agent opts out of that guess.
SubagentTranscriptPath 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
// ModifiedFiles is the list of file paths modified by a subagent (SubagentEnd)
// or a tool call (ToolUse). Paths may be absolute, cwd-relative, or
// repo-relative; lifecycle handlers normalize against the worktree root.
ModifiedFiles []string
// NewFiles and DeletedFiles carry create/delete paths for ToolUse events,
// kept separate from ModifiedFiles so consumers can reason about agent intent.
NewFiles []string
DeletedFiles []string
// CWD is the working directory the agent was running in when the event fired.
// Set on ToolUse so cwd-relative payload paths can be resolved before
// repo-root normalization.
CWD string
// ResponseMessage is an optional message to display to the user via the agent.
ResponseMessage string
// Hook-provided session metrics (populated by agents that report these via hooks).
DurationMs int64 // Session duration from agent hook (e.g., Cursor SessionEnd)
TurnCount int // Number of agent turns/loops (e.g., Cursor Stop hook)
ContextTokens int // Context window tokens used (e.g., Cursor PreCompact hook)
ContextWindowSize int // Total context window size (e.g., Cursor PreCompact hook)
// TokenUsage carries per-turn token accounting reported by an agent hook
// directly (e.g., Cursor's Stop hook). Set when the hook payload contains
// authoritative token data that the JSONL transcript does not. Lifecycle
// handlers prefer this over transcript-based calculation when populated.
TokenUsage *TokenUsage
// SkillEvents records native agent skill signals surfaced by hooks.
// The lifecycle layer persists these to session state and later checkpoint metadata.
SkillEvents []SkillEvent
// 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 // ModelUpdate indicates the agent reported the LLM model being used. // This fires on hooks that carry model info but have no other lifecycle action // (e.g., Gemini CLI's BeforeModel). The framework stores the model as a hint // for subsequent TurnStart/TurnEnd events in the same session. ModelUpdate // ToolUse indicates the agent ran a tool that touched files mid-turn. // Carries ModifiedFiles/NewFiles/DeletedFiles so the framework can populate // state.FilesTouched incrementally — without this, agents like Codex that // commit mid-turn (before TurnEnd fires) have no per-tool file accounting, // and the carry-forward path falls back to whole-transcript extraction. ToolUse )
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 ForegroundCommandSpec ¶ added in v0.7.8
ForegroundCommandSpec describes a command Entire can launch in the caller's terminal without going through a shell.
func ResumeCommandSpecFor ¶ added in v0.7.8
func ResumeCommandSpecFor(name types.AgentName, sessionID string) (ForegroundCommandSpec, bool)
ResumeCommandSpecFor returns the foreground command shape for agents whose resume command is safe for Entire to launch directly. Agents not listed here still expose FormatResumeCommand for print-only resume instructions.
type GenerationProgress ¶ added in v0.9.0
type GenerationProgress struct {
Phase ProgressPhase
OutputTokens int // running estimate during PhaseGenerating; final at PhaseDone
InputTokens int // populated at PhaseFirstToken
CachedInputTokens int // populated at PhaseFirstToken
TTFTms int // time-to-first-token, populated at PhaseFirstToken
DurationMs int // populated at PhaseDone (final result event)
}
GenerationProgress reports a snapshot of streaming text generation progress. Fields not relevant to the current Phase may be zero-valued.
type HookConfigFile ¶ added in v0.10.4
type HookConfigFile struct {
// contains filtered or unexported fields
}
HookConfigFile is an agent's hook-configuration file inside the worktree — .claude/settings.json, .cursor/hooks.json, .gemini/settings.json, .github/hooks/entire.json, .factory/settings.json, .codex/hooks.json, .opencode/plugin/entire.ts.
Seven agents each carried the same four lines: filepath.Join a repo root with a fixed relative path, then os.ReadFile / os.MkdirAll / os.WriteFile / os.Remove on the result, each with a //nolint:gosec saying the path came from the repo root. That justification is about where the path was BUILT and says nothing about where it RESOLVES, which is the part that matters: `.claude` and its siblings live in the working tree, and a working tree arrives by clone. A repository carrying a checked-in symlink at `.claude` therefore had `entire enable` create directories and write JSON through it, to wherever it pointed — outside the repository included.
Anchoring on worktreedir fixes that at the root rather than at each call site: the base is the worktree root, the agent's fixed subpath is a NAME inside it, and directories are created with MkdirAllNoSymlink so a symlinked component is refused by name instead of silently followed. It also collapses the seven copies into one, which is what keeps the next agent integration from reintroducing the pattern.
Every symlink component is refused, including the file itself. os.Root blocks a link that escapes the worktree but follows one pointing elsewhere inside it; accepting the leaf would let `.claude/settings.json -> ../victim.json` redirect both the merge read and the subsequent write.
An earlier revision of this type deliberately allowed a symlinked leaf, on the grounds that pointing `.claude/settings.json` at a dotfile repo is a real setup Entire has no business breaking. That was reconsidered, and the reason is worth keeping: the merge READ pulls the target's contents into what Entire then writes, and the write is a rename, which replaces the user's link with a regular file rather than following it. Both happen silently. Refusing is the legible version of the same outcome.
The setup still works, because the thing that makes a link dangerous here is that it arrived with the checkout. A developer who wants one keeps it out of the repository — `git rm --cached .claude/settings.json` and a .gitignore entry — and manages it locally, which is what a dotfile workflow does anyway.
func OpenHookConfig ¶ added in v0.10.4
func OpenHookConfig(worktreeRoot, relPath string) (*HookConfigFile, error)
OpenHookConfig returns the hook-config file at relPath inside worktreeRoot.
relPath is slash-separated and relative to the worktree root (".cursor/hooks.json"). The directory does not need to exist: Read reports a missing file the way os.ReadFile does, and Write creates the parents.
func (*HookConfigFile) Exists ¶ added in v0.10.4
func (f *HookConfigFile) Exists() bool
Exists reports whether the file is present at a path Entire will read.
A symlinked parent directory counts as absent rather than as an error, since the signature has nowhere to put one. That is the useful answer: every caller uses this to choose between merging into an existing file and writing a fresh one, and Write refuses the same path with a message that names the link.
func (*HookConfigFile) GeneratedState ¶ added in v0.10.4
func (f *HookConfigFile) GeneratedState(marker, render string) HookConfigState
GeneratedState is GeneratedHookFileState for a file read through this root. See that function for what marker and render mean.
func (*HookConfigFile) Path ¶ added in v0.10.4
func (f *HookConfigFile) Path() string
Path is the file's absolute path, for messages and for the agent config that has to name it. It is not an invitation to do I/O on the result.
func (*HookConfigFile) Read ¶ added in v0.10.4
func (f *HookConfigFile) Read() ([]byte, error)
Read returns the file's contents. A missing file is reported unwrapped so callers can classify it with os.IsNotExist, which is how every agent's install path decides between "merge into existing" and "write fresh".
func (*HookConfigFile) Remove ¶ added in v0.10.4
func (f *HookConfigFile) Remove() error
Remove deletes the file. A file that is not there is not an error; a symlinked parent directory is, because the file this would delete is at the far end of a link Entire did not create.
func (*HookConfigFile) Root ¶ added in v0.10.4
func (f *HookConfigFile) Root() (*os.Root, string)
Root exposes the underlying root and the file's name inside it, for the callers that need a descriptor rather than the bytes. Both are Codex, which bounds .codex/hooks.json on its stat size before reading any of it: Read is an unbounded io.ReadAll, so it cannot express a limit, and the file arrives with the checkout. Everything else must use Read/Write/Remove.
The root is owned by the shared registry, so do not close it. Callers must go through the osroot no-follow primitives rather than resolving name with Root.Open, which is what Read does for them.
type HookConfigState ¶ added in v0.10.0
type HookConfigState int
HookConfigState describes how an agent's installed Entire hook config compares to what InstallHooks would write today.
const ( // HooksAbsent means Entire hooks are not installed for this agent here. HooksAbsent HookConfigState = iota // HooksCurrent means the installed hooks match what would be written today. HooksCurrent // HooksOutdated means Entire hooks are installed but stale — an older CLI // wrote a config that no longer matches the current one. Fix: // `entire enable --force`. HooksOutdated )
func GeneratedHookFileState ¶ added in v0.10.0
func GeneratedHookFileState(path, marker, render string) HookConfigState
GeneratedHookFileState reports hook-config drift for agents whose entire Entire integration is one generated file in the repo (Pi's extension, OpenCode's plugin), for use by HookFreshness implementations.
The file counts as ours only if it contains marker, and as current only if it matches render — what InstallHooks writes today. Pass exactly that one render: anything else still carrying the marker (a template edit, or a file left by an older version) must read as outdated so the next install replaces it. Passing a legacy render here would mark it current and strand it on disk forever, which is what TestCheckHookConfig_LegacyLocalDevIsDrift pins against. Matching on content rather than a stamped version keeps this honest for free — any edit to the embedded template makes installed copies read as outdated without anyone remembering to bump anything.
Line endings are normalized before comparing. These files are generated with LF but are typically committed, so on Windows a checkout under the default core.autocrlf=true hands us CRLF and byte equality never holds. That would report permanent drift the user cannot clear: InstallHooks writes LF back, and the next checkout converts it to CRLF again.
A file that exists but lacks marker reads as HooksAbsent, not HooksOutdated: InstallHooks refuses to overwrite a foreign file at our path, so there is no Entire config there for us to call stale.
type HookFreshness ¶ added in v0.10.0
type HookFreshness interface {
Agent
// CheckHookConfig reports whether this agent's Entire hook config is
// absent, current, or outdated in the current repo.
CheckHookConfig(ctx context.Context) HookConfigState
}
HookFreshness is implemented by hook-supporting agents that can report whether their installed config has drifted from the current one.
AreHooksInstalled answers "is Entire wired up here at all?" — for agents whose hook config is a generated file checked into the repo, that stays true forever even after the generated content goes stale, because the file is still present and still recognisably Entire's. CheckHookConfig answers the separate question "is what's installed still what we'd write today?", so `entire status` and `entire doctor` can flag a stale config instead of reporting it healthy while its hooks silently no-op.
Implementations must be read-only: they are diagnostics and must never modify the agent's config.
func AsHookFreshness ¶ added in v0.10.0
func AsHookFreshness(ag Agent) (HookFreshness, bool)
AsHookFreshness returns the agent as HookFreshness if it implements the interface. No capability declaration is needed: hook-config drift detection is built-in only, since it compares against a template the CLI itself embeds. External agents own their hook config and report installation state through their own protocol.
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.
func AsHookResponseWriter ¶ added in v0.5.0
func AsHookResponseWriter(ag Agent) (HookResponseWriter, bool)
AsHookResponseWriter returns the agent as HookResponseWriter if it both implements the interface and (for CapabilityDeclarer agents) has declared the capability.
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 force is true, removes existing Entire hooks before installing.
// Returns the number of hooks installed.
//
// Installed hook commands must always name the "entire" binary, never a
// path derived from repository content. Implementations recognize the
// legacy local-dev command shapes (see LegacyLocalDevHookScript) only so
// they can replace them.
InstallHooks(ctx context.Context, force bool) (int, error)
// UninstallHooks removes installed hooks
UninstallHooks(ctx context.Context) error
// AreHooksInstalled reports whether hooks are currently installed, and
// returns an error when the agent could not find out.
//
// The two are different answers and callers may act on the difference: "no
// hooks" means there is nothing to remove, while an error means the state is
// unknown and hooks may well be installed. Built-in agents read a local
// config file, where absent means absent, so they report no error. An
// external agent answers over a subprocess that can crash, time out, or
// print junk, and reports that as an error rather than as "no hooks".
AreHooksInstalled(ctx context.Context) (bool, error)
}
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
func AsHookSupport ¶ added in v0.5.0
func AsHookSupport(ag Agent) (HookSupport, bool)
AsHookSupport returns the agent as HookSupport if it both implements the interface and (for CapabilityDeclarer agents) has declared the capability.
type Launcher ¶ added in v0.6.1
Launcher is implemented by agents that `entire` can subprocess-spawn. This is used by `entire review` to start an agent with a pre-composed initial prompt; other commands may use it later.
Contract:
- LaunchCmd builds an *exec.Cmd with stdin/stdout/stderr wired to the caller's TTY. The agent runs in the foreground and the call blocks.
- The returned cmd is ready to Run() or Start(); it must NOT be modified by the caller except to set environment variables or working dir.
- initialPrompt is the first user message to send to the agent.
type ModelExtractor ¶ added in v0.7.0
type ModelExtractor interface {
Agent
// ExtractModel returns the model identifier from the transcript (e.g.
// "gpt-5.5"), or "" if none can be determined.
ExtractModel(transcriptData []byte) (string, error)
}
ModelExtractor extracts the LLM model identifier from a transcript for agents that do not report the model through lifecycle hooks. Pi, for example, records the model on every assistant message (message.model) but its hook events carry no model field, so the transcript is the only source. The framework calls this during condensation to backfill session state when the model is otherwise unknown.
func AsModelExtractor ¶ added in v0.7.0
func AsModelExtractor(ag Agent) (ModelExtractor, bool)
AsModelExtractor returns the agent as ModelExtractor if it implements the interface. No capability declaration is needed: transcript-based model extraction is a built-in-only fallback for agents whose hooks omit the model (e.g., Pi). External agents report the model through their own hook protocol.
type ModelInfo ¶ added in v0.7.8
type ModelInfo struct {
// ID is the value passed to the agent CLI's --model flag (an exact model
// identifier or a provider alias such as "sonnet").
ID string
// Note is an optional short human hint (e.g. "alias", "faster",
// "example") shown alongside the ID. It carries no behavior.
Note string
}
ModelInfo describes one model an agent can run via `--model`.
type ModelLister ¶ added in v0.7.8
type ModelLister interface {
Agent
// ListModels returns the advertised models for this agent. The list is
// advisory; callers must still allow arbitrary `--model` values.
ListModels(ctx context.Context) ([]ModelInfo, error)
}
ModelLister is an optional capability for agents that can advertise the models usable with `entire review --model`.
claude-code advertises a small curated list of real, valid aliases (opus/sonnet/haiku). Agents whose CLI has no enumeration command do not implement this interface at all; the picker then offers only Default + Custom, since `--model` ultimately accepts anything the agent CLI does.
func AsModelLister ¶ added in v0.7.8
func AsModelLister(ag Agent) (ModelLister, bool)
AsModelLister returns the agent as a ModelLister if it implements the capability. Unlike AsTextGenerator this does not consult CapabilityDeclarer: the model list is advisory only, so a plain type assertion is sufficient and keeps the external-agent capability protocol unchanged.
type PermissionConfigOwner ¶ added in v0.10.5
type PermissionConfigOwner interface {
Agent
// PermissionConfig returns the agent's permission-bearing config file for
// the current worktree. It does not create anything.
PermissionConfig(ctx context.Context) (*HookConfigFile, error)
}
PermissionConfigOwner is implemented by an agent that keeps Entire-managed entries in a JSON config with a top-level "permissions" object. It exists so the shared diagnostics and the retired-rule migration can inspect and repair that block without knowing where each agent keeps its config.
Implemented by Claude Code (.claude/settings.json) and Factory AI Droid (.factory/settings.json) — the two agents Entire ever wrote MetadataDenyRule into.
func AsPermissionConfigOwner ¶ added in v0.10.5
func AsPermissionConfigOwner(ag Agent) (PermissionConfigOwner, bool)
AsPermissionConfigOwner returns ag as a PermissionConfigOwner when it keeps a permissions block Entire may have written into.
type ProgressFn ¶ added in v0.9.0
type ProgressFn func(GenerationProgress)
ProgressFn receives streaming progress updates. It must not block — invoke it from the same goroutine that reads the stream and keep handlers fast.
type ProgressPhase ¶ added in v0.9.0
type ProgressPhase string
ProgressPhase identifies a coarse stage in streaming text generation.
const ( // PhaseConnecting is emitted once when the CLI signals it is making the upstream request. PhaseConnecting ProgressPhase = "connecting" // PhaseFirstToken is emitted once when the upstream responds with the first event, // carrying TTFT and input/cache token counts. PhaseFirstToken ProgressPhase = "first-token" // PhaseGenerating is emitted repeatedly as text or thinking deltas arrive. // OutputTokens carries a running estimate based on delta sizes. PhaseGenerating ProgressPhase = "generating" // PhaseDone is emitted once when the final result event is received without error. PhaseDone ProgressPhase = "done" )
type PromptExtractor ¶ added in v0.5.1
type PromptExtractor interface {
Agent
// ExtractPrompts returns user prompts from the transcript starting at the given offset.
ExtractPrompts(sessionRef string, fromOffset int) ([]string, error)
}
PromptExtractor extracts user prompts from a transcript file. Used as a fallback when prompt data isn't captured via hooks (e.g., Factory AI Droid's exec mode doesn't fire UserPromptSubmit).
func AsPromptExtractor ¶ added in v0.5.1
func AsPromptExtractor(ag Agent) (PromptExtractor, bool)
AsPromptExtractor returns the agent as PromptExtractor if it both implements the interface and (for CapabilityDeclarer agents) has declared TranscriptAnalyzer. ExtractPrompts is conceptually part of transcript analysis, so it shares the same capability gate — this prevents calling extract-prompts on external agent binaries that never declared transcript_analyzer support.
type ProtectedFilesProvider ¶ added in v0.5.6
type ProtectedFilesProvider interface {
Agent
// ProtectedFiles returns repo-root-relative files that belong to the
// agent's own config/state and should be excluded from tracking.
ProtectedFiles() []string
}
ProtectedFilesProvider is implemented by agents that need to exclude repo-root-relative files owned by the agent integration itself from session tracking or destructive operations.
type RestoredSessionPathResolver ¶ added in v0.5.4
type RestoredSessionPathResolver interface {
Agent
// ResolveRestoredSessionFile returns where Entire should write a restored
// transcript so the agent can discover it later.
ResolveRestoredSessionFile(sessionDir, agentSessionID string, transcript []byte) (string, error)
}
RestoredSessionPathResolver is implemented by agents that need a transcript-specific path when Entire reconstructs a session from checkpoint metadata. This is used for restored sessions only; live sessions still use the agent's native hook/session references.
type SessionBaseDirProvider ¶ added in v0.5.3
type SessionBaseDirProvider interface {
Agent
// GetSessionBaseDir returns the base directory containing per-project
// session subdirectories (e.g., ~/.claude/projects, ~/.gemini/tmp).
GetSessionBaseDir() (string, error)
}
SessionBaseDirProvider is implemented by agents that store transcripts in a home-directory-based structure with per-project subdirectories. This enables cross-project transcript search (e.g., when a session was started from a different working directory). Agents with ephemeral/temp-based storage or flat session layouts should NOT implement this interface.
func AsSessionBaseDirProvider ¶ added in v0.5.3
func AsSessionBaseDirProvider(ag Agent) (SessionBaseDirProvider, bool)
AsSessionBaseDirProvider returns the agent as SessionBaseDirProvider if it implements the interface. No capability declaration is needed since this is a built-in-only feature (external agents use the agent binary's own session resolution).
type SessionChange ¶
type SessionChange struct {
SessionID string
SessionRef string
EventType HookType
Timestamp time.Time
}
SessionChange represents detected session activity (for FileWatcher)
type SessionEndBudgeter ¶ added in v0.10.1
type SessionEndBudgeter interface {
Agent
// SessionEndBudget returns the wall-clock budget for the whole session-end
// hook invocation, measured from process start. A non-positive value means
// no budget applies.
SessionEndBudget() time.Duration
}
SessionEndBudgeter is implemented by agents whose host enforces a hard wall-clock budget on the session-end hook, because it runs inside the agent's own shutdown sequence rather than between turns.
Codex is the motivating case: it defaults SessionEnd handlers to a 1s timeout and clamps any configured value to 3s (SESSION_END_MAX_TIMEOUT_SEC in codex-rs/hooks/src/events/session_end.rs), keeping teardown inside app-server's five-second shutdown bound. On expiry it terminates the hook's entire process tree.
Declaring a budget makes Entire stop itself just short of that ceiling rather than being killed mid-write: the session is marked ENDED first (a single atomic state-file rename), and only the eager condense — which is fail-open — runs against the remaining budget. PostCommit handles sessions with pending files; doctor retries no-file ENDED sessions. Agents whose session-end hook has a normal timeout should not implement this.
func AsSessionEndBudgeter ¶ added in v0.10.1
func AsSessionEndBudgeter(ag Agent) (SessionEndBudgeter, bool)
AsSessionEndBudgeter returns the agent as SessionEndBudgeter if it implements the interface. Built-in only because no external agent needs it yet — not because external agents could enforce a budget themselves: a plugin supplies only parse-hook, while the work being bounded (endSessionNow, the condense) runs in the entire process. Widen DeclaredCaps with a session_end_budget field when an external agent has a host that clamps its session-end hook.
type SessionLocator ¶ added in v0.10.4
type SessionLocator interface {
Name() types.AgentName
GetSessionDir(repoPath string) (string, error)
ResolveSessionFile(sessionDir, agentSessionID string) string
}
SessionLocator is the slice of Agent a session store needs: where the agent keeps sessions for a repo, and how it names a session's file inside that directory. Narrow on purpose — a store has no business with transcripts, chunking, or hooks, and depending on three methods instead of thirty is what lets a test supply one without implementing the whole agent contract.
type SessionStore ¶ added in v0.10.4
type SessionStore struct {
// contains filtered or unexported fields
}
SessionStore is one agent's own session directory, held as an *os.Root.
Every agent keeps its transcripts somewhere Entire does not own — ~/.claude/projects/<hash>/, ~/.codex/sessions/, Cursor's SQLite store, ~/.pi/agent/sessions/<encoded-repo>/, a temp dir for OpenCode — and Entire reads and writes into all of them. The session ID that selects a file inside one comes from an agent hook payload, so this is where an untrusted name becomes a path, and a store gives each agent one handle to resolve it through.
It deliberately does NOT try to contain the agent's own writes: the agent is a separate process writing its own store. What it contains is Entire's reads and writes, which is the half Entire is responsible for.
func OpenSessionStore ¶ added in v0.10.4
func OpenSessionStore(ag SessionLocator, repoPath string) (*SessionStore, error)
OpenSessionStore returns ag's session store for repoPath.
The directory need NOT exist yet: resolving a session file is path arithmetic plus a containment check, and callers legitimately ask where a transcript *would* live before anything has created the directory. Only the I/O methods need the directory, and they open a root at that point and close it again — see openRoot for why a store is not one of the memoized anchors.
func OpenSessionStoreAt ¶ added in v0.10.4
func OpenSessionStoreAt(ag SessionLocator, dir string) (*SessionStore, error)
OpenSessionStoreAt is OpenSessionStore for a session directory the caller already resolved — the cross-project fallbacks that scan sibling directories, and agentimport, which is handed one.
func (*SessionStore) Dir ¶ added in v0.10.4
func (s *SessionStore) Dir() string
Dir returns the store's absolute directory. It is for messages and for the paths Entire hands to other processes (an agent's own resume command, a transcript path recorded as a checkpoint's SessionRef); it is not an invitation to do I/O on the result.
func (*SessionStore) Exists ¶ added in v0.10.4
func (s *SessionStore) Exists(name string) bool
Exists reports whether name is present in the store. Lstat, not Stat: a dangling symlink is still a file that exists and must not be overwritten silently (see the rewind restore path, which distinguishes the two).
func (*SessionStore) Name ¶ added in v0.10.4
func (s *SessionStore) Name(p string) (string, error)
Name converts a path into a name inside the store, reporting ErrOutsideSessionStore for one that is not. A path already relative to the store is accepted as-is.
func (*SessionStore) ReadFile ¶ added in v0.10.4
func (s *SessionStore) ReadFile(name string) ([]byte, error)
ReadFile reads name from the store.
func (*SessionStore) SessionFile ¶ added in v0.10.4
func (s *SessionStore) SessionFile(agentSessionID string) (name, absPath string, err error)
SessionFile resolves agentSessionID to a name inside the store, and to the absolute path of the same file.
This is the check the whole type exists for. The agent decides its own file layout via ResolveSessionFile — some nest, some append an extension — and the ID reaching it came from a hook payload. Converting the result back into a name relative to the store rejects an ID that walked out of the directory, which a plain filepath.Join would have produced silently.
type SidecarImageProvider ¶ added in v0.9.0
type SidecarImageProvider interface {
Agent
// SidecarImages returns images stored outside the transcript for the session
// identified by sessionRef (the transcript path).
SidecarImages(ctx context.Context, sessionRef string) ([]CompactedTranscriptAsset, error)
}
SidecarImageProvider is implemented by agents that keep images OUTSIDE the transcript Entire condenses — e.g. Cursor stores pasted images in a per-session SQLite blob store, not the JSONL transcript. The strategy layer calls this during condensation/finalize to capture those images as checkpoint assets so they're preserved with the session. Best-effort: returns nil (no error) when the sidecar store is unavailable or unreadable.
func AsSidecarImageProvider ¶ added in v0.9.0
func AsSidecarImageProvider(ag Agent) (SidecarImageProvider, bool)
AsSidecarImageProvider returns the agent as SidecarImageProvider if it implements the interface. This is a best-effort, optional capability (image capture from a store outside the transcript, e.g. Cursor's SQLite blob store), so it resolves by type assertion alone with no DeclaredCaps gate.
type SkillDiscoverer ¶ added in v0.6.1
type SkillDiscoverer interface {
Agent
DiscoverReviewSkills(ctx context.Context) ([]DiscoveredSkill, error)
}
SkillDiscoverer is implemented by agents that can enumerate review-adjacent skills installed locally on disk (e.g. plugin skills under ~/.claude/plugins/...). This powers the "Installed plugin skills" section of the `entire review` picker and the runtime verification that configured skills still exist before spawn.
Contract:
- Safe to call on fresh installs where no plugin dir exists yet — return (nil, nil), not an error.
- Malformed individual skill metadata must be skipped with a Debug log, not propagated as an error.
- A (nil, non-nil) error means "discovery could not run at all" (e.g. home dir inaccessible). Callers may treat all errors as "found nothing" and log at Debug — discovery must never block the picker.
type SkillEvent ¶ added in v0.7.0
type SkillEvent = types.SkillEvent
func AppendPromptSlashCommandSkillEvent ¶ added in v0.7.4
func AppendPromptSlashCommandSkillEvent(events []SkillEvent, agentName, prompt string, timestamp time.Time) []SkillEvent
AppendPromptSlashCommandSkillEvent adds a generic prompt-invocation skill event for a "/<command>" prompt. If an agent adapter already surfaced an equivalent prompt skill event (for example Pi's pre-expansion input event), the adapter event wins and no generic duplicate is appended.
func ExtractSkillEvents ¶ added in v0.7.0
func ExtractSkillEvents(ctx context.Context, ag Agent, transcriptData []byte, fromOffset int) []SkillEvent
ExtractSkillEvents extracts normalized skill events from transcript data. Returns nil if the agent does not support skill-event extraction or extraction fails.
func SkillEventFromPromptSlashCommand ¶ added in v0.7.4
func SkillEventFromPromptSlashCommand(agentName, prompt string, timestamp time.Time) (SkillEvent, bool)
SkillEventFromPromptSlashCommand returns a skill event for a prompt beginning with a "/<command>" slash command. A recorded prompt only contains a slash command that was submitted as a turn, so runtime/UI-only commands (/mcp, /model, ...) are naturally absent; pasted filesystem paths are rejected.
Only the command token is stored, never the prompt body. Tool-call skills (e.g. Claude Code's Skill tool) are captured separately by SkillEventExtractors as "tool_invocation" events.
type SkillEventCollapse ¶ added in v0.7.0
type SkillEventCollapse = types.SkillEventCollapse
type SkillEventExtractor ¶ added in v0.7.0
type SkillEventExtractor interface {
ExtractSkillEvents(transcriptData []byte, fromOffset int) ([]SkillEvent, error)
}
SkillEventExtractor is implemented by agents that can derive native skill events from their transcript format.
func AsSkillEventExtractor ¶ added in v0.7.0
func AsSkillEventExtractor(ag Agent) (SkillEventExtractor, bool)
AsSkillEventExtractor returns the agent as SkillEventExtractor if it implements the interface. Skill-event extraction is currently built-in only; external agents do not expose this optional interface through declared capabilities.
type SkillEventSkill ¶ added in v0.7.0
type SkillEventSkill = types.SkillEventSkill
type SkillEventSource ¶ added in v0.7.0
type SkillEventSource = types.SkillEventSource
type SkillEventTranscriptAnchor ¶ added in v0.7.0
type SkillEventTranscriptAnchor = types.SkillEventTranscriptAnchor
type StreamingTextGenerator ¶ added in v0.9.0
type StreamingTextGenerator interface {
Agent
// GenerateTextStreaming invokes the agent's streaming text generation and
// calls progress for each phase update. progress may be nil to suppress
// reporting. The returned string is the final response text.
GenerateTextStreaming(ctx context.Context, prompt, model string, progress ProgressFn) (string, error)
}
StreamingTextGenerator is an optional interface for text generators whose underlying CLI exposes a streaming output mode. Callers can use AsStreamingTextGenerator to detect support and fall back to plain GenerateText when unavailable.
func AsStreamingTextGenerator ¶ added in v0.9.0
func AsStreamingTextGenerator(ag Agent) (StreamingTextGenerator, bool)
AsStreamingTextGenerator returns the agent as StreamingTextGenerator if it both implements the interface and (for CapabilityDeclarer agents) has declared the capability.
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
// (an empty subagentsDir skips subagent accounting and leaves SubagentTokens nil).
//
// CONTRACT — the returned SubagentTokens is a CUMULATIVE-SINCE-SESSION-START
// snapshot, NOT a delta scoped to fromOffset like the main-agent fields
// (InputTokens/OutputTokens/...). Implementations MUST discover spawned agent
// IDs from the FULL transcript prefix [0,end) — so a subagent spawned before
// fromOffset is still found (#329) — and re-read each subagent transcript from
// line 0 on every call. Consequently a subagent's full total repeats on every
// call after it is first discovered.
//
// Callers that accumulate across checkpoints/turns therefore MUST NOT sum
// SubagentTokens across calls: replace the running total with the latest
// snapshot, and rescope any window delta by subtracting a previously captured
// baseline (see accumulateTokenUsage / resetCheckpointWindow and
// session.State.SubagentTokensBaseline in cmd/entire/cli/strategy, and
// rescopeSubagentTokensToDeltas in cmd/entire/cli/agentimport for the import
// path). An implementation that instead returned per-window deltas would
// silently break that accounting with no compile-time or test signal.
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.
func AsSubagentAwareExtractor ¶ added in v0.5.0
func AsSubagentAwareExtractor(ag Agent) (SubagentAwareExtractor, bool)
AsSubagentAwareExtractor returns the agent as SubagentAwareExtractor if it both implements the interface and (for CapabilityDeclarer agents) has declared the capability.
type SubagentSessionLink ¶ added in v0.10.1
type SubagentSessionLink struct {
// ParentSessionID is the session that invoked the task tool.
ParentSessionID string
// ToolUseID is the parent's tool-use ID for this invocation. It keys the
// task checkpoint's metadata directory, so it must be stable across hooks.
ToolUseID string
// ParentTranscriptPath locates the parent session's transcript, which the
// task checkpoint stores alongside the subagent's own. Empty when the agent
// cannot resolve it; the checkpoint is then written without it.
ParentTranscriptPath string
// SubagentType is the kind of subagent (e.g. "worker"); may be empty.
SubagentType string
// TaskDescription is a short human-readable task label; may be empty.
TaskDescription string
}
SubagentSessionLink identifies the parent task invocation that spawned a subagent session. It is resolved from the subagent's own transcript, so it stays valid regardless of when the parent's tool hooks fire.
type SubagentSessionResolver ¶ added in v0.10.1
type SubagentSessionResolver interface {
Agent
// ResolveSubagentSession reports whether the session behind sessionRef was
// spawned by a parent task invocation, and if so identifies the parent.
// Returns false for ordinary top-level sessions and whenever the link
// cannot be read — callers treat a failure as "not a subagent session".
ResolveSubagentSession(sessionRef string) (SubagentSessionLink, bool)
}
SubagentSessionResolver is implemented by agents that run subagents as full sessions of their own — with their own SessionStart/UserPromptSubmit/Stop hooks — rather than as a blocking tool call inside the parent's turn.
For such agents the parent's post-tool hook cannot delimit the subagent's work: it fires when the task is *dispatched*, not when it completes, so the worktree is still untouched at that point. Turn-end consults this instead, to recognize a subagent session and attribute its work to the parent as a task checkpoint rather than minting an unrelated top-level session checkpoint.
Agents whose subagents block the parent turn (Claude Code's Task tool) must NOT implement this — their SubagentEnd path already bounds the work correctly.
func AsSubagentSessionResolver ¶ added in v0.10.1
func AsSubagentSessionResolver(ag Agent) (SubagentSessionResolver, bool)
AsSubagentSessionResolver returns the agent as SubagentSessionResolver if it implements the interface. No capability declaration is needed: whether an agent's subagents run as sessions of their own is a property of the agent's own hook protocol, which only built-in agents model. External agents report subagent boundaries through their own hooks.
type TestOnly ¶ added in v0.5.0
TestOnly is implemented by agents that exist solely for testing (e.g., the Vogon canary agent). These agents are excluded from the user-facing agent selection in `entire enable`.
type TextCommandRunner ¶ added in v0.5.6
TextCommandRunner matches exec.CommandContext and allows tests to inject a runner.
type TextGenerationError ¶ added in v0.9.0
TextGenerationError carries captured subprocess output alongside a TextGenerator's error so the explain layer can build a meaningful timeout diagnostic ("provider produced no output" vs "was generating output when killed"). Wraps the original error so errors.As against the inner type (e.g. *ClaudeError) keeps working.
func (*TextGenerationError) Error ¶ added in v0.9.0
func (e *TextGenerationError) Error() string
func (*TextGenerationError) Unwrap ¶ added in v0.9.0
func (e *TextGenerationError) Unwrap() error
type TextGenerator ¶ added in v0.5.0
type TextGenerator interface {
Agent
// GenerateText sends a prompt to the agent's CLI and returns the raw text response.
// model is a hint (e.g., "haiku", "sonnet"). Implementations may ignore if not applicable.
GenerateText(ctx context.Context, prompt string, model string) (string, error)
}
TextGenerator is an optional interface for agents whose CLI supports non-interactive text generation (e.g., claude --print). Used for AI-powered metadata generation (trail titles, summaries).
func AsTextGenerator ¶ added in v0.5.0
func AsTextGenerator(ag Agent) (TextGenerator, bool)
AsTextGenerator returns the agent as TextGenerator if it both implements the interface and (for CapabilityDeclarer agents) has declared the capability.
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.
func AsTokenCalculator ¶ added in v0.5.0
func AsTokenCalculator(ag Agent) (TokenCalculator, bool)
AsTokenCalculator returns the agent as TokenCalculator if it both implements the interface and (for CapabilityDeclarer agents) has declared the capability.
type TokenUsage ¶
type TokenUsage = types.TokenUsage
TokenUsage is defined in the leaf agent/types package so the checkpoint contract can reference it without importing the full agent package. The alias keeps existing agent.TokenUsage references working.
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 ToolInvocation ¶ added in v0.10.3
type ToolInvocation struct {
// Tool is the agent-native tool name, e.g. "Bash", "Agent", "Task".
Tool string
// Command is the shell command for shell tools, empty otherwise.
Command string
// SubagentType is the dispatched subagent's name for subagent-spawning
// tools, empty otherwise.
SubagentType string
}
ToolInvocation is one tool call recorded in a transcript, reduced to the fields callers ask operational questions about ("did this session run X?"). Content-free by construction: no prompts, no file contents, no tool output. Command is the raw shell command, which is user content — callers must treat it as something to match against, never as something to store or transmit.
type ToolInvocationScanner ¶ added in v0.10.3
type ToolInvocationScanner interface {
// ScanToolInvocations calls visit for each recorded tool invocation and
// returns true as soon as visit does, stopping the walk.
//
// hints is a PERFORMANCE contract, not a semantic filter: an implementation
// may skip parsing any transcript line whose raw bytes contain none of the
// hints. A caller must therefore pass hints that every invocation it can
// possibly match is guaranteed to contain literally, or pass nil to visit
// everything. Passing a hint that its own matcher can accept without is a
// silent false negative, so callers should pin the relationship with a test.
ScanToolInvocations(transcriptData []byte, hints [][]byte, visit func(ToolInvocation) bool) bool
}
ToolInvocationScanner is implemented by agents whose transcript records tool calls structurally, so a caller can ask "did this session invoke X" instead of substring-probing the transcript and matching every mention of X.
An agent with no walker here MUST NOT implement this. Not implementing it is the honest answer, and the dispatcher below turns it into a reportable "cannot tell": callers are required to distinguish that from "did not run", because a fabricated "did not run" is indistinguishable from a real one in aggregate.
"No walker yet" is the only reason any agent is absent — do not read it as "impossible". Cursor in particular shares this JSONL shape (see the transcript package doc) and does record tool_use blocks; the "contains no tool_use blocks" comments in cmd/entire/cli/agent/cursor date to 2026-03, are pinned by no test, and are stale. What blocks a Cursor implementation is narrower: its input key for a shell command is unconfirmed, so reusing ToolInvocation.Command would risk the very false negative this interface exists to prevent. A name-based matcher (subagent dispatch) would work today.
func AsToolInvocationScanner ¶ added in v0.10.3
func AsToolInvocationScanner(ag Agent) (ToolInvocationScanner, bool)
AsToolInvocationScanner returns the agent as ToolInvocationScanner if it implements the interface. Built-in only: reading tool calls out of a transcript needs knowledge of that transcript's shape, which an external agent's parse-hook does not convey.
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)
}
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.
func AsTranscriptAnalyzer ¶ added in v0.5.0
func AsTranscriptAnalyzer(ag Agent) (TranscriptAnalyzer, bool)
AsTranscriptAnalyzer returns the agent as TranscriptAnalyzer if it both implements the interface and (for CapabilityDeclarer agents) has declared the capability.
type TranscriptCompactor ¶ added in v0.5.6
type TranscriptCompactor interface {
Agent
// CompactTranscript converts the transcript referenced by sessionRef into
// Entire Transcript Format and returns the compact transcript bytes.
CompactTranscript(ctx context.Context, sessionRef string) (*CompactedTranscript, error)
}
TranscriptCompactor is implemented by agents that can produce Entire Transcript Format directly from their native transcript representation.
func AsTranscriptCompactor ¶ added in v0.5.6
func AsTranscriptCompactor(ag Agent) (TranscriptCompactor, bool)
AsTranscriptCompactor returns the agent as TranscriptCompactor if it both implements the interface and (for CapabilityDeclarer agents) has declared the capability.
type TranscriptFetcher ¶ added in v0.10.3
type TranscriptFetcher interface {
Agent
// FetchTranscript writes the session's transcript to the agent's cache
// location and returns its path. Errors may be shown to users after other
// transcript sources fail, so they must be concise and safe to display.
FetchTranscript(ctx context.Context, sessionID string) (string, error)
}
TranscriptFetcher is implemented by agents that can materialize a session transcript on demand (e.g. OpenCode via `opencode export`), including for sessions Entire never tracked — where no hook-cached transcript file exists (e.g. sessions spawned by an external host rather than a hooked terminal). TranscriptPreparer, by contrast, only refreshes an already-existing file.
func AsTranscriptFetcher ¶ added in v0.10.3
func AsTranscriptFetcher(ag Agent) (TranscriptFetcher, bool)
AsTranscriptFetcher returns the agent as TranscriptFetcher if it implements the interface. This is an optional capability (materializing a transcript on demand for sessions with no hook-cached file), so it resolves by type assertion alone with no DeclaredCaps gate.
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.
func AsTranscriptPreparer ¶ added in v0.5.0
func AsTranscriptPreparer(ag Agent) (TranscriptPreparer, bool)
AsTranscriptPreparer returns the agent as TranscriptPreparer if it both implements the interface and (for CapabilityDeclarer agents) has declared the capability.
type TranscriptSanitizer ¶ added in v0.10.0
type TranscriptSanitizer interface {
Agent
// SanitizeTranscriptForStorage returns the transcript with non-portable state
// removed. It must return the input unchanged rather than nil when it cannot
// parse the transcript, so a sanitizer failure never loses the session.
SanitizeTranscriptForStorage(data []byte) []byte
}
TranscriptSanitizer is implemented by agents whose native transcript format carries state that Entire must not keep in its own copy — e.g. Codex rollouts embed encrypted reasoning payloads and compaction blobs that are bound to the originating session and cannot be replayed out of a checkpoint.
Entire always leaves the agent's own transcript untouched; this transform applies only to the copy Entire stores. Sanitizing before redaction is what keeps non-replayable payloads out of storage AND keeps the redaction layers from scanning megabytes of ciphertext they would only discard afterwards (base64 is the pathological input for the entropy layer).
Implementations must be pure byte transforms: idempotent (sanitizing an already-sanitized transcript is a no-op), safe to call from hooks, and never dependent on the agent process being alive.
func AsTranscriptSanitizer ¶ added in v0.10.0
func AsTranscriptSanitizer(ag Agent) (TranscriptSanitizer, bool)
AsTranscriptSanitizer returns the agent as TranscriptSanitizer if it implements the interface. This is a pure local byte transform with no external process to negotiate with, so it needs no DeclaredCaps gate.
type WarningFormat ¶ added in v0.5.6
type WarningFormat int
const ( WarningFormatSingleLine WarningFormat = iota + 1 WarningFormatMultiLine )
Source Files
¶
- agent.go
- capabilities.go
- chunking.go
- event.go
- foreground.go
- hook_command.go
- hook_config_file.go
- inject.go
- model_lister.go
- permissions.go
- registry.go
- resume_command.go
- session.go
- session_store.go
- skill_events.go
- skill_events_extract.go
- skill_events_prompt.go
- testing.go
- text_generator_cli.go
- token_usage.go
- tool_invocations.go
- transcript_file.go
- types.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package claudecode implements the Agent interface for Claude Code.
|
Package claudecode implements the Agent interface for Claude Code. |
|
Package codex implements the Agent interface for OpenAI's Codex CLI.
|
Package codex implements the Agent interface for OpenAI's Codex CLI. |
|
Package copilotcli implements the Agent interface for GitHub Copilot CLI.
|
Package copilotcli implements the Agent interface for GitHub Copilot CLI. |
|
Package cursor implements the Agent interface for Cursor.
|
Package cursor implements the Agent interface for Cursor. |
|
Package external provides an adapter that bridges external agent binaries (discovered via PATH as entire-agent-<name>) to the agent.Agent interface.
|
Package external provides an adapter that bridges external agent binaries (discovered via PATH as entire-agent-<name>) to the agent.Agent interface. |
|
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 pi implements the Agent interface for the pi coding agent (https://github.com/earendil-works/pi-mono).
|
Package pi implements the Agent interface for the pi coding agent (https://github.com/earendil-works/pi-mono). |
|
pijsonl
Package pijsonl provides shared parsing primitives for Pi's session JSONL format.
|
Package pijsonl provides shared parsing primitives for Pi's session JSONL format. |
|
Package skilldiscovery holds the per-agent registries (curated built-ins, install hints) and the keyword match helper that the `entire review` picker uses to discover review-adjacent skills.
|
Package skilldiscovery holds the per-agent registries (curated built-ins, install hints) and the keyword match helper that the `entire review` picker uses to discover review-adjacent skills. |
|
Package spawn provides the Spawner interface used by both `entire review` and `entire investigate` to start an agent process non-interactively.
|
Package spawn provides the Spawner interface used by both `entire review` and `entire investigate` to start an agent process non-interactively. |
|
Package testutil provides shared test utilities for agent packages.
|
Package testutil provides shared test utilities for agent packages. |
|
Package vogon implements the Agent interface for a deterministic test agent used as an E2E canary.
|
Package vogon implements the Agent interface for a deterministic test agent used as an E2E canary. |