core

package
v0.2.3 Latest Latest
Warning

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

Go to latest
Published: Apr 27, 2026 License: MIT Imports: 14 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
var ErrErrorStop = errors.New("loop stopped: consecutive error threshold exceeded")

ErrErrorStop indicates the loop stopped because too many consecutive iterations emitted errors.

View Source
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.

View Source
var ErrLoopCancelled = errors.New("loop cancelled")

ErrLoopCancelled indicates the loop was cancelled by the user.

View Source
var ErrLoopTimeout = errors.New("loop timeout exceeded")

ErrLoopTimeout indicates the loop exceeded the configured timeout.

View Source
var ErrMaxIterations = errors.New("maximum iterations reached")

ErrMaxIterations indicates the maximum iterations were reached.

View Source
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.

View Source
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.

View Source
var ErrStallDetected = errors.New("loop stopped: stall detected (identical responses)")

ErrStallDetected indicates the loop stopped because consecutive iterations produced identical responses.

Functions

func BuildSystemPrompt

func BuildSystemPrompt(promisePhrase string) string

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

type AutoCommitEvent struct {
	SHA       string
	Message   string
	Tag       string
	Iteration int
}

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

type CheckpointSavedEvent struct {
	Path      string
	Iteration int
}

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

type LoopResult struct {
	Error      error
	State      LoopState
	Iterations int
	Duration   time.Duration
}

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"
)

func (LoopState) String

func (s LoopState) String() string

String returns the string representation of the state.

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

type OracleAdviceEvent struct {
	Model     string
	Advice    string
	Iteration int
	Reason    string
}

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

type OracleClient interface {
	Consult(ctx context.Context, prompt string) (string, error)
}

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 ToolEvent

type ToolEvent struct {
	Parameters map[string]any
	ToolName   string
	Iteration  int
}

ToolEvent describes a tool invocation request observed by the loop engine.

func (*ToolEvent) Info

func (e *ToolEvent) Info(emoji string) string

Info returns a formatted string describing the tool execution based on parameters. This provides human-readable information about what the tool is doing.

type ToolExecutionEvent

type ToolExecutionEvent struct {
	Error error
	ToolEvent
	Result   string
	Duration time.Duration
}

ToolExecutionEvent indicates a tool was executed.

func NewToolExecutionEvent

func NewToolExecutionEvent(toolName string, params map[string]any, result string, err error, duration time.Duration, iteration int) *ToolExecutionEvent

NewToolExecutionEvent creates a new ToolExecutionEvent.

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

type WorkspaceDiffEvent struct {
	Stat      string
	Iteration int
}

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.

Jump to

Keyboard shortcuts

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