shared

package
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Mar 26, 2026 License: MIT Imports: 33 Imported by: 0

Documentation

Overview

Package shared provides common flag types and options for loop subcommands.

Index

Constants

View Source
const (
	WorkTypeImplementation = "IMPLEMENTATION"
	WorkTypeTesting        = "TESTING"
	WorkTypeDocumentation  = "DOCUMENTATION"
	WorkTypeRefactoring    = "REFACTORING"
)

Work type constants.

View Source
const (
	EventStop         = "Stop"
	EventSessionStart = "SessionStart"
	EventPreToolUse   = "PreToolUse"
	EventPostToolUse  = "PostToolUse"
	EventNotification = "Notification"
)

Hook event names matching Claude Code settings.json.

View Source
const (
	HandlerCommand = "command"
	HandlerPrompt  = "prompt"
	HandlerAgent   = "agent"
)

Hook handler types matching Claude Code settings.json.

View Source
const (
	HookScriptDir       = "/tmp/clawker-hooks"
	StopCheckScriptPath = "/tmp/clawker-hooks/stop-check.js"
)

Script paths inside the container.

View Source
const (
	// ReadyLogPrefix is the prefix for the ready signal log line.
	ReadyLogPrefix = "[clawker] ready"

	// ErrorLogPrefix is the prefix for error signal log lines.
	ErrorLogPrefix = "[clawker] error"
)
View Source
const (
	DefaultMaxLoops                  = 50
	DefaultStagnationThreshold       = 3
	DefaultTimeoutMinutes            = 15
	DefaultCallsPerHour              = 100
	DefaultCompletionThreshold       = 2
	DefaultSessionExpirationHours    = 24
	DefaultSameErrorThreshold        = 5
	DefaultOutputDeclineThreshold    = 70
	DefaultMaxConsecutiveTestLoops   = 3
	DefaultSafetyCompletionThreshold = 5 // Force exit after N consecutive loops with completion indicators
	DefaultLoopDelaySeconds          = 3 // Seconds to wait between loop iterations
)

Default configuration values.

View Source
const (
	SystemSubtypeInit            = "init"
	SystemSubtypeCompactBoundary = "compact_boundary"
)

System event subtypes.

View Source
const (
	ResultSubtypeSuccess              = "success"
	ResultSubtypeErrorMaxTurns        = "error_max_turns"
	ResultSubtypeErrorDuringExecution = "error_during_execution"
	ResultSubtypeErrorMaxBudget       = "error_max_budget_usd"
)

Result event subtypes.

View Source
const (
	ContentTypeText       = "text"
	ContentTypeToolUse    = "tool_use"
	ContentTypeToolResult = "tool_result"
	ContentTypeThinking   = "thinking"
)

Content block types within messages.

View Source
const LoopStatusInstructions = `` /* 1167-byte string literal not displayed */

LoopStatusInstructions is the default system prompt that instructs the agent to output a structured LOOP_STATUS block at the end of each response. This block is parsed by the loop engine for circuit breaker and progress tracking.

The example block within this prompt is intentionally valid — it is used in tests to verify that the prompt stays in sync with ParseStatus.

View Source
const (
	// MaxHistoryEntries is the maximum number of history entries to keep.
	MaxHistoryEntries = 50
)
View Source
const StatusBlocked = "BLOCKED"

StatusBlocked indicates the agent is blocked.

View Source
const StatusComplete = "COMPLETE"

StatusComplete indicates the task is complete.

View Source
const StatusPending = "IN_PROGRESS"

StatusPending indicates work is still in progress.

View Source
const TestsFailing = "FAILING"

TestsFailing indicates tests are failing.

View Source
const TestsNotRun = "NOT_RUN"

TestsNotRun indicates tests were not run.

View Source
const TestsPassing = "PASSING"

TestsPassing indicates tests are passing.

Variables

This section is empty.

Functions

func AddLoopFlags

func AddLoopFlags(cmd *cobra.Command, opts *LoopOptions)

AddLoopFlags registers shared loop flags on the Cobra command. Call this before AddFormatFlags to ensure correct PreRunE chain ordering.

func ApplyLoopConfigDefaults

func ApplyLoopConfigDefaults(loopOpts *LoopOptions, flags *pflag.FlagSet, cfg *config.LoopConfig)

ApplyLoopConfigDefaults applies config.LoopConfig values to LoopOptions fields that are consumed before the runner is created (e.g., hooks_file, append_system_prompt). Call this after loading the config but before SetupLoopContainer and BuildRunnerOptions.

func BuildSystemPrompt

func BuildSystemPrompt(additional string) string

BuildSystemPrompt combines the default LOOP_STATUS instructions with optional additional instructions provided by the user via --append-system-prompt. If additional is empty, returns only the default instructions.

func CountCompletionIndicators

func CountCompletionIndicators(output string) int

CountCompletionIndicators counts how many completion phrases appear in the output.

func DefaultHookFiles

func DefaultHookFiles() map[string][]byte

DefaultHookFiles returns the scripts that default hooks reference. Keys are absolute paths inside the container; values are file contents.

func DetectRateLimitError

func DetectRateLimitError(output string) bool

DetectRateLimitError checks if the output contains Claude's rate limit error. Returns true if a rate limit error is detected.

func ExtractErrorSignature

func ExtractErrorSignature(output string) string

ExtractErrorSignature extracts a normalized error signature from output. The signature can be used to detect repeated identical errors. Returns empty string if no error pattern is found.

func GenerateAgentName

func GenerateAgentName() string

GenerateAgentName creates a unique agent name for a loop session. Format: loop-<adjective>-<noun> (e.g., "loop-brave-turing"). The generated name is always a valid Docker resource name.

func InjectLoopHooks

func InjectLoopHooks(ctx context.Context, cfgGateway config.Config, containerID string, hooksFile string, copyFn containershared.CopyToContainerFn, readFn containershared.CopyFromContainerFn, log *logger.Logger) error

InjectLoopHooks injects hook configuration and scripts into a created (not started) container. If hooksFile is empty, default hooks are used. If provided, the file is read as a complete replacement. Hook scripts referenced by default hooks are also injected.

Hooks are merged into the existing settings.json to preserve any pre-existing configuration (MCP servers, skills, extensions) set up by containerfs or post-init.

func MakeCreateContainerFunc

func MakeCreateContainerFunc(cfg *LoopContainerConfig) func(context.Context) (*ContainerStartConfig, error)

MakeCreateContainerFunc creates a factory closure that creates a new container for each loop iteration. The returned containers are created with hooks injected but NOT started — the Runner's StartContainer handles attachment and start.

func MarkVerboseExclusive

func MarkVerboseExclusive(cmd *cobra.Command)

MarkVerboseExclusive marks --verbose as mutually exclusive with output format flags. Must be called after both AddLoopFlags and AddFormatFlags.

func NewTextAccumulator

func NewTextAccumulator() (*TextAccumulator, *StreamHandler)

NewTextAccumulator creates a TextAccumulator and returns it along with a StreamHandler wired to collect text. Pass the handler to ParseStream.

func ResolveLoopImage

func ResolveLoopImage(ctx context.Context, client *docker.Client, ios *iostreams.IOStreams, loopOpts *LoopOptions, projectName string) (string, error)

ResolveLoopImage resolves the container image for loop execution. If an image is explicitly set on loopOpts, it's returned as-is. Otherwise, the Docker client's image resolution chain is used.

