Documentation
¶
Overview ¶
Package loop implements the implementation loop runner with rate-limit recovery.
Index ¶
Constants ¶
const DefaultImplementTemplate = `` /* 810-byte string literal not displayed */
DefaultImplementTemplate is the built-in prompt template used when no custom template file is configured. It uses [[ and ]] as delimiters to avoid conflicts with {{ and }} that commonly appear in task spec content (e.g. Go template syntax, JSON, shell substitutions).
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AgentErrorRecovery ¶
type AgentErrorRecovery struct {
// contains filtered or unexported fields
}
AgentErrorRecovery tracks consecutive agent errors and decides whether the implementation loop should continue or abort.
func NewAgentErrorRecovery ¶
func NewAgentErrorRecovery(maxConsecutiveErrors int, logger interface { Warn(msg string, kv ...interface{}) }) *AgentErrorRecovery
NewAgentErrorRecovery creates an AgentErrorRecovery. Set maxConsecutiveErrors to 0 or negative to disable the limit (the loop never aborts due to consecutive errors). logger may be nil.
func (*AgentErrorRecovery) RecordError ¶
func (aer *AgentErrorRecovery) RecordError(err error) bool
RecordError records an agent error and returns whether the loop should continue. Returns false when the consecutive error limit has been reached, signalling the caller to abort the loop.
func (*AgentErrorRecovery) RecordSuccess ¶
func (aer *AgentErrorRecovery) RecordSuccess()
RecordSuccess resets the consecutive error counter. Call this after each successful agent invocation.
func (*AgentErrorRecovery) ShouldAbort ¶
func (aer *AgentErrorRecovery) ShouldAbort() bool
ShouldAbort returns true if the consecutive error count has reached or exceeded the configured maximum. Returns false when the limit is disabled (maxConsecutiveErrors <= 0).
type CompletionSignal ¶
type CompletionSignal string
CompletionSignal represents a signal detected in agent output.
const ( SignalPhaseComplete CompletionSignal = "PHASE_COMPLETE" SignalTaskBlocked CompletionSignal = "TASK_BLOCKED" SignalRavenError CompletionSignal = "RAVEN_ERROR" )
func DetectSignals ¶
func DetectSignals(output string) (CompletionSignal, string)
DetectSignals scans output for completion signal strings. It returns the first CompletionSignal found and any trailing detail text (e.g., reason following TASK_BLOCKED or RAVEN_ERROR). Returns an empty signal if none found.
This function is exported for use in tests.
func DetectSignalsFromJSONL ¶
func DetectSignalsFromJSONL(output string) (CompletionSignal, string)
DetectSignalsFromJSONL scans JSONL output (stream-json format) for completion signals embedded within assistant text content blocks. Each line is parsed as a StreamEvent; text blocks within assistant messages are scanned for signals. Returns an empty signal if none is found.
This function is exported for use in tests.
type DirtyTreeRecovery ¶
type DirtyTreeRecovery struct {
// contains filtered or unexported fields
}
DirtyTreeRecovery detects and handles uncommitted changes in the working tree before an agent run, ensuring the agent starts from a clean state.
func NewDirtyTreeRecovery ¶
func NewDirtyTreeRecovery( gitClient *git.GitClient, events chan<- RecoveryEvent, logger interface { Info(msg string, kv ...interface{}) Warn(msg string, kv ...interface{}) }, ) *DirtyTreeRecovery
NewDirtyTreeRecovery creates a DirtyTreeRecovery. events may be nil.
func (*DirtyTreeRecovery) CheckAndStash ¶
CheckAndStash checks for uncommitted changes and stashes them if present. Returns true if changes were stashed, false if the working tree was already clean (or if git stash reported nothing to save).
func (*DirtyTreeRecovery) EnsureCleanTree ¶
func (dtr *DirtyTreeRecovery) EnsureCleanTree(ctx context.Context, taskID string) error
EnsureCleanTree verifies the working tree is clean. If dirty, it stashes the changes. Returns an error if the status check or stash operation fails.
func (*DirtyTreeRecovery) RestoreStash ¶
func (dtr *DirtyTreeRecovery) RestoreStash(ctx context.Context) error
RestoreStash pops the most recent stash entry created by CheckAndStash.
type LoopEvent ¶
type LoopEvent struct {
Type LoopEventType
Iteration int
TaskID string
AgentName string
Message string
Timestamp time.Time
Duration time.Duration
WaitTime time.Duration
// Stream-level observability fields (populated for tool/thinking/stats events).
ToolName string // Name of the tool called (EventToolStarted) or tool_use_id (EventToolCompleted).
CostUSD float64 // Session cost in USD (EventSessionStats).
TokensIn int // Input token count (EventSessionStats).
TokensOut int // Output token count (EventSessionStats).
}
LoopEvent represents a structured event emitted during loop execution.
type LoopEventType ¶
type LoopEventType string
LoopEventType identifies the type of loop event.
const ( EventLoopStarted LoopEventType = "loop_started" EventTaskSelected LoopEventType = "task_selected" EventPromptGenerated LoopEventType = "prompt_generated" EventAgentStarted LoopEventType = "agent_started" EventAgentCompleted LoopEventType = "agent_completed" EventAgentError LoopEventType = "agent_error" EventRateLimitWait LoopEventType = "rate_limit_wait" EventRateLimitResume LoopEventType = "rate_limit_resume" EventTaskCompleted LoopEventType = "task_completed" EventTaskBlocked LoopEventType = "task_blocked" EventPhaseComplete LoopEventType = "phase_complete" EventLoopError LoopEventType = "loop_error" EventLoopAborted LoopEventType = "loop_aborted" EventMaxIterations LoopEventType = "max_iterations" EventSleeping LoopEventType = "sleeping" EventDryRun LoopEventType = "dry_run" // Fine-grained stream observability events (emitted when Claude is // invoked with stream-json output format). EventToolStarted LoopEventType = "tool_started" EventToolCompleted LoopEventType = "tool_completed" EventAgentThinking LoopEventType = "agent_thinking" EventSessionStats LoopEventType = "session_stats" )
type PromptContext ¶
type PromptContext struct {
// Task-specific context.
TaskSpec string // Full markdown content of the current task spec.
TaskID string // e.g., "T-016"
TaskTitle string // e.g., "Task Spec Markdown Parser"
// Phase context.
PhaseID int // Current phase number.
PhaseName string // e.g., "Task System & Agent Adapters"
PhaseRange string // e.g., "T-016 to T-030"
// Project context.
ProjectName string // From raven.toml project.name.
ProjectLanguage string // From raven.toml project.language.
// Verification.
VerificationCommands []string // From raven.toml project.verification_commands.
VerificationString string // Commands joined with " && ".
// Task progress context.
CompletedTasks []string // IDs of completed tasks.
RemainingTasks []string // IDs of remaining tasks in current phase.
CompletedSummary string // Formatted summary of completed tasks.
RemainingSummary string // Formatted summary of remaining tasks.
// Agent context.
AgentName string // e.g., "claude"
Model string // e.g., "claude-opus-4-6"
}
PromptContext holds all runtime values that are substituted into a prompt template when generating an agent prompt.
func BuildContext ¶
func BuildContext( spec *task.ParsedTaskSpec, phase *task.Phase, cfg *config.Config, selector *task.TaskSelector, agentName string, ) (*PromptContext, error)
BuildContext constructs a PromptContext from the provided parsed task spec, phase, project configuration, task selector, and agent name.
The selector is used to populate CompletedTasks and RemainingTasks. The agentName is matched against cfg.Agents to look up the configured model.
type PromptGenerator ¶
type PromptGenerator struct {
// contains filtered or unexported fields
}
PromptGenerator loads, caches, and renders prompt templates. It uses [[ and ]] as template delimiters so that {{ and }} in task spec content are never misinterpreted as template actions.
func NewPromptGenerator ¶
func NewPromptGenerator(templateDir string) (*PromptGenerator, error)
NewPromptGenerator creates a PromptGenerator. If templateDir is non-empty, it must refer to an existing directory; an error is returned otherwise. The built-in DefaultImplementTemplate is pre-parsed and cached as the fallback template.
func (*PromptGenerator) Generate ¶
func (pg *PromptGenerator) Generate(templateName string, ctx PromptContext) (string, error)
Generate renders a prompt for the given PromptContext. If templateName is non-empty, the named template file is loaded (and cached) from the generator's templateDir. If templateName is empty, the built-in DefaultImplementTemplate is used.
func (*PromptGenerator) GenerateFromString ¶
func (pg *PromptGenerator) GenerateFromString(tmplStr string, ctx PromptContext) (string, error)
GenerateFromString renders a prompt from an inline template string rather than a file. The string must use [[ and ]] as delimiters.
func (*PromptGenerator) LoadTemplate ¶
func (pg *PromptGenerator) LoadTemplate(name string) (*template.Template, error)
LoadTemplate loads the named template file from the generator's templateDir, parses it with [[ / ]] delimiters, and caches the result. Subsequent calls for the same name return the cached template without re-reading the file.
The name must not be empty and must not contain path components that would escape the templateDir (directory traversal is rejected).
type RateLimitWaiter ¶
type RateLimitWaiter struct {
// contains filtered or unexported fields
}
RateLimitWaiter blocks the loop until the rate limit for a given agent has reset, displaying a live countdown to the output writer and emitting structured recovery events.
func NewRateLimitWaiter ¶
func NewRateLimitWaiter( coordinator *agent.RateLimitCoordinator, output io.Writer, events chan<- RecoveryEvent, logger interface { Info(msg string, kv ...interface{}) }, ) *RateLimitWaiter
NewRateLimitWaiter creates a RateLimitWaiter. output may be nil to suppress the countdown display. events may be nil to disable event emission.
type RecoveryEvent ¶
type RecoveryEvent struct {
// Type identifies what happened.
Type RecoveryEventType
// Message is a human-readable description of the event.
Message string
// Remaining is the time remaining in a rate-limit countdown (zero otherwise).
Remaining time.Duration
// Timestamp is the wall-clock time at which the event was created.
Timestamp time.Time
}
RecoveryEvent is a structured event emitted during recovery operations.
type RecoveryEventType ¶
type RecoveryEventType string
RecoveryEventType identifies the type of recovery event.
const ( // EventRateLimitCountdown is emitted when a rate-limit countdown begins. EventRateLimitCountdown RecoveryEventType = "rate_limit_countdown" // EventRateLimitResuming is emitted when the countdown ends and the agent may retry. EventRateLimitResuming RecoveryEventType = "rate_limit_resuming" // EventDirtyTreeDetected is emitted when uncommitted changes are found. EventDirtyTreeDetected RecoveryEventType = "dirty_tree_detected" // EventStashCreated is emitted when changes are successfully stashed. EventStashCreated RecoveryEventType = "stash_created" // EventStashRestored is emitted when the stash is successfully popped. EventStashRestored RecoveryEventType = "stash_restored" // EventStashFailed is emitted when a stash push or pop operation fails. EventStashFailed RecoveryEventType = "stash_failed" // EventRecoveryError is emitted when a recovery step encounters an error. EventRecoveryError RecoveryEventType = "recovery_error" )
type RunConfig ¶
type RunConfig struct {
AgentName string
PhaseID int
TaskID string // Specific task ID (empty for phase mode).
MaxIterations int // default: 50
MaxLimitWaits int // default: 5
SleepBetween time.Duration // default: 5s
DryRun bool
TemplateName string
}
RunConfig configures the implementation loop behavior.
type Runner ¶
type Runner struct {
// contains filtered or unexported fields
}
Runner orchestrates the implementation loop. It selects the next actionable task, generates a prompt, invokes the agent, interprets the output, updates task state, and repeats until the phase is complete, limits are reached, or the context is cancelled.
func NewRunner ¶
func NewRunner( selector *task.TaskSelector, promptGen *PromptGenerator, ag agent.Agent, stateManager *task.StateManager, rateLimiter *agent.RateLimitCoordinator, cfg *config.Config, phases []task.Phase, events chan<- LoopEvent, logger interface { Info(msg string, kv ...interface{}) Debug(msg string, kv ...interface{}) }, ) *Runner
NewRunner creates an implementation loop runner with all dependencies. The events channel receives structured LoopEvent values; pass nil to disable event emission. The logger must not be nil.
func (*Runner) Run ¶
Run executes the implementation loop in phase mode. It iterates over all not-started tasks in runCfg.PhaseID, running the agent on each, until the phase is complete, max iterations are reached, or ctx is cancelled.
func (*Runner) RunSingleTask ¶
RunSingleTask runs the loop for a specific task ID (--task T-007 mode). It selects the task by ID, generates a prompt, invokes the agent, and returns after one successful invocation (or error).
func (*Runner) SetProgressGenerator ¶
func (r *Runner) SetProgressGenerator(pg *task.ProgressGenerator, progressPath string)
SetProgressGenerator configures a ProgressGenerator that regenerates PROGRESS.md at progressPath after each task state change. If not set, progress file regeneration is skipped. This should be called before Run or RunSingleTask.