Documentation
¶
Overview ¶
Package core provides the loop engine execution logic.
Package core provides loop event types for the loop engine.
Package core provides interfaces for dependency injection.
Package core implements the Ralph loop engine and business logic.
This package contains the core loop execution engine that orchestrates iterative AI development loops. It manages state transitions, promise detection, and event emission.
The LoopEngine follows a state machine pattern with the following states:
- StateIdle: Initial state, ready to start
- StateRunning: Loop is executing iterations
- StateComplete: Successfully completed (promise detected)
- StateFailed: Failed due to error, timeout, or max iterations
- StateCancelled: Cancelled by user
See .github/copilot-instructions.md for the full developer guide and architectural overview.
Package core provides promise detection for the loop engine.
Package core provides summary extraction for iteration carry-context.
Index ¶
- Variables
- func BuildSystemPrompt(promisePhrase string) string
- type AIResponseEvent
- type AutoCommitEvent
- type BlockedPhraseDetectedEvent
- type CarryContextMode
- type CheckpointSavedEvent
- type ErrorEvent
- type ErrorStopEvent
- type IterationCompleteEvent
- type IterationStartEvent
- type IterationTimeoutEvent
- type LoopCancelledEvent
- type LoopCompleteEvent
- type LoopConfig
- type LoopEngine
- type LoopFailedEvent
- type LoopResult
- type LoopStartEvent
- type LoopState
- type NoChangesStopEvent
- type OracleAdviceEvent
- type OracleClient
- type PlanUpdatedEvent
- type PromiseDetectedEvent
- type RateLimitWaitEvent
- type SDKClient
- type StallStopEvent
- type ToolEvent
- type ToolExecutionEvent
- type ToolExecutionStartEvent
- type VerifyResultEvent
- type WorkspaceDiffEvent
Constants ¶
This section is empty.
Variables ¶
var ErrErrorStop = errors.New("loop stopped: consecutive error threshold exceeded")
ErrErrorStop indicates the loop stopped because too many consecutive iterations emitted errors.
var ErrLoopBlocked = errors.New("loop blocked: model signalled it cannot proceed")
ErrLoopBlocked indicates the model emitted the blocked signal, meaning it cannot make further progress without external intervention.
var ErrLoopCancelled = errors.New("loop cancelled")
ErrLoopCancelled indicates the loop was cancelled by the user.
var ErrLoopTimeout = errors.New("loop timeout exceeded")
ErrLoopTimeout indicates the loop exceeded the configured timeout.
var ErrMaxIterations = errors.New("maximum iterations reached")
ErrMaxIterations indicates the maximum iterations were reached.
var ErrNoChangesStop = errors.New("loop stopped: no changes detected")
ErrNoChangesStop indicates the loop stopped because the working tree stayed clean for the configured number of consecutive iterations.
var ErrRateLimitFatal = errors.New("loop stopped: rate limit reached and --no-rate-limit-wait is set")
ErrRateLimitFatal indicates the loop stopped because --no-rate-limit-wait is set and a rate-limit / quota error was reported.
var ErrStallDetected = errors.New("loop stopped: stall detected (identical responses)")
ErrStallDetected indicates the loop stopped because consecutive iterations produced identical responses.
Functions ¶
func BuildSystemPrompt ¶
BuildSystemPrompt constructs the system prompt from the embedded template. It replaces {{.Task}} with the actual user task and {{.Promise}} with the completion phrase.
Types ¶
type AIResponseEvent ¶
type AIResponseEvent struct {
// Text is the AI response text.
Text string
// Iteration is the current iteration number.
Iteration int
}
AIResponseEvent indicates AI response text was received.
func NewAIResponseEvent ¶
func NewAIResponseEvent(text string, iteration int) *AIResponseEvent
NewAIResponseEvent creates a new AIResponseEvent.
type AutoCommitEvent ¶ added in v0.2.0
AutoCommitEvent indicates Ralph auto-committed the iteration's changes.
func NewAutoCommitEvent ¶ added in v0.2.0
func NewAutoCommitEvent(sha, message, tag string, iteration int) *AutoCommitEvent
NewAutoCommitEvent creates a new AutoCommitEvent.
type BlockedPhraseDetectedEvent ¶ added in v0.2.0
type BlockedPhraseDetectedEvent struct {
// Phrase is the blocked phrase that was detected.
Phrase string
// Iteration is the iteration where the phrase was detected.
Iteration int
}
BlockedPhraseDetectedEvent indicates the assistant emitted the blocked signal, signalling it cannot make further progress without intervention.
func NewBlockedPhraseDetectedEvent ¶ added in v0.2.0
func NewBlockedPhraseDetectedEvent(phrase string, iteration int) *BlockedPhraseDetectedEvent
NewBlockedPhraseDetectedEvent creates a new BlockedPhraseDetectedEvent.
type CarryContextMode ¶ added in v0.2.0
type CarryContextMode string
CarryContextMode controls how a previous iteration's reply is fed into the next iteration's prompt.
const ( // CarryContextOff disables carry-context. Each iteration sees only the // original task prompt. This was Ralph's pre-Phase-A behavior. CarryContextOff CarryContextMode = "off" // CarryContextSummary feeds forward only the contents of the last // `<summary>...</summary>` block emitted by the assistant. This is the // default and matches Huntley's "summarise then carry forward" guidance. CarryContextSummary CarryContextMode = "summary" // CarryContextVerbatim feeds the full last assistant reply forward. It // is the most expensive option and exists for debugging. CarryContextVerbatim CarryContextMode = "verbatim" )
type CheckpointSavedEvent ¶ added in v0.2.0
CheckpointSavedEvent indicates the engine wrote a checkpoint file.
func NewCheckpointSavedEvent ¶ added in v0.2.0
func NewCheckpointSavedEvent(path string, iteration int) *CheckpointSavedEvent
NewCheckpointSavedEvent creates a new CheckpointSavedEvent.
type ErrorEvent ¶
type ErrorEvent struct {
// Error is the error that occurred.
Error error
// Iteration is the current iteration number (0 if not in iteration).
Iteration int
// Recoverable indicates if the error is recoverable.
Recoverable bool
}
ErrorEvent indicates an error occurred.
func NewErrorEvent ¶
func NewErrorEvent(err error, iteration int, recoverable bool) *ErrorEvent
NewErrorEvent creates a new ErrorEvent.
type ErrorStopEvent ¶ added in v0.2.0
type ErrorStopEvent struct {
// Threshold is the configured consecutive-error budget.
Threshold int
// Iteration is the iteration where the threshold was reached.
Iteration int
}
ErrorStopEvent indicates the loop is stopping because too many consecutive iterations emitted errors.
func NewErrorStopEvent ¶ added in v0.2.0
func NewErrorStopEvent(threshold, iteration int) *ErrorStopEvent
NewErrorStopEvent creates a new ErrorStopEvent.
type IterationCompleteEvent ¶
type IterationCompleteEvent struct {
// Iteration is the iteration number (1-based).
Iteration int
// Duration is how long the iteration took.
Duration time.Duration
}
IterationCompleteEvent indicates an iteration completed.
func NewIterationCompleteEvent ¶
func NewIterationCompleteEvent(iteration int, duration time.Duration) *IterationCompleteEvent
NewIterationCompleteEvent creates a new IterationCompleteEvent.
type IterationStartEvent ¶
type IterationStartEvent struct {
// Iteration is the iteration number (1-based).
Iteration int
// MaxIterations is the maximum number of iterations.
MaxIterations int
}
IterationStartEvent indicates an iteration has started.
func NewIterationStartEvent ¶
func NewIterationStartEvent(iteration, maxIterations int) *IterationStartEvent
NewIterationStartEvent creates a new IterationStartEvent.
type IterationTimeoutEvent ¶ added in v0.2.0
type IterationTimeoutEvent struct {
// Timeout is the configured per-iteration timeout.
Timeout time.Duration
// Iteration is the iteration that timed out.
Iteration int
}
IterationTimeoutEvent indicates an iteration hit its per-iteration soft deadline and was cut short. The loop continues with the next iteration unless other stop conditions fire.
func NewIterationTimeoutEvent ¶ added in v0.2.0
func NewIterationTimeoutEvent(timeout time.Duration, iteration int) *IterationTimeoutEvent
NewIterationTimeoutEvent creates a new IterationTimeoutEvent.
type LoopCancelledEvent ¶
type LoopCancelledEvent struct {
// Result contains partial loop result.
Result *LoopResult
}
LoopCancelledEvent indicates the loop was cancelled by the user.
func NewLoopCancelledEvent ¶
func NewLoopCancelledEvent(result *LoopResult) *LoopCancelledEvent
NewLoopCancelledEvent creates a new LoopCancelledEvent.
type LoopCompleteEvent ¶
type LoopCompleteEvent struct {
// Result contains the loop result.
Result *LoopResult
}
LoopCompleteEvent indicates the loop completed successfully.
func NewLoopCompleteEvent ¶
func NewLoopCompleteEvent(result *LoopResult) *LoopCompleteEvent
NewLoopCompleteEvent creates a new LoopCompleteEvent.
type LoopConfig ¶
type LoopConfig struct {
Prompt string
PromisePhrase string
Model string
WorkingDir string
LogLevel string
SystemPrompt string
SystemPromptMode string
// CarryContext controls how the previous iteration's reply is fed into
// the next iteration's prompt. Defaults to CarryContextSummary.
CarryContext CarryContextMode
// CarryContextMaxRunes clamps a carried summary so a chatty assistant
// cannot inflate the next prompt indefinitely. <=0 disables truncation.
CarryContextMaxRunes int
// PlanFile is the path (relative or absolute) to the running fix_plan.md
// scratchpad. Empty means "do not surface a plan file in the prompt".
PlanFile string
// SpecsDir is a directory whose Markdown files are listed in the prompt
// so the assistant knows specs exist. Empty means "no specs".
SpecsDir string
// StopOnNoChanges halts the loop when this many consecutive iterations
// leave the git working tree unchanged. <=0 disables the check.
StopOnNoChanges int
// StopOnError halts the loop when this many consecutive iterations emit
// an error event. <=0 disables the check.
StopOnError int
// IterationTimeout is the per-iteration soft deadline. <=0 disables.
IterationTimeout time.Duration
// VerifyCmd is a shell command run after each iteration. When it
// exits non-zero its captured output is folded into the next
// iteration's prompt so the assistant can react to failing
// tests/builds. Empty disables the feature.
VerifyCmd string
// VerifyTimeout caps a single verify run. <=0 disables.
VerifyTimeout time.Duration
// VerifyMaxBytes truncates the captured stdout/stderr/combined buffers
// per stream. <=0 means unlimited (use with care).
VerifyMaxBytes int
// AutoCommit, when true, runs `git add -A && git commit` after every
// iteration that produced changes. Requires WorkingDir to be a git
// repository.
AutoCommit bool
// AutoCommitOnVerifyOnly, when true, only auto-commits when verify
// succeeded (or is disabled). Defaults to true to avoid recording
// known-bad states.
AutoCommitOnVerifyOnly bool
// AutoCommitMessage is a Go template-free format string for the commit
// message. Use %d for the iteration number. Empty falls back to a
// sensible default.
AutoCommitMessage string
// AutoTag, when non-empty, creates an annotated tag (with %d for
// iteration number) on each successful auto-commit. Tags are never
// pushed.
AutoTag string
// EmitDiffStat, when true, emits a WorkspaceDiffEvent at the end of
// each iteration carrying `git diff --stat HEAD`.
EmitDiffStat bool
// CheckpointFile is the path the engine writes loop state to after
// each iteration. Empty disables checkpointing.
CheckpointFile string
// ResumeFromIteration, when >0, instructs the engine to start
// numbering iterations from this value+1 (i.e. it continues a
// previously-checkpointed run). The engine treats it as advisory and
// still respects MaxIterations.
ResumeFromIteration int
// ResumeSummary, when non-empty, primes the carry-context summary so
// the first resumed iteration sees the previous run's last summary.
ResumeSummary string
// OracleModel is the model used for the second-opinion oracle. Empty
// disables oracle consultations entirely.
OracleModel string
// OracleEvery, when >0, consults the oracle every N iterations.
OracleEvery int
// OracleOnVerifyFail, when true, also consults the oracle whenever
// --verify-cmd fails for an iteration.
OracleOnVerifyFail bool
// PromptStack lists additional prompts (already resolved to text)
// that are prepended to the main prompt in order.
PromptStack []string
// NoRateLimitWait, when true, surfaces Copilot rate-limit errors as
// fatal instead of pausing the loop until reset.
NoRateLimitWait bool
// BlockedPhrase, when non-empty, makes the engine watch for
// <blocked>...</blocked> in every assistant response. When detected the
// loop stops with StateBlocked (exit code 5).
BlockedPhrase string
// StallAfter, when > 0, stops the loop after this many consecutive
// iterations that produce an identical (byte-for-byte) assistant
// response. Protects against models stuck in a non-progressing loop.
StallAfter int
// IterationDelay is a pause inserted between iterations. <=0 disables.
IterationDelay time.Duration
// OnCompleteCmd is a shell command executed after a successful loop
// (promise detected). Empty disables the hook.
OnCompleteCmd string
// OnBlockedCmd is a shell command executed when the model emits the
// blocked signal. Empty disables the hook.
OnBlockedCmd string
MaxIterations int
Timeout time.Duration
DryRun bool
Streaming bool
}
LoopConfig contains configuration for loop execution.
func DefaultLoopConfig ¶
func DefaultLoopConfig() *LoopConfig
DefaultLoopConfig returns a LoopConfig with default values.
type LoopEngine ¶
type LoopEngine struct {
// contains filtered or unexported fields
}
LoopEngine manages the execution of AI development loops. It coordinates with the Copilot SDK, detects completion promises, and handles state transitions.
func NewLoopEngine ¶
func NewLoopEngine(config *LoopConfig, sdk SDKClient) *LoopEngine
NewLoopEngine creates a new loop engine with the given configuration. If sdk is nil, the engine will run in dry-run mode.
func (*LoopEngine) Config ¶
func (e *LoopEngine) Config() *LoopConfig
Config returns the loop configuration.
func (*LoopEngine) Events ¶
func (e *LoopEngine) Events() <-chan any
Events returns a read-only channel for receiving loop events. Subscribers should read from this channel to receive updates.
func (*LoopEngine) Iteration ¶
func (e *LoopEngine) Iteration() int
Iteration returns the current iteration number (1-based). Returns 0 if the loop hasn't started yet.
func (*LoopEngine) SetOracle ¶ added in v0.2.0
func (e *LoopEngine) SetOracle(o OracleClient)
SetOracle attaches an OracleClient. Calling with nil disables oracle consultations even when OracleEvery > 0.
func (*LoopEngine) Start ¶
func (e *LoopEngine) Start(ctx context.Context) (*LoopResult, error)
Start begins loop execution and runs until completion, failure, or cancellation. It returns the loop result containing statistics and outcome. The provided context can be used to cancel execution externally.
func (*LoopEngine) State ¶
func (e *LoopEngine) State() LoopState
State returns the current loop state.
type LoopFailedEvent ¶
type LoopFailedEvent struct {
// Error is the error that caused the failure.
Error error
// Result contains partial loop result.
Result *LoopResult
}
LoopFailedEvent indicates the loop failed.
func NewLoopFailedEvent ¶
func NewLoopFailedEvent(err error, result *LoopResult) *LoopFailedEvent
NewLoopFailedEvent creates a new LoopFailedEvent.
type LoopResult ¶
LoopResult contains the outcome of loop execution.
type LoopStartEvent ¶
type LoopStartEvent struct {
// Config is the loop configuration.
Config *LoopConfig
}
LoopStartEvent indicates the loop has started.
func NewLoopStartEvent ¶
func NewLoopStartEvent(config *LoopConfig) *LoopStartEvent
NewLoopStartEvent creates a new LoopStartEvent.
type LoopState ¶
type LoopState string
LoopState represents the current state of the loop.
const ( // StateIdle indicates the loop is ready to start. StateIdle LoopState = "idle" // StateRunning indicates the loop is executing iterations. StateRunning LoopState = "running" // StateComplete indicates the loop completed successfully. StateComplete LoopState = "complete" // StateBlocked indicates the model signalled it cannot proceed. StateBlocked LoopState = "blocked" // StateFailed indicates the loop failed. StateFailed LoopState = "failed" // StateCancelled indicates the loop was cancelled. StateCancelled LoopState = "cancelled" )
type NoChangesStopEvent ¶ added in v0.2.0
type NoChangesStopEvent struct {
// Threshold is the configured number of clean iterations that triggers
// the stop.
Threshold int
// Iteration is the iteration where the threshold was reached.
Iteration int
}
NoChangesStopEvent indicates the loop is stopping because the working tree stayed clean for a configured number of consecutive iterations.
func NewNoChangesStopEvent ¶ added in v0.2.0
func NewNoChangesStopEvent(threshold, iteration int) *NoChangesStopEvent
NewNoChangesStopEvent creates a new NoChangesStopEvent.
type OracleAdviceEvent ¶ added in v0.2.0
OracleAdviceEvent indicates the second-opinion oracle returned advice that will be folded into the next iteration's prompt.
func NewOracleAdviceEvent ¶ added in v0.2.0
func NewOracleAdviceEvent(model, advice, reason string, iteration int) *OracleAdviceEvent
NewOracleAdviceEvent creates a new OracleAdviceEvent.
type OracleClient ¶ added in v0.2.0
OracleClient produces a single advisory response from a second-opinion model. Implementations must respect the supplied context and return the assistant's text in full.
type PlanUpdatedEvent ¶ added in v0.2.0
type PlanUpdatedEvent struct {
// Path is the absolute path of the plan file.
Path string
// Bytes is the new size of the plan file on disk.
Bytes int
// Iteration is the iteration number that produced the change.
Iteration int
}
PlanUpdatedEvent indicates the assistant changed the running fix_plan.md scratchpad during an iteration.
func NewPlanUpdatedEvent ¶ added in v0.2.0
func NewPlanUpdatedEvent(path string, bytes, iteration int) *PlanUpdatedEvent
NewPlanUpdatedEvent creates a new PlanUpdatedEvent.
type PromiseDetectedEvent ¶
type PromiseDetectedEvent struct {
// Phrase is the promise phrase that was detected.
Phrase string
// Source is where the promise was found (e.g., "ai_response", "tool_output").
Source string
// Iteration is the iteration number where promise was found.
Iteration int
}
PromiseDetectedEvent indicates the promise phrase was found.
func NewPromiseDetectedEvent ¶
func NewPromiseDetectedEvent(phrase, source string, iteration int) *PromiseDetectedEvent
NewPromiseDetectedEvent creates a new PromiseDetectedEvent.
type RateLimitWaitEvent ¶ added in v0.2.0
type RateLimitWaitEvent struct {
// ResetAt is when the rate limit is expected to reset. Only meaningful
// when HasReset is true.
ResetAt time.Time
// Wait is the duration the engine will sleep before retrying.
Wait time.Duration
// Message is the human-readable message reported by the SDK.
Message string
// ErrorType is the SDK-reported error category (e.g. "rate_limit").
ErrorType string
// Iteration is the iteration number being retried.
Iteration int
// HasReset indicates whether ResetAt is meaningful.
HasReset bool
}
RateLimitWaitEvent indicates the loop is paused waiting for a Copilot rate-limit / quota window to reset before retrying the current iteration.
func NewRateLimitWaitEvent ¶ added in v0.2.0
func NewRateLimitWaitEvent(message, errorType string, resetAt time.Time, hasReset bool, wait time.Duration, iteration int) *RateLimitWaitEvent
NewRateLimitWaitEvent creates a new RateLimitWaitEvent.
type SDKClient ¶
type SDKClient interface {
// Start initializes the SDK client.
Start() error
// Stop closes the SDK client and releases resources.
Stop() error
// CreateSession creates a new SDK session.
// The implementation should initialize any SDK session resources and return an error if it fails.
CreateSession(ctx context.Context) error
// DestroySession destroys the current session.
DestroySession(ctx context.Context) error
// SendPrompt sends a prompt to the AI and returns an event stream.
SendPrompt(ctx context.Context, prompt string) (<-chan sdk.Event, error)
// Model returns the configured AI model name.
Model() string
}
SDKClient defines the interface for the Copilot SDK client. This interface abstracts the SDK implementation for testability.
type StallStopEvent ¶ added in v0.2.0
type StallStopEvent struct {
// Threshold is the configured number of identical iterations that
// triggers the stop.
Threshold int
// Iteration is the iteration where the threshold was reached.
Iteration int
}
StallStopEvent indicates the loop stopped because consecutive iterations produced identical assistant responses.
func NewStallStopEvent ¶ added in v0.2.0
func NewStallStopEvent(threshold, iteration int) *StallStopEvent
NewStallStopEvent creates a new StallStopEvent.
type ToolExecutionEvent ¶
ToolExecutionEvent indicates a tool was executed.
type ToolExecutionStartEvent ¶
type ToolExecutionStartEvent struct {
ToolEvent
}
ToolExecutionStartEvent indicates a tool execution has started.
func NewToolExecutionStartEvent ¶
func NewToolExecutionStartEvent(toolName string, params map[string]any, iteration int) *ToolExecutionStartEvent
NewToolExecutionStartEvent creates a new ToolExecutionStartEvent.
type VerifyResultEvent ¶ added in v0.2.0
type VerifyResultEvent struct {
Cmd string
Output string
Iteration int
ExitCode int
Duration time.Duration
Success bool
TimedOut bool
}
VerifyResultEvent indicates the post-iteration verify command finished. It is emitted exactly once per iteration when --verify-cmd is set.
func NewVerifyResultEvent ¶ added in v0.2.0
func NewVerifyResultEvent(cmd, output string, exitCode, iteration int, duration time.Duration, success, timedOut bool) *VerifyResultEvent
NewVerifyResultEvent creates a new VerifyResultEvent.
type WorkspaceDiffEvent ¶ added in v0.2.0
WorkspaceDiffEvent reports a `git diff --stat HEAD` snapshot taken at the end of an iteration.
func NewWorkspaceDiffEvent ¶ added in v0.2.0
func NewWorkspaceDiffEvent(stat string, iteration int) *WorkspaceDiffEvent
NewWorkspaceDiffEvent creates a new WorkspaceDiffEvent.