func ResolvePrompt

func ResolvePrompt(prompt, promptFile string) (string, error)

ResolvePrompt resolves the prompt from either an inline string or a file path. Exactly one of prompt or promptFile should be non-empty.

func ResolveTasksPrompt

func ResolveTasksPrompt(tasksFile, taskPrompt, taskPromptFile string) (string, error)

ResolveTasksPrompt builds the prompt for a task-driven loop. It reads the tasks file and combines it with either:

  • the default template (if no custom prompt/file given)
  • a custom inline template (taskPrompt)
  • a custom template file (taskPromptFile)

Templates containing %s get the tasks substituted in; otherwise tasks are appended.

func WireLoopDashboard

func WireLoopDashboard(opts *Options, ch chan<- LoopDashEvent, setup *LoopContainerResult, maxLoops int, log *logger.Logger)

WireLoopDashboard sets Runner callback options to send events on the dashboard channel. It configures OnLoopStart, OnLoopEnd, and OnOutput callbacks. It does NOT close the channel — the goroutine wrapping runner.Run() is responsible for that.

func WriteResult

func WriteResult(out, errOut io.Writer, result *Result, format *cmdutil.FormatFlags) error

WriteResult writes the loop result in the appropriate format.

Types

type AnalysisResult

type AnalysisResult struct {
	Status          *Status
	RateLimitHit    bool
	ErrorSignature  string
	OutputSize      int
	CompletionCount int

	// Stream metadata (populated by AnalyzeStreamResult, zero when using AnalyzeOutput).
	NumTurns     int
	TotalCostUSD float64
	DurationMS   int
}

AnalysisResult contains the full analysis of a loop's output.

func AnalyzeOutput

func AnalyzeOutput(output string) *AnalysisResult

AnalyzeOutput performs full analysis of a loop's output.

func AnalyzeStreamResult

func AnalyzeStreamResult(text string, result *ResultEvent) *AnalysisResult

AnalyzeStreamResult produces an AnalysisResult by combining text analysis (from TextAccumulator output) with stream ResultEvent metadata. Use this instead of AnalyzeOutput when processing --output-format stream-json output.

The text parameter should contain the concatenated assistant text from TextAccumulator.Text(). The result parameter is the terminal ResultEvent from ParseStream (may be nil if the stream ended prematurely).

type AssistantEvent

type AssistantEvent struct {
	Type            EventType        `json:"type"`
	UUID            string           `json:"uuid,omitempty"`
	SessionID       string           `json:"session_id"`
	ParentToolUseID *string          `json:"parent_tool_use_id"`
	Message         AssistantMessage `json:"message"`
}

AssistantEvent is a complete assistant message containing text and/or tool invocations.

type AssistantMessage

type AssistantMessage struct {
	ID         string         `json:"id"`
	Role       string         `json:"role"`
	Model      string         `json:"model"`
	StopReason string         `json:"stop_reason"`
	Content    []ContentBlock `json:"content"`
	Usage      *TokenUsage    `json:"usage,omitempty"`
}

AssistantMessage is the Anthropic API message object embedded in an AssistantEvent.

func (*AssistantMessage) ExtractText

func (m *AssistantMessage) ExtractText() string

ExtractText returns the concatenated text from all text content blocks.

func (*AssistantMessage) ToolUseBlocks

func (m *AssistantMessage) ToolUseBlocks() []ContentBlock

ToolUseBlocks returns all tool_use content blocks from the message.

type CircuitBreaker

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

CircuitBreaker detects stagnation in loop iterations and trips when unhealthy patterns persist. Trip conditions (any one triggers a trip):

  • Stagnation: N consecutive loops without progress (threshold)
  • No LOOP_STATUS: N consecutive loops where status block is missing (threshold)
  • Same error: N identical error signatures in a row (sameErrorThreshold)
  • Output decline: Output size shrinks >= threshold% for 2 consecutive loops
  • Test-only loops: N consecutive TESTING-only work type loops (maxConsecutiveTestLoops)
  • Safety completion: N consecutive loops with completion indicators but no EXIT_SIGNAL (safetyCompletionThreshold)
  • Blocked status: Trips immediately when agent reports BLOCKED

func NewCircuitBreaker

func NewCircuitBreaker(threshold int) *CircuitBreaker

NewCircuitBreaker creates a new circuit breaker with the given threshold.

func NewCircuitBreakerWithConfig

func NewCircuitBreakerWithConfig(cfg CircuitBreakerConfig) *CircuitBreaker

NewCircuitBreakerWithConfig creates a circuit breaker with full configuration.

func (*CircuitBreaker) Check

func (cb *CircuitBreaker) Check() bool

Check returns true if the circuit is still open (not tripped).

func (*CircuitBreaker) ConsecutiveTestLoops

func (cb *CircuitBreaker) ConsecutiveTestLoops() int

ConsecutiveTestLoops returns the count of consecutive test-only loops.

func (*CircuitBreaker) IsTripped

func (cb *CircuitBreaker) IsTripped() bool

IsTripped returns true if the circuit has tripped.

func (*CircuitBreaker) NoProgressCount

func (cb *CircuitBreaker) NoProgressCount() int

NoProgressCount returns the current count of loops without progress.

func (*CircuitBreaker) Reset

func (cb *CircuitBreaker) Reset()

Reset clears the circuit breaker state.

func (*CircuitBreaker) RestoreState

func (cb *CircuitBreaker) RestoreState(state CircuitBreakerState)

RestoreState restores circuit breaker state from persistence.

func (*CircuitBreaker) SameErrorCount

func (cb *CircuitBreaker) SameErrorCount() int

SameErrorCount returns the current count of same-error loops.

func (*CircuitBreaker) State

func (cb *CircuitBreaker) State() CircuitBreakerState

State returns the current circuit breaker state for persistence.

func (*CircuitBreaker) Threshold

func (cb *CircuitBreaker) Threshold() int

Threshold returns the configured stagnation threshold.

func (*CircuitBreaker) TripReason

func (cb *CircuitBreaker) TripReason() string

TripReason returns the reason the circuit tripped, or empty if not tripped.

func (*CircuitBreaker) Update

func (cb *CircuitBreaker) Update(status *Status) (tripped bool, reason string)

Update evaluates the status and updates the circuit breaker state. Returns true and a reason if the circuit has tripped.

func (*CircuitBreaker) UpdateWithAnalysis

func (cb *CircuitBreaker) UpdateWithAnalysis(status *Status, analysis *AnalysisResult) UpdateResult

UpdateWithAnalysis evaluates the full analysis and updates circuit state. It checks all trip conditions in order: safety completion, strict completion (success path), nil status, blocked status, same-error sequence, output decline, test-only loops, and finally no-progress stagnation. Returns an UpdateResult indicating whether the circuit tripped, the reason, or whether strict completion criteria were met.

type CircuitBreakerConfig

type CircuitBreakerConfig struct {
	StagnationThreshold       int
	SameErrorThreshold        int
	OutputDeclineThreshold    int
	MaxConsecutiveTestLoops   int
	CompletionThreshold       int
	SafetyCompletionThreshold int
}

CircuitBreakerConfig holds configuration for the circuit breaker.

func DefaultCircuitBreakerConfig

func DefaultCircuitBreakerConfig() CircuitBreakerConfig

DefaultCircuitBreakerConfig returns default configuration.

type CircuitBreakerState

type CircuitBreakerState struct {
	NoProgressCount            int    `json:"no_progress_count"`
	Tripped                    bool   `json:"tripped"`
	TripReason                 string `json:"trip_reason,omitempty"`
	SameErrorCount             int    `json:"same_error_count"`
	LastErrorSignature         string `json:"last_error_signature,omitempty"`
	DeclineCount               int    `json:"decline_count"`
	LastOutputSize             int    `json:"last_output_size"`
	ConsecutiveTestLoops       int    `json:"consecutive_test_loops"`
	ConsecutiveCompletionCount int    `json:"consecutive_completion_count"`
}

CircuitBreakerState holds the state for persistence.

type CircuitHistory

type CircuitHistory struct {
	Project string                `json:"project"`
	Agent   string                `json:"agent"`
	Entries []CircuitHistoryEntry `json:"entries"`
}

CircuitHistory tracks circuit breaker state changes.

type CircuitHistoryEntry

type CircuitHistoryEntry struct {
	Timestamp       time.Time `json:"timestamp"`
	FromState       string    `json:"from_state"` // closed, tripped
	ToState         string    `json:"to_state"`
	Reason          string    `json:"reason,omitempty"`
	NoProgressCount int       `json:"no_progress_count"`
	SameErrorCount  int       `json:"same_error_count,omitempty"`
	TestLoopCount   int       `json:"test_loop_count,omitempty"`
	CompletionCount int       `json:"completion_count,omitempty"`
}

CircuitHistoryEntry represents a circuit breaker state change.

type CircuitState

type CircuitState struct {
	Project         string     `json:"project"`
	Agent           string     `json:"agent"`
	NoProgressCount int        `json:"no_progress_count"`
	Tripped         bool       `json:"tripped"`
	TripReason      string     `json:"trip_reason,omitempty"`
	TrippedAt       *time.Time `json:"tripped_at,omitempty"`
	UpdatedAt       time.Time  `json:"updated_at"`
}

CircuitState represents the persistent state of the circuit breaker.

type CompactMetadata

type CompactMetadata struct {
	Trigger   string `json:"trigger"`
	PreTokens int    `json:"pre_tokens"`
}

CompactMetadata describes a conversation compaction event.

type ConcurrencyAction

type ConcurrencyAction int

ConcurrencyAction represents the user's chosen action after a concurrency check.

const (
	// ActionProceed continues without worktree isolation.
	ActionProceed ConcurrencyAction = iota
	// ActionWorktree uses a worktree for isolation.
	ActionWorktree
	// ActionAbort cancels the operation.
	ActionAbort
)

func CheckConcurrency

func CheckConcurrency(ctx context.Context, cfg *ConcurrencyCheckConfig) (ConcurrencyAction, error)

CheckConcurrency checks for running containers in the same project and working directory. If a conflict is found, it either warns (non-interactive) or prompts the user to choose between worktree isolation, proceeding anyway, or aborting.

type ConcurrencyCheckConfig

type ConcurrencyCheckConfig struct {
	// Client is the Docker client for listing containers.
	Client *docker.Client

	// Project is the project name to check.
	Project string

	// WorkDir is the current absolute host working directory.
	WorkDir string

	// IOStreams is used for warning output.
	IOStreams *iostreams.IOStreams

	// Prompter returns the interactive prompter. Nil means non-interactive.
	Prompter func() *prompter.Prompter
}

ConcurrencyCheckConfig holds inputs for CheckConcurrency.

type Config

type Config struct {
	MaxLoops                  int  `yaml:"max_loops" mapstructure:"max_loops"`
	StagnationThreshold       int  `yaml:"stagnation_threshold" mapstructure:"stagnation_threshold"`
	TimeoutMinutes            int  `yaml:"timeout_minutes" mapstructure:"timeout_minutes"`
	AutoConfirm               bool `yaml:"auto_confirm" mapstructure:"auto_confirm"`
	CallsPerHour              int  `yaml:"calls_per_hour" mapstructure:"calls_per_hour"`
	CompletionThreshold       int  `yaml:"completion_threshold" mapstructure:"completion_threshold"`
	SessionExpirationHours    int  `yaml:"session_expiration_hours" mapstructure:"session_expiration_hours"`
	SameErrorThreshold        int  `yaml:"same_error_threshold" mapstructure:"same_error_threshold"`
	OutputDeclineThreshold    int  `yaml:"output_decline_threshold" mapstructure:"output_decline_threshold"`
	MaxConsecutiveTestLoops   int  `yaml:"max_consecutive_test_loops" mapstructure:"max_consecutive_test_loops"`
	LoopDelaySeconds          int  `yaml:"loop_delay_seconds" mapstructure:"loop_delay_seconds"`
	SafetyCompletionThreshold int  `yaml:"safety_completion_threshold" mapstructure:"safety_completion_threshold"`
}

Config holds loop-specific configuration.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns the default loop configuration.

func (Config) GetCallsPerHour

func (c Config) GetCallsPerHour() int

GetCallsPerHour returns the calls per hour limit with default fallback. Returns 0 to disable rate limiting.

func (Config) GetCompletionThreshold

func (c Config) GetCompletionThreshold() int

GetCompletionThreshold returns the completion indicator threshold with default fallback.

func (Config) GetLoopDelaySeconds

func (c Config) GetLoopDelaySeconds() int

GetLoopDelaySeconds returns the loop delay in seconds with default fallback.

func (Config) GetMaxConsecutiveTestLoops

func (c Config) GetMaxConsecutiveTestLoops() int

GetMaxConsecutiveTestLoops returns the max consecutive test loops with default fallback.

func (Config) GetMaxLoops

func (c Config) GetMaxLoops() int

GetMaxLoops returns the max loops with default fallback.

func (Config) GetOutputDeclineThreshold

func (c Config) GetOutputDeclineThreshold() int

GetOutputDeclineThreshold returns the output decline threshold percentage with default fallback.

func (Config) GetSafetyCompletionThreshold

func (c Config) GetSafetyCompletionThreshold() int

GetSafetyCompletionThreshold returns the safety completion threshold with default fallback.

func (Config) GetSameErrorThreshold

func (c Config) GetSameErrorThreshold() int

GetSameErrorThreshold returns the same error threshold with default fallback.

func (Config) GetSessionExpirationHours

func (c Config) GetSessionExpirationHours() int

GetSessionExpirationHours returns the session expiration hours with default fallback.

func (Config) GetStagnationThreshold

func (c Config) GetStagnationThreshold() int

GetStagnationThreshold returns the stagnation threshold with default fallback.

func (Config) Timeout

func (c Config) Timeout() time.Duration

Timeout returns the per-loop timeout duration.

type ContainerStartConfig

type ContainerStartConfig struct {
	ContainerID string
	Cleanup     func()
}

ContainerStartConfig is returned by the per-iteration container factory. It provides the container ID and a cleanup function that removes the container and its associated volumes.

type ContentBlock

type ContentBlock struct {
	Type string `json:"type"`

	// Text block: populated when Type == "text".
	Text string `json:"text,omitempty"`

	// Tool use block: populated when Type == "tool_use".
	ID    string          `json:"id,omitempty"`
	Name  string          `json:"name,omitempty"`
	Input json.RawMessage `json:"input,omitempty"`

	// Tool result block: populated when Type == "tool_result".
	// Content is json.RawMessage because the API sends either a string or an array.
	ToolUseID string          `json:"tool_use_id,omitempty"`
	Content   json.RawMessage `json:"content,omitempty"`
	IsError   bool            `json:"is_error,omitempty"`

	// Thinking block: populated when Type == "thinking".
	Thinking string `json:"thinking,omitempty"`
}

ContentBlock is a polymorphic element in a message's content array. Discriminate on Type to determine which fields are populated.

func (*ContentBlock) ToolResultText

func (b *ContentBlock) ToolResultText() string

ToolResultText extracts the tool result content as a string. Handles both the string form and the array-of-blocks form.

type EventType

type EventType string

EventType discriminates top-level stream-json events.

const (
	EventTypeSystem      EventType = "system"
	EventTypeAssistant   EventType = "assistant"
	EventTypeUser        EventType = "user"
	EventTypeResult      EventType = "result"
	EventTypeStreamEvent EventType = "stream_event"
)

type HistoryStore

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

HistoryStore manages history persistence.

func DefaultHistoryStore

func DefaultHistoryStore() (*HistoryStore, error)

DefaultHistoryStore returns a history store using the default clawker directory.

func NewHistoryStore

func NewHistoryStore(baseDir string) *HistoryStore

NewHistoryStore creates a new history store at the given base directory.

func (*HistoryStore) AddCircuitEntry

func (h *HistoryStore) AddCircuitEntry(project, agent, fromState, toState, reason string, noProgressCount, sameErrorCount, testLoopCount, completionCount int) error

AddCircuitEntry adds an entry to circuit history, trimming to MaxHistoryEntries.

func (*HistoryStore) AddSessionEntry

func (h *HistoryStore) AddSessionEntry(project, agent, event, status, errorMsg string, loopCount int) error

AddSessionEntry adds an entry to session history, trimming to MaxHistoryEntries.

func (*HistoryStore) DeleteCircuitHistory

func (h *HistoryStore) DeleteCircuitHistory(project, agent string) error

DeleteCircuitHistory removes circuit history from disk.

func (*HistoryStore) DeleteSessionHistory

func (h *HistoryStore) DeleteSessionHistory(project, agent string) error

DeleteSessionHistory removes session history from disk.

func (*HistoryStore) LoadCircuitHistory

func (h *HistoryStore) LoadCircuitHistory(project, agent string) (*CircuitHistory, error)

LoadCircuitHistory loads circuit history from disk. Returns an empty history if no file exists.

func (*HistoryStore) LoadSessionHistory

func (h *HistoryStore) LoadSessionHistory(project, agent string) (*SessionHistory, error)

LoadSessionHistory loads session history from disk. Returns an empty history if no file exists.

func (*HistoryStore) SaveCircuitHistory

func (h *HistoryStore) SaveCircuitHistory(history *CircuitHistory) error

SaveCircuitHistory saves circuit history to disk.

func (*HistoryStore) SaveSessionHistory

func (h *HistoryStore) SaveSessionHistory(history *SessionHistory) error

SaveSessionHistory saves session history to disk.

type HookConfig

type HookConfig map[string][]HookMatcherGroup

HookConfig maps event names to matcher groups. This is the value of the "hooks" key in Claude Code's settings.json.

func DefaultHooks

func DefaultHooks() HookConfig

DefaultHooks returns the default hook configuration for loop execution. The Stop hook blocks Claude from stopping unless a LOOP_STATUS block is present. The SessionStart hook re-injects a LOOP_STATUS reminder after context compaction.

func ResolveHooks

func ResolveHooks(hooksFile string) (HookConfig, map[string][]byte, error)

ResolveHooks loads hook configuration from a file or returns defaults. If hooksFile is empty, returns DefaultHooks() and DefaultHookFiles(). If hooksFile is provided, it is read as a complete HookConfig replacement with no default hook files (custom hooks manage their own scripts).

func (HookConfig) MarshalSettingsJSON

func (hc HookConfig) MarshalSettingsJSON() ([]byte, error)

MarshalSettingsJSON serializes the hook config as a settings.json fragment with the "hooks" wrapper key: {"hooks": {...}}.

func (HookConfig) Validate

func (hc HookConfig) Validate() error

Validate checks the hook configuration for structural errors. It verifies that event names are non-empty, handler types are valid, required fields are populated for each handler type, timeouts are non-negative, and each matcher group has at least one hook.

type HookHandler

type HookHandler struct {
	Type    string `json:"type"`
	Command string `json:"command,omitempty"`
	Prompt  string `json:"prompt,omitempty"`
	Timeout int    `json:"timeout,omitempty"`
}

HookHandler is a single hook action matching the Claude Code settings.json format.

type HookMatcherGroup

type HookMatcherGroup struct {
	Matcher string        `json:"matcher,omitempty"`
	Hooks   []HookHandler `json:"hooks"`
}

HookMatcherGroup is a matcher regex paired with one or more hook handlers.

type LoopContainerConfig

type LoopContainerConfig struct {
	// Client is the Docker client.
	Client *docker.Client

	Command []string

	// Config is the config.Config interface providing Project(), Settings(), etc.
	Config config.Config

	// ProjectName is the resolved project name (from project.ProjectManager).
	// Empty string when no project is registered.
	ProjectName string

	// LoopOpts holds the shared loop flags (agent, image, worktree, etc.).
	LoopOpts *LoopOptions

	// Flags is the command's pflag.FlagSet for Changed() detection.
	Flags *pflag.FlagSet

	// Version is the clawker build version.
	Version string

	// ProjectManager returns the project manager for worktree registration.
	ProjectManager func() (project.ProjectManager, error)

	// HostProxy returns the host proxy service.
	HostProxy func() hostproxy.HostProxyService

	// Firewall returns the firewall manager.
	Firewall func(context.Context) (firewall.FirewallManager, error)

	// SocketBridge returns the socket bridge manager.
	SocketBridge func() socketbridge.SocketBridgeManager

	// IOStreams is the I/O streams for spinner output during creation.
	IOStreams *iostreams.IOStreams

	// Log is the file logger for diagnostic output.
	Log *logger.Logger
}

LoopContainerConfig holds all inputs needed to set up a container for loop execution.

type LoopContainerResult

type LoopContainerResult struct {
	// ContainerID is the Docker container ID.
	ContainerID string

	// ContainerName is the full container name (clawker.project.agent).
	ContainerName string

	// AgentName is the resolved agent name.
	AgentName string

	// ProjectName is the resolved project name (from project.ProjectManager).
	ProjectName string

	// ProjectCfg is the project configuration.
	ProjectCfg *config.Project

	// WorkDir is the host working directory for this session.
	WorkDir string
}

LoopContainerResult holds outputs from container setup.

func SetupLoopContainer deprecated

func SetupLoopContainer(ctx context.Context, cfg *LoopContainerConfig) (*LoopContainerResult, func(), error)

SetupLoopContainer creates, configures, and starts a container for loop execution.

Deprecated: Use ResolveLoopImage + MakeCreateContainerFunc for per-iteration containers.

The cleanup function uses context.Background() so it runs even after cancellation.

type LoopDashEvent

type LoopDashEvent struct {
	Kind          LoopDashEventKind
	Iteration     int
	MaxIterations int
	AgentName     string
	Project       string

	// Status (populated on IterEnd)
	StatusText     string
	TasksCompleted int
	FilesModified  int
	TestsStatus    string
	ExitSignal     bool

	// Circuit breaker
	CircuitProgress  int
	CircuitThreshold int
	CircuitTripped   bool

	// Rate limiter
	RateRemaining int
	RateLimit     int

	// Timing
	IterDuration time.Duration

	// Completion
	ExitReason string
	Error      error

	// Session totals
	TotalTasks int
	TotalFiles int

	// Cost/token data (populated on IterEnd from ResultEvent)
	IterCostUSD float64
	IterTokens  int
	IterTurns   int

	// Output
	OutputChunk string
	OutputKind  OutputKind
}

LoopDashEvent is sent on the channel to update the dashboard.

type LoopDashEventKind

type LoopDashEventKind int

LoopDashEventKind discriminates dashboard events.

const (
	// LoopDashEventStart is sent once at the beginning of a loop session.
	LoopDashEventStart LoopDashEventKind = iota

	// LoopDashEventIterStart is sent when an iteration begins.
	LoopDashEventIterStart

	// LoopDashEventIterEnd is sent when an iteration completes.
	LoopDashEventIterEnd

	// LoopDashEventOutput is sent with output chunks for the streaming feed.
	LoopDashEventOutput

	// LoopDashEventRateLimit is sent when the rate limiter triggers a wait.
	LoopDashEventRateLimit

	// LoopDashEventComplete is sent when the loop session finishes.
	LoopDashEventComplete
)

func (LoopDashEventKind) String

func (k LoopDashEventKind) String() string

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

type LoopDashboardConfig

type LoopDashboardConfig struct {
	AgentName string
	Project   string
	MaxLoops  int
}

LoopDashboardConfig configures the dashboard.

type LoopDashboardResult

type LoopDashboardResult struct {
	Err         error // display error only
	Detached    bool  // user pressed q/Esc — loop continues, switch to minimal output
	Interrupted bool  // user pressed Ctrl+C — stop the loop
}

LoopDashboardResult is returned when the dashboard exits.

func RunLoopDashboard

func RunLoopDashboard(ios *iostreams.IOStreams, cfg LoopDashboardConfig, ch <-chan LoopDashEvent) LoopDashboardResult

RunLoopDashboard runs the loop dashboard display, consuming events from ch until the channel is closed. Returns when the BubbleTea program exits.

type LoopOptions

type LoopOptions struct {
	// Loop control
	MaxLoops            int
	StagnationThreshold int
	TimeoutMinutes      int
	LoopDelay           int

	// Circuit breaker tuning
	SameErrorThreshold        int
	OutputDeclineThreshold    int
	MaxConsecutiveTestLoops   int
	SafetyCompletionThreshold int
	CompletionThreshold       int
	StrictCompletion          bool

	// Execution
	SkipPermissions bool
	CallsPerHour    int
	ResetCircuit    bool

	// Hooks
	HooksFile string

	// System prompt
	AppendSystemPrompt string

	// Container — Agent is set programmatically by iterate/tasks run functions
	// (auto-generated via loop.GenerateAgentName), not exposed as a CLI flag.
	// Status and reset commands register their own --agent flag independently.
	Agent    string
	Worktree string
	Image    string

	// Output
	Verbose bool
}

LoopOptions holds flags shared between loop iterate and loop tasks commands. Command-specific Options structs embed this to get the common flags.

func NewLoopOptions

func NewLoopOptions() *LoopOptions

NewLoopOptions creates a LoopOptions with zero values. Defaults are applied via Cobra flag registration in AddLoopFlags.

type Monitor

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

Monitor provides real-time progress output for loop iterations.

func NewMonitor

func NewMonitor(opts MonitorOptions) *Monitor

NewMonitor creates a new monitor with the given options.

func (*Monitor) FormatAPILimitError

func (m *Monitor) FormatAPILimitError(isInteractive bool) string

FormatAPILimitError returns the API limit error message.

func (*Monitor) FormatLoopEnd

func (m *Monitor) FormatLoopEnd(loopNum int, status *Status, err error, outputSize int, elapsed time.Duration) string

FormatLoopEnd returns the loop end message.

func (*Monitor) FormatLoopProgress

func (m *Monitor) FormatLoopProgress(loopNum int, status *Status, circuit *CircuitBreaker) string

FormatLoopProgress returns the progress line for monitor mode. Format: [Loop 3/50] IN_PROGRESS | Tasks: 2 | Files: 5 | Rate: 97/100 | Circuit: 0/3

func (*Monitor) FormatLoopStart

func (m *Monitor) FormatLoopStart(loopNum int) string

FormatLoopStart returns the loop start message.

func (*Monitor) FormatRateLimitWait

func (m *Monitor) FormatRateLimitWait(resetTime time.Time) string

FormatRateLimitWait returns the rate limit wait message.

func (*Monitor) FormatResult

func (m *Monitor) FormatResult(result *Result) string

FormatResult returns the final result summary.

func (*Monitor) PrintLoopEnd

func (m *Monitor) PrintLoopEnd(loopNum int, status *Status, err error, outputSize int, elapsed time.Duration)

PrintLoopEnd writes the loop end message.

func (*Monitor) PrintLoopProgress

func (m *Monitor) PrintLoopProgress(loopNum int, status *Status, circuit *CircuitBreaker)

PrintLoopProgress writes the progress line.

func (*Monitor) PrintLoopStart

func (m *Monitor) PrintLoopStart(loopNum int)

PrintLoopStart writes the loop start message.

func (*Monitor) PrintResult

func (m *Monitor) PrintResult(result *Result)

PrintResult writes the final result summary.

type MonitorOptions

type MonitorOptions struct {
	// Writer is where monitor output goes (typically os.Stderr).
	Writer io.Writer

	// MaxLoops is the total max loops for progress calculation.
	MaxLoops int

	// ShowRateLimit shows rate limiter status if enabled.
	ShowRateLimit bool

	// RateLimiter to query for status.
	RateLimiter *RateLimiter

	// Verbose enables detailed output.
	Verbose bool
}

MonitorOptions configures the monitor output.

type Options

type Options struct {
	// CreateContainer creates a new container for each iteration.
	// Returns a ContainerStartConfig with the container ID and cleanup function.
	CreateContainer func(ctx context.Context) (*ContainerStartConfig, error)

	// ProjectCfg is the project configuration.
	ProjectCfg *config.Project

	// ProjectName is the project identity string used for session/circuit
	// storage keys and container naming. Resolved from project.ProjectManager
	// at command level; empty string when no project is registered.
	ProjectName string

	// Agent is the agent name.
	Agent string

	// Prompt is the prompt used for each iteration (-p flag).
	Prompt string

	// MaxLoops is the maximum number of loops to run.
	MaxLoops int

	// StagnationThreshold is how many loops without progress before tripping.
	StagnationThreshold int

	// Timeout is the per-loop timeout.
	Timeout time.Duration

	// ResetCircuit resets the circuit breaker before starting.
	ResetCircuit bool

	// CallsPerHour is the rate limit (0 to disable).
	CallsPerHour int

	// CompletionThreshold is the number of completion indicators required.
	CompletionThreshold int

	// SessionExpirationHours is the session TTL (0 for default).
	SessionExpirationHours int

	// SameErrorThreshold is how many same-error loops before tripping.
	SameErrorThreshold int

	// OutputDeclineThreshold is the percentage decline that triggers trip.
	OutputDeclineThreshold int

	// MaxConsecutiveTestLoops is how many test-only loops before tripping.
	MaxConsecutiveTestLoops int

	// LoopDelaySeconds is the delay between loop iterations.
	LoopDelaySeconds int

	// SafetyCompletionThreshold is loops with completion indicators but no exit signal before trip.
	SafetyCompletionThreshold int

	// UseStrictCompletion requires both EXIT_SIGNAL and completion indicators.
	UseStrictCompletion bool

	// SkipPermissions passes --dangerously-skip-permissions to claude.
	SkipPermissions bool

	// SystemPrompt is the full system prompt appended via --append-system-prompt.
	// Built by BuildSystemPrompt() which combines the default LOOP_STATUS
	// instructions with any user-provided additional instructions.
	SystemPrompt string

	// Monitor is the optional monitor for live output.
	Monitor *Monitor

	// WorkDir is the host working directory for this session.
	WorkDir string

	// Logger is the diagnostic file logger (from Factory).
	Logger *logger.Logger

	// Verbose enables verbose logging.
	Verbose bool

	// OnLoopStart is called before each loop iteration.
	OnLoopStart func(loopNum int)

	// OnLoopEnd is called after each loop iteration with optional cost data.
	OnLoopEnd func(loopNum int, status *Status, resultEvent *ResultEvent, err error)

	// OnOutput is called with text output chunks during execution.
	OnOutput func(chunk []byte)

	// OnStreamEvent is called for each raw streaming event (content_block_start,
	// content_block_delta, etc.). Enables rich TUI updates like tool activity
	// indicators. When set alongside OnOutput, both fire independently.
	OnStreamEvent func(*StreamDeltaEvent)

	// OnRateLimitHit is called when Claude's API limit is detected.
	// Return true to wait and retry, false to exit.
	OnRateLimitHit func() bool
}

Options configures the loop execution.

func BuildRunnerOptions

func BuildRunnerOptions(
	loopOpts *LoopOptions,
	project *config.Project,
	projectName string,
	agent, prompt, workDir string,
	createContainer func(ctx context.Context) (*ContainerStartConfig, error),
	flags *pflag.FlagSet,
	loopCfg *config.LoopConfig,
	log *logger.Logger,
) Options

BuildRunnerOptions maps CLI LoopOptions + command context into Options. Config overrides are applied for any flags the user did not explicitly set.

type OutputKind

type OutputKind int

OutputKind distinguishes output chunk types.

const (
	// OutputText is a raw text chunk from the assistant.
	OutputText OutputKind = iota

	// OutputToolStart indicates a tool invocation began.
	OutputToolStart
)

type RateLimitState

type RateLimitState struct {
	Calls       int       `json:"calls"`
	WindowStart time.Time `json:"window_start"`
}

RateLimitState represents the persistent state of the rate limiter.

type RateLimiter

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

RateLimiter tracks API calls per hour and enforces a configurable limit.

func NewRateLimiter

func NewRateLimiter(limit int) *RateLimiter

NewRateLimiter creates a new rate limiter with the given limit per hour. A limit of 0 or less disables rate limiting.

func (*RateLimiter) Allow

func (r *RateLimiter) Allow() bool

Allow checks if a call is allowed and records it if so. Returns true if the call is allowed, false if rate limited.

func (*RateLimiter) CallCount

func (r *RateLimiter) CallCount() int

CallCount returns the current number of calls in this window.

func (*RateLimiter) IsEnabled

func (r *RateLimiter) IsEnabled() bool

IsEnabled returns true if rate limiting is enabled.

func (*RateLimiter) Limit

func (r *RateLimiter) Limit() int

Limit returns the configured limit.

func (*RateLimiter) Record

func (r *RateLimiter) Record()

Record manually records a call without checking the limit. Useful when a call has already been made.

func (*RateLimiter) Remaining

func (r *RateLimiter) Remaining() int

Remaining returns the number of calls remaining in the current window.

func (*RateLimiter) ResetTime

func (r *RateLimiter) ResetTime() time.Time

ResetTime returns when the current rate limit window will reset.

func (*RateLimiter) RestoreState

func (r *RateLimiter) RestoreState(state RateLimitState) bool

RestoreState restores the rate limiter from a persisted state. Returns true if the state was actually restored, false if it was discarded due to expiration or invalid values.

func (*RateLimiter) State

func (r *RateLimiter) State() RateLimitState

State returns the current state for persistence.

type Result

type Result struct {
	// LoopsCompleted is the number of loops that ran.
	LoopsCompleted int

	// FinalStatus is the last parsed status.
	FinalStatus *Status

	// ExitReason describes why the loop exited.
	ExitReason string

	// Session is the final session state.
	Session *Session

	// Error is set if the loop exited due to an error.
	Error error

	// RateLimitHit is true if the loop hit Claude's API rate limit.
	RateLimitHit bool
}

Result represents the outcome of running the loop.

func RunLoop

func RunLoop(ctx context.Context, cfg RunLoopConfig) (*Result, error)

RunLoop executes the loop with either the TUI dashboard (TTY default) or the text monitor (verbose/json/quiet/non-TTY). Returns the result and any error. This consolidates the output mode selection shared by iterate and tasks.

type ResultEvent

type ResultEvent struct {
	Type          EventType   `json:"type"`
	Subtype       string      `json:"subtype"`
	SessionID     string      `json:"session_id"`
	IsError       bool        `json:"is_error"`
	DurationMS    int         `json:"duration_ms"`
	DurationAPIMS int         `json:"duration_api_ms"`
	NumTurns      int         `json:"num_turns"`
	TotalCostUSD  float64     `json:"total_cost_usd"`
	Usage         *TokenUsage `json:"usage,omitempty"`

	// Success-only fields.
	Result string `json:"result,omitempty"`

	// Error-only fields.
	Errors []string `json:"errors,omitempty"`
}

ResultEvent is the final event in the stream, indicating completion or error.

func ParseStream

func ParseStream(ctx context.Context, r io.Reader, handler *StreamHandler, log *logger.Logger) (*ResultEvent, error)

ParseStream reads NDJSON lines from r, dispatches events to handler, and returns the final ResultEvent. Returns an error if the stream ends without a result event, on context cancellation, or on scan failure.

Before parsing NDJSON, ParseStream waits for a ready signal line (ReadyLogPrefix). Lines before the signal are debug-logged and skipped. An error signal (ErrorLogPrefix) during init returns an error immediately. If the stream ends before the ready signal, an error is returned.

Malformed lines are debug-logged and skipped. Unrecognized event types are silently skipped for forward compatibility. Known event types that fail to parse (system, assistant, user) are warn-logged and skipped. A malformed result event returns an error (terminal event corruption).

func (*ResultEvent) CombinedText

func (r *ResultEvent) CombinedText() string

CombinedText returns the result text (success) or joined error messages.

func (*ResultEvent) IsSuccess

func (r *ResultEvent) IsSuccess() bool

IsSuccess returns true if this result represents a successful completion.

type ResultOutput

type ResultOutput struct {
	LoopsCompleted      int    `json:"loops_completed"`
	ExitReason          string `json:"exit_reason"`
	Success             bool   `json:"success"`
	Error               string `json:"error,omitempty"`
	TotalTasksCompleted int    `json:"total_tasks_completed,omitempty"`
	TotalFilesModified  int    `json:"total_files_modified,omitempty"`
	FinalStatus         string `json:"final_status,omitempty"`
	RateLimitHit        bool   `json:"rate_limit_hit,omitempty"`
}

ResultOutput is the structured output for loop results.

func NewResultOutput

func NewResultOutput(result *Result) *ResultOutput

NewResultOutput maps a Result into a ResultOutput for serialization.

type RunLoopConfig

type RunLoopConfig struct {
	Runner      *Runner
	RunnerOpts  Options
	TUI         interface{} // unused after refactor, kept for API compat (TODO: remove)
	IOStreams   *iostreams.IOStreams
	Setup       *LoopContainerResult
	Format      *cmdutil.FormatFlags
	Verbose     bool
	CommandName string // "iterate" or "tasks"
}

RunLoopConfig holds all inputs for RunLoop.

type Runner

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

Runner executes autonomous loops.

func NewRunner

func NewRunner(client *docker.Client) (*Runner, error)

NewRunner creates a new Runner with the given Docker client and default stores.

func NewRunnerWith

func NewRunnerWith(client *docker.Client, store *SessionStore, history *HistoryStore) *Runner

NewRunnerWith creates a Runner with explicit store and history dependencies. This is useful for testing with custom storage directories.

func (*Runner) GetCircuitState

func (r *Runner) GetCircuitState(project, agent string) (*CircuitState, error)

GetCircuitState returns the circuit breaker state for a project/agent.

func (*Runner) GetSession

func (r *Runner) GetSession(project, agent string) (*Session, error)

GetSession returns the current session for a project/agent.

func (*Runner) ResetCircuit

func (r *Runner) ResetCircuit(project, agent string) error

ResetCircuit resets the circuit breaker for a project/agent.

func (*Runner) ResetSession

func (r *Runner) ResetSession(project, agent string) error

ResetSession resets both session and circuit for a project/agent.

func (*Runner) Run

func (r *Runner) Run(ctx context.Context, opts Options) (*Result, error)

Run executes the loop until completion, error, or max loops. Each iteration creates a new container via opts.CreateContainer, attaches, parses stream-json output, and cleans up the container.

func (*Runner) StartContainer

func (r *Runner) StartContainer(ctx context.Context, log *logger.Logger, projectCfg *config.Project, containerConfig *ContainerStartConfig, onOutput func([]byte), onStreamEvent func(*StreamDeltaEvent)) (string, *ResultEvent, int, error)

StartContainer attaches to the container, starts it, parses stream-json output via io.Pipe + ParseStream, and returns the accumulated text, the terminal ResultEvent, the exit code, and any error.

Streaming events are forwarded to onStreamEvent for rich TUI updates (tool starts, text deltas). Text deltas are also forwarded to onOutput for simple text display. Complete assistant messages are accumulated via TextAccumulator for LOOP_STATUS parsing.

type Session

type Session struct {
	// Project is the clawker project name.
	Project string `json:"project"`

	// Agent is the agent name.
	Agent string `json:"agent"`

	// WorkDir is the host working directory for this session.
	WorkDir string `json:"work_dir,omitempty"`

	// StartedAt is when the session started.
	StartedAt time.Time `json:"started_at"`

	// UpdatedAt is when the session was last updated.
	UpdatedAt time.Time `json:"updated_at"`

	// LoopsCompleted is the number of loops completed.
	LoopsCompleted int `json:"loops_completed"`

	// Status is the last known status.
	Status string `json:"status"`

	// NoProgressCount is the current count without progress.
	NoProgressCount int `json:"no_progress_count"`

	// TotalTasksCompleted is the cumulative tasks completed.
	TotalTasksCompleted int `json:"total_tasks_completed"`

	// TotalFilesModified is the cumulative files modified.
	TotalFilesModified int `json:"total_files_modified"`

	// LastError is the last error message, if any.
	LastError string `json:"last_error,omitempty"`

	// InitialPrompt is the prompt that started the session.
	InitialPrompt string `json:"initial_prompt,omitempty"`

	// RateLimitState tracks the rate limiter state for persistence.
	RateLimitState *RateLimitState `json:"rate_limit_state,omitempty"`
}

Session represents the persistent state of a loop session.

func NewSession

func NewSession(project, agent, prompt, workDir string) *Session

NewSession creates a new session.

func (*Session) Age

func (sess *Session) Age() time.Duration

Age returns the duration since the session started.

func (*Session) IsExpired

func (sess *Session) IsExpired(hours int) bool

IsExpired returns true if the session is older than the given hours.

func (*Session) Update

func (sess *Session) Update(status *Status, loopErr error)

Update updates the session with loop results. It always increments LoopsCompleted and refreshes UpdatedAt. A non-nil loopErr is recorded as LastError; nil clears it. When status is non-nil, the session's Status, TotalTasksCompleted, and TotalFilesModified are updated from the status fields, and NoProgressCount is reset if the status shows progress. When status is nil (no LOOP_STATUS block found), the loop is treated as a no-progress iteration.

type SessionHistory

type SessionHistory struct {
	Project string                `json:"project"`
	Agent   string                `json:"agent"`
	Entries []SessionHistoryEntry `json:"entries"`
}

SessionHistory tracks session lifecycle events.

type SessionHistoryEntry

type SessionHistoryEntry struct {
	Timestamp time.Time `json:"timestamp"`
	Event     string    `json:"event"` // created, updated, expired, deleted, repeated_error
	LoopCount int       `json:"loop_count"`
	Status    string    `json:"status,omitempty"`
	Error     string    `json:"error,omitempty"`
}

SessionHistoryEntry represents a session state transition.

type SessionStore

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

SessionStore manages session and circuit breaker persistence.

func DefaultSessionStore

func DefaultSessionStore() (*SessionStore, error)

DefaultSessionStore returns a session store using the default clawker directory.

func NewSessionStore

func NewSessionStore(baseDir string, log ...*logger.Logger) *SessionStore

NewSessionStore creates a new session store at the given base directory.

func (*SessionStore) DeleteCircuitState

func (s *SessionStore) DeleteCircuitState(project, agent string) error

DeleteCircuitState removes circuit breaker state from disk.

func (*SessionStore) DeleteSession

func (s *SessionStore) DeleteSession(project, agent string) error

DeleteSession removes a session from disk.

func (*SessionStore) ListSessions

func (s *SessionStore) ListSessions(project string) ([]*Session, error)

ListSessions returns all sessions for a given project. Malformed session files are silently skipped.

func (*SessionStore) LoadCircuitState

func (s *SessionStore) LoadCircuitState(project, agent string) (*CircuitState, error)

LoadCircuitState loads circuit breaker state from disk. Returns nil if no state exists.

func (*SessionStore) LoadSession

func (s *SessionStore) LoadSession(project, agent string) (*Session, error)

LoadSession loads a session from disk. Returns nil if no session exists.

func (*SessionStore) LoadSessionWithExpiration

func (s *SessionStore) LoadSessionWithExpiration(project, agent string, expirationHours int) (*Session, bool, error)

LoadSessionWithExpiration loads a session and auto-resets if expired. Returns nil (and deletes the old session) if the session is expired.

func (*SessionStore) SaveCircuitState

func (s *SessionStore) SaveCircuitState(state *CircuitState) error

SaveCircuitState saves circuit breaker state to disk.

func (*SessionStore) SaveSession

func (s *SessionStore) SaveSession(session *Session) error

SaveSession saves a session to disk.

type Status

type Status struct {
	// Status is one of: IN_PROGRESS, COMPLETE, BLOCKED
	Status string

	// TasksCompleted is the number of tasks completed in this loop.
	TasksCompleted int

	// FilesModified is the number of files modified in this loop.
	FilesModified int

	// TestsStatus is one of: PASSING, FAILING, NOT_RUN
	TestsStatus string

	// WorkType describes the type of work done (IMPLEMENTATION, TESTING, etc.)
	WorkType string

	// ExitSignal indicates whether Claude requested to exit the loop.
	ExitSignal bool

	// Recommendation is a one-line recommendation for next steps.
	Recommendation string

	// CompletionIndicators is the count of completion phrases found in output.
	CompletionIndicators int
}

Status represents the parsed LOOP_STATUS block from Claude's output.

func ParseStatus

func ParseStatus(output string) *Status

ParseStatus extracts the LOOP_STATUS block from output and parses it. Returns nil if no valid status block is found.

func (*Status) HasProgress

func (s *Status) HasProgress() bool

HasProgress returns true if the status indicates meaningful progress.

func (*Status) IsBlocked

func (s *Status) IsBlocked() bool

IsBlocked returns true if the status indicates the agent is blocked.

func (*Status) IsComplete

func (s *Status) IsComplete() bool

IsComplete returns true if the status indicates completion. This is the basic check: STATUS: COMPLETE or EXIT_SIGNAL: true

func (*Status) IsCompleteStrict

func (s *Status) IsCompleteStrict(threshold int) bool

IsCompleteStrict returns true only if BOTH conditions are met: - EXIT_SIGNAL is true - CompletionIndicators >= threshold This prevents premature exit due to false positives.

func (*Status) IsTestOnly

func (s *Status) IsTestOnly() bool

IsTestOnly returns true if this loop was test-only work.

func (*Status) String

func (s *Status) String() string

String returns a human-readable summary of the status.

type StreamAPIEvent

type StreamAPIEvent struct {
	Type         string              `json:"type"` // message_start, content_block_start, content_block_delta, content_block_stop, message_delta, message_stop
	Index        int                 `json:"index,omitempty"`
	ContentBlock *StreamContentBlock `json:"content_block,omitempty"`
	Delta        *StreamDelta        `json:"delta,omitempty"`
	Message      *StreamMessageDelta `json:"message,omitempty"`
	Usage        *TokenUsage         `json:"usage,omitempty"`
}

StreamAPIEvent is the inner SSE event from the Anthropic streaming API.

type StreamContentBlock

type StreamContentBlock struct {
	Type string `json:"type"`           // "text", "tool_use"
	ID   string `json:"id,omitempty"`   // block ID
	Name string `json:"name,omitempty"` // tool name when Type == "tool_use"
}

StreamContentBlock describes a content block in a content_block_start event.

type StreamDelta

type StreamDelta struct {
	Type        string `json:"type"`                   // "text_delta", "input_json_delta"
	Text        string `json:"text,omitempty"`         // text content when Type == "text_delta"
	PartialJSON string `json:"partial_json,omitempty"` // partial JSON when Type == "input_json_delta"
}

StreamDelta holds incremental content from a content_block_delta event.

type StreamDeltaEvent

type StreamDeltaEvent struct {
	Type            EventType      `json:"type"`
	UUID            string         `json:"uuid,omitempty"`
	SessionID       string         `json:"session_id"`
	ParentToolUseID *string        `json:"parent_tool_use_id"`
	Event           StreamAPIEvent `json:"event"`
}

StreamDeltaEvent wraps a raw Claude API streaming event. Emitted with --include-partial-messages for real-time token display.

func (*StreamDeltaEvent) IsContentBlockStop

func (e *StreamDeltaEvent) IsContentBlockStop() bool

IsContentBlockStop returns true if this event ends a content block.

func (*StreamDeltaEvent) IsToolStart

func (e *StreamDeltaEvent) IsToolStart() bool

IsToolStart returns true if this event starts a tool_use content block.

func (*StreamDeltaEvent) TextDelta

func (e *StreamDeltaEvent) TextDelta() string

TextDelta returns text if this is a text_delta event, empty string otherwise.

func (*StreamDeltaEvent) ToolName

func (e *StreamDeltaEvent) ToolName() string

ToolName returns the tool name if this is a content_block_start for a tool_use block.

type StreamHandler

type StreamHandler struct {
	// OnSystem is called for system events (init, compact_boundary).
	OnSystem func(*SystemEvent)

	// OnAssistant is called for each complete assistant message.
	OnAssistant func(*AssistantEvent)

	// OnUser is called for each tool result message.
	OnUser func(*UserEvent)

	// OnResult is called when the final result event arrives.
	OnResult func(*ResultEvent)

	// OnStreamEvent is called for real-time streaming events (token deltas).
	// Requires --include-partial-messages flag.
	OnStreamEvent func(*StreamDeltaEvent)
}

StreamHandler receives parsed stream-json events via callbacks. All callbacks are optional — nil callbacks are skipped.

type StreamMessageDelta

type StreamMessageDelta struct {
	StopReason string `json:"stop_reason,omitempty"`
}

StreamMessageDelta holds message-level delta fields from a message_delta event.

type SystemEvent

type SystemEvent struct {
	Type           EventType `json:"type"`
	UUID           string    `json:"uuid,omitempty"`
	Subtype        string    `json:"subtype"`
	SessionID      string    `json:"session_id"`
	Model          string    `json:"model,omitempty"`
	Tools          []string  `json:"tools,omitempty"`
	CWD            string    `json:"cwd,omitempty"`
	PermissionMode string    `json:"permissionMode,omitempty"`

	// CompactBoundary-only fields.
	CompactMetadata *CompactMetadata `json:"compact_metadata,omitempty"`
}

SystemEvent is emitted once at session start (init) or on conversation compaction.

type TextAccumulator

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

TextAccumulator collects assistant text output across multiple messages. It is a convenience handler that aggregates text for LOOP_STATUS parsing and output analysis after the stream completes.

func (*TextAccumulator) Text

func (a *TextAccumulator) Text() string

Text returns all accumulated assistant text joined by newlines.

func (*TextAccumulator) ToolCallCount

func (a *TextAccumulator) ToolCallCount() int

ToolCallCount returns the total number of tool invocations observed.

type TokenUsage

type TokenUsage struct {
	InputTokens              int `json:"input_tokens"`
	OutputTokens             int `json:"output_tokens"`
	CacheCreationInputTokens int `json:"cache_creation_input_tokens,omitempty"`
	CacheReadInputTokens     int `json:"cache_read_input_tokens,omitempty"`
}

TokenUsage tracks API token consumption.

func (*TokenUsage) Total

func (u *TokenUsage) Total() int

Total returns the sum of input and output tokens. Cache tokens are not added separately because the Anthropic API's input_tokens field already accounts for cache reads in billing.

type UpdateResult

type UpdateResult struct {
	Tripped       bool
	Reason        string
	IsComplete    bool // True if strict completion criteria met
	CompletionMsg string
}

UpdateResult contains the result of a circuit breaker update.

type UserEvent

type UserEvent struct {
	Type            EventType        `json:"type"`
	UUID            string           `json:"uuid,omitempty"`
	SessionID       string           `json:"session_id"`
	ParentToolUseID *string          `json:"parent_tool_use_id"`
	Message         UserEventMessage `json:"message"`
}

UserEvent is a tool result message returned after tool execution.

type UserEventMessage

type UserEventMessage struct {
	Role    string         `json:"role"`
	Content []ContentBlock `json:"content"`
}

UserEventMessage is the user message embedded in a UserEvent.

Jump to

Keyboard shortcuts

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