Documentation
¶
Overview ¶
Package continuation provides durable, application-driven execution across bounded worker runs. It owns lifecycle, accounting, wakeups, and retry boundaries; optional completion and activation policies live in the sibling goal and loop packages. Team, workflow, and product scheduling remain above it.
The package never starts a scheduler or background retry loop. Applications call Advance for one attempt or Drive for a bounded synchronous sequence, and explicitly deliver time and signal wakeups.
Example (TeamWorkflowStyle) ¶
package main
import (
"context"
"fmt"
"github.com/rsbin1178/pips/agent/continuation"
"github.com/rsbin1178/pips/ai"
)
type exampleWorker func(context.Context, continuation.WorkRequest) (continuation.WorkResult, error)
func (worker exampleWorker) Run(
ctx context.Context,
request continuation.WorkRequest,
) (continuation.WorkResult, error) {
return worker(ctx, request)
}
type exampleController func(context.Context, continuation.DecisionRequest) (continuation.Decision, error)
func (controller exampleController) Decide(
ctx context.Context,
request continuation.DecisionRequest,
) (continuation.Decision, error) {
return controller(ctx, request)
}
func exampleEngine() (*continuation.Engine, continuation.Handlers, error) {
store, err := continuation.NewMemoryStore()
if err != nil {
return nil, continuation.Handlers{}, err
}
engine, err := continuation.New(store)
if err != nil {
return nil, continuation.Handlers{}, err
}
workerRef := continuation.HandlerRef{Kind: "example-worker", Version: "v1"}
controllerRef := continuation.HandlerRef{Kind: "example-controller", Version: "v1"}
worker := exampleWorker(func(_ context.Context, request continuation.WorkRequest) (continuation.WorkResult, error) {
return continuation.WorkResult{
Value: ai.JSON(fmt.Sprintf(`{"attempt":%d}`, request.Attempt)),
Progress: continuation.ProgressChanged,
}, nil
})
return engine, continuation.Handlers{
WorkerRef: workerRef, Worker: worker,
ControllerRef: controllerRef,
}, nil
}
func main() {
engine, handlers, _ := exampleEngine()
handlers.Controller = exampleController(func(
context.Context,
continuation.DecisionRequest,
) (continuation.Decision, error) {
return continuation.Decision{
Action: continuation.ActionBlock,
Block: &continuation.Block{Kind: "dependency", Data: ai.JSON(`{"task":"build"}`)},
}, nil
})
execution, _ := engine.Create(context.Background(), continuation.CreateRequest{
ID: "workflow-example", Target: continuation.Target{Kind: "workflow_node", ID: "test"},
Worker: handlers.WorkerRef, Controller: handlers.ControllerRef,
})
blocked, _ := engine.Advance(context.Background(), execution.ID, execution.Revision, handlers)
ready, _ := engine.ResolveBlock(context.Background(), blocked.ID, blocked.Revision, ai.JSON(`{"task":"build","status":"done"}`))
fmt.Println(blocked.Status, ready.Status)
}
Output: blocked ready
Index ¶
- Constants
- Variables
- type Accounting
- type Action
- type Activation
- type ActivationSource
- type Attempt
- type AttemptID
- type Block
- type Cause
- type Clock
- type ClockFunc
- type ConflictError
- type Controller
- type CorruptStoreError
- type CreateRequest
- type Decision
- type DecisionRequest
- type DriveOptions
- type DriveResult
- type Engine
- func (engine *Engine) Advance(ctx context.Context, id ID, expected Revision, handlers Handlers) (Execution, error)
- func (engine *Engine) Cancel(ctx context.Context, id ID, expected Revision, reason string) (Execution, error)
- func (engine *Engine) Create(ctx context.Context, request CreateRequest) (Execution, error)
- func (engine *Engine) Drive(ctx context.Context, id ID, expected Revision, handlers Handlers, ...) (DriveResult, error)
- func (engine *Engine) Fail(ctx context.Context, id ID, expected Revision, reason string) (Execution, error)
- func (engine *Engine) Get(ctx context.Context, id ID) (Execution, error)
- func (engine *Engine) History(ctx context.Context, id ID) ([]Record, error)
- func (engine *Engine) List(ctx context.Context, options ListOptions) (ListPage, error)
- func (engine *Engine) Pause(ctx context.Context, id ID, expected Revision, reason string) (Execution, error)
- func (engine *Engine) ResolveBlock(ctx context.Context, id ID, expected Revision, payload ai.JSON) (Execution, error)
- func (engine *Engine) Resume(ctx context.Context, id ID, expected Revision, reason string) (Execution, error)
- func (engine *Engine) ResumeDue(ctx context.Context, id ID, expected Revision) (Execution, error)
- func (engine *Engine) RetryDecision(ctx context.Context, id ID, expected Revision, reason string) (Execution, error)
- func (engine *Engine) RetryWork(ctx context.Context, id ID, expected Revision, reason string) (Execution, error)
- func (engine *Engine) Signal(ctx context.Context, id ID, expected Revision, signal Signal) (Execution, error)
- type Execution
- type Gate
- type HandlerRef
- type Handlers
- type ID
- type IDSource
- type JSONLStore
- func (store *JSONLStore) CompareAndSwap(ctx context.Context, id ID, expected Revision, next Record) error
- func (store *JSONLStore) Create(ctx context.Context, record Record) error
- func (store *JSONLStore) History(ctx context.Context, id ID) ([]Record, error)
- func (store *JSONLStore) List(ctx context.Context, options ListOptions) (ListPage, error)
- func (store *JSONLStore) Load(ctx context.Context, id ID) (Record, error)
- type Limits
- type ListOptions
- type ListPage
- type MemoryStore
- func (store *MemoryStore) CompareAndSwap(ctx context.Context, id ID, expected Revision, next Record) error
- func (store *MemoryStore) Create(ctx context.Context, record Record) error
- func (store *MemoryStore) History(ctx context.Context, id ID) ([]Record, error)
- func (store *MemoryStore) List(ctx context.Context, options ListOptions) (ListPage, error)
- func (store *MemoryStore) Load(ctx context.Context, id ID) (Record, error)
- type Option
- type Phase
- type Progress
- type Record
- type Remaining
- type Revision
- type Signal
- type SignalSpec
- type StageFailure
- type StateError
- type Status
- type Store
- type StoreLimits
- type StoreOption
- type Suspension
- type Target
- type Transition
- type WaitCondition
- type WorkRequest
- type WorkResult
- type Worker
- type YieldReason
Examples ¶
Constants ¶
const DefaultMaxAttempts = 25
DefaultMaxAttempts is the finite default continuation bound.
Variables ¶
var ( ErrNotFound = errors.New("continuation: not found") ErrExists = errors.New("continuation: already exists") ErrConflict = errors.New("continuation: revision conflict") ErrBusy = errors.New("continuation: execution is active") ErrInvalid = errors.New("continuation: invalid value") ErrTooLarge = errors.New("continuation: value too large") ErrHandlerMismatch = errors.New("continuation: handler mismatch") ErrNotRunnable = errors.New("continuation: execution is not runnable") ErrTerminal = errors.New("continuation: execution is terminal") ErrRetryRequired = errors.New("continuation: explicit retry required") ErrNotWaiting = errors.New("continuation: execution is not waiting") ErrNotDue = errors.New("continuation: wait is not due") ErrSignalMismatch = errors.New("continuation: signal does not match") ErrSignalExpired = errors.New("continuation: signal wait expired") ErrCorruptStore = errors.New("continuation: corrupt store") ErrStoreFull = errors.New("continuation: store limit reached") )
Lifecycle and store errors.
Functions ¶
This section is empty.
Types ¶
type Accounting ¶
type Accounting struct {
Attempts int `json:"attempts"`
Turns int `json:"turns"`
Usage ai.Usage `json:"usage"`
ActiveDuration time.Duration `json:"active_duration"`
}
Accounting is observed cumulative usage.
func (Accounting) Tokens ¶
func (a Accounting) Tokens() int
Tokens returns cumulative input plus output tokens.
type Activation ¶
type Activation struct {
Source ActivationSource `json:"source"`
At time.Time `json:"at"`
SignalID string `json:"signal_id,omitempty"`
Payload ai.JSON `json:"payload,omitempty"`
}
Activation is durable evidence for why a new Work stage became runnable.
type ActivationSource ¶
type ActivationSource string
ActivationSource identifies the explicit event that made work runnable.
const ( ActivationInitial ActivationSource = "initial" ActivationSignal ActivationSource = "signal" ActivationTime ActivationSource = "time" ActivationBlock ActivationSource = "block" )
Activation sources.
type Attempt ¶
type Attempt struct {
ID AttemptID `json:"id"`
Number int `json:"number"`
Phase Phase `json:"phase"`
Activation *Activation `json:"activation,omitempty"`
WorkStartedAt time.Time `json:"work_started_at"`
WorkCompletedAt time.Time `json:"work_completed_at,omitzero"`
DecisionStartedAt time.Time `json:"decision_started_at,omitzero"`
DecisionEndedAt time.Time `json:"decision_ended_at,omitzero"`
Work *WorkResult `json:"work,omitempty"`
Decision *Decision `json:"decision,omitempty"`
Failure *StageFailure `json:"failure,omitempty"`
Interrupted bool `json:"interrupted,omitempty"`
}
Attempt records the durable Work/Decision boundary.
type AttemptID ¶
type AttemptID string
AttemptID identifies one Worker invocation and its following decision.
type Cause ¶
type Cause string
Cause identifies why a durable transition occurred.
const ( CauseCreate Cause = "create" CauseStageStart Cause = "stage_start" CauseWorkComplete Cause = "work_complete" CauseStageInterrupted Cause = "stage_interrupted" CauseControllerAction Cause = "controller_action" CausePauseRequested Cause = "pause_requested" CausePause Cause = "pause" CauseResume Cause = "resume" CauseRetryWork Cause = "retry_work" CauseRetryDecision Cause = "retry_decision" CauseSignal Cause = "signal" CauseTimeWake Cause = "time_wake" CauseBlockResolved Cause = "block_resolved" CauseCancelRequested Cause = "cancel_requested" CauseCancel Cause = "cancel" CauseFail Cause = "fail" CauseLimit Cause = "limit" CauseRecovery Cause = "recovery" )
Transition causes.
type ConflictError ¶
ConflictError reports the optimistic revision mismatch.
type Controller ¶
type Controller interface {
Decide(context.Context, DecisionRequest) (Decision, error)
}
Controller evaluates a durable Work result without rerunning it.
type CorruptStoreError ¶
CorruptStoreError identifies a malformed durable execution record.
func (*CorruptStoreError) Error ¶
func (e *CorruptStoreError) Error() string
Error implements error.
func (*CorruptStoreError) Unwrap ¶
func (e *CorruptStoreError) Unwrap() error
Unwrap exposes ErrCorruptStore.
type CreateRequest ¶
type CreateRequest struct {
ID ID
Target Target
Worker HandlerRef
Controller HandlerRef
ControllerState ai.JSON
Input ai.JSON
Limits Limits
}
CreateRequest configures a new Execution.
type Decision ¶
type Decision struct {
Action Action `json:"action"`
Reason string `json:"reason,omitempty"`
State ai.JSON `json:"state,omitempty"`
NextInput ai.JSON `json:"next_input,omitempty"`
Wait *WaitCondition `json:"wait,omitempty"`
Block *Block `json:"block,omitempty"`
Output ai.JSON `json:"output,omitempty"`
Progress Progress `json:"progress,omitempty"`
Usage ai.Usage `json:"usage,omitzero"`
}
Decision controls the durable state after a completed Work stage.
type DecisionRequest ¶
type DecisionRequest struct {
ExecutionID ID `json:"execution_id"`
AttemptID AttemptID `json:"attempt_id"`
Attempt int `json:"attempt"`
Target Target `json:"target"`
Work WorkResult `json:"work"`
Activation *Activation `json:"activation,omitempty"`
ControllerState ai.JSON `json:"controller_state,omitempty"`
Limits Limits `json:"limits"`
Accounting Accounting `json:"accounting"`
}
DecisionRequest is the immutable input for post-Work evaluation.
type DriveOptions ¶
DriveOptions bounds synchronous advancement.
type DriveResult ¶
type DriveResult struct {
Execution Execution
Advances int
Yield YieldReason
}
DriveResult is the latest state and why Drive yielded.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine coordinates one process's access to durable continuation state.
func (*Engine) Advance ¶
func (engine *Engine) Advance( ctx context.Context, id ID, expected Revision, handlers Handlers, ) (Execution, error)
Advance performs at most one Worker invocation and its Controller decision.
func (*Engine) Cancel ¶
func (engine *Engine) Cancel( ctx context.Context, id ID, expected Revision, reason string, ) (Execution, error)
Cancel records product cancellation and cancels a locally active stage.
func (*Engine) Drive ¶
func (engine *Engine) Drive( ctx context.Context, id ID, expected Revision, handlers Handlers, options DriveOptions, ) (DriveResult, error)
Drive synchronously repeats Advance within a mandatory finite quantum.
func (*Engine) Fail ¶
func (engine *Engine) Fail( ctx context.Context, id ID, expected Revision, reason string, ) (Execution, error)
Fail records an explicit caller-requested terminal failure.
func (*Engine) Pause ¶
func (engine *Engine) Pause( ctx context.Context, id ID, expected Revision, reason string, ) (Execution, error)
Pause durably requests suspension and cancels a locally active stage.
func (*Engine) ResolveBlock ¶
func (engine *Engine) ResolveBlock( ctx context.Context, id ID, expected Revision, payload ai.JSON, ) (Execution, error)
ResolveBlock supplies external input and starts a new Work attempt later.
func (*Engine) Resume ¶
func (engine *Engine) Resume( ctx context.Context, id ID, expected Revision, reason string, ) (Execution, error)
Resume restores a Paused execution without bypassing waits or retries.
func (*Engine) ResumeDue ¶
func (engine *Engine) ResumeDue( ctx context.Context, id ID, expected Revision, ) (Execution, error)
ResumeDue explicitly wakes a wait whose NotBefore time has arrived.
func (*Engine) RetryDecision ¶
func (engine *Engine) RetryDecision( ctx context.Context, id ID, expected Revision, reason string, ) (Execution, error)
RetryDecision re-evaluates the same durable Work result.
type Execution ¶
type Execution struct {
ID ID `json:"id"`
Revision Revision `json:"revision"`
Status Status `json:"status"`
Phase Phase `json:"phase"`
Target Target `json:"target"`
Worker HandlerRef `json:"worker"`
Controller HandlerRef `json:"controller"`
ControllerState ai.JSON `json:"controller_state,omitempty"`
NextInput ai.JSON `json:"next_input,omitempty"`
Activation *Activation `json:"activation,omitempty"`
Wait *WaitCondition `json:"wait,omitempty"`
Block *Block `json:"block,omitempty"`
Suspension *Suspension `json:"suspension,omitempty"`
CurrentAttempt *Attempt `json:"current_attempt,omitempty"`
LastAttempt *Attempt `json:"last_attempt,omitempty"`
Limits Limits `json:"limits"`
Accounting Accounting `json:"accounting"`
Reason string `json:"reason,omitempty"`
Output ai.JSON `json:"output,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
Execution is the latest full continuation snapshot.
type HandlerRef ¶
HandlerRef durably identifies a compatible handler implementation.
type Handlers ¶
type Handlers struct {
WorkerRef HandlerRef
Worker Worker
ControllerRef HandlerRef
Controller Controller
}
Handlers binds runtime implementations to persisted references.
type JSONLStore ¶
type JSONLStore struct {
// contains filtered or unexported fields
}
JSONLStore is a bounded single-process directory Store.
func NewJSONLStore ¶
func NewJSONLStore(dir string, options ...StoreOption) (*JSONLStore, error)
NewJSONLStore opens a directory-backed control store.
func (*JSONLStore) CompareAndSwap ¶
func (store *JSONLStore) CompareAndSwap( ctx context.Context, id ID, expected Revision, next Record, ) error
CompareAndSwap implements Store.
func (*JSONLStore) Create ¶
func (store *JSONLStore) Create(ctx context.Context, record Record) error
Create implements Store.
func (*JSONLStore) List ¶
func (store *JSONLStore) List(ctx context.Context, options ListOptions) (ListPage, error)
List implements Store.
type Limits ¶
type Limits struct {
MaxAttempts int `json:"max_attempts,omitempty"`
MaxTurns int `json:"max_turns,omitempty"`
MaxTokens int `json:"max_tokens,omitempty"`
MaxActiveDuration time.Duration `json:"max_active_duration,omitempty"`
Deadline time.Time `json:"deadline,omitzero"`
}
Limits are cumulative hard execution limits. Zero means unset except that MaxAttempts zero resolves to DefaultMaxAttempts; -1 means unlimited.
type ListOptions ¶
ListOptions bounds one lexicographically ordered store page.
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore is a bounded in-memory Store for tests and ephemeral applications.
func NewMemoryStore ¶
func NewMemoryStore(options ...StoreOption) (*MemoryStore, error)
NewMemoryStore creates an empty in-memory control store.
func (*MemoryStore) CompareAndSwap ¶
func (store *MemoryStore) CompareAndSwap( ctx context.Context, id ID, expected Revision, next Record, ) error
CompareAndSwap implements Store.
func (*MemoryStore) Create ¶
func (store *MemoryStore) Create(ctx context.Context, record Record) error
Create implements Store.
func (*MemoryStore) List ¶
func (store *MemoryStore) List(ctx context.Context, options ListOptions) (ListPage, error)
List implements Store.
type Option ¶
type Option func(*engineConfig) error
Option configures an Engine.
func WithIDSource ¶
WithIDSource replaces ID generation for deterministic applications and tests.
type Progress ¶
type Progress string
Progress classifies whether a completed stage made observable progress.
type Record ¶
type Record struct {
Execution Execution `json:"execution"`
Transition Transition `json:"transition"`
}
Record is a full post-transition snapshot.
type Remaining ¶
type Remaining struct {
Attempts int `json:"attempts,omitempty"`
Turns int `json:"turns,omitempty"`
Tokens int `json:"tokens,omitempty"`
ActiveDuration time.Duration `json:"active_duration,omitempty"`
Deadline time.Time `json:"deadline,omitzero"`
}
Remaining reports cooperative budgets before a Worker invocation.
type Signal ¶
type Signal struct {
ID string `json:"id"`
Key string `json:"key"`
Payload ai.JSON `json:"payload,omitempty"`
}
Signal is one idempotent external wakeup delivery.
type SignalSpec ¶
type SignalSpec struct {
Key string `json:"key"`
}
SignalSpec selects one exact, application-delivered signal key.
type StageFailure ¶
StageFailure is the bounded durable projection of a stage error.
type StateError ¶
StateError describes an operation rejected by the current lifecycle state.
type Status ¶
type Status string
Status is the durable lifecycle state of an Execution.
const ( StatusReady Status = "ready" StatusRunning Status = "running" StatusWaiting Status = "waiting" StatusPauseRequested Status = "pause_requested" StatusPaused Status = "paused" StatusBlocked Status = "blocked" StatusInterrupted Status = "interrupted" StatusCancelRequested Status = "cancel_requested" StatusCompleted Status = "completed" StatusFailed Status = "failed" StatusCancelled Status = "cancelled" StatusLimited Status = "limited" )
Execution statuses.
type Store ¶
type Store interface {
Create(context.Context, Record) error
Load(context.Context, ID) (Record, error)
CompareAndSwap(context.Context, ID, Revision, Record) error
List(context.Context, ListOptions) (ListPage, error)
History(context.Context, ID) ([]Record, error)
}
Store is the durable optimistic control-state boundary.
type StoreLimits ¶
type StoreLimits struct {
MaxRecordBytes int
MaxFileBytes int64
MaxTransitions int
MaxListPage int
}
StoreLimits bound local control-store resource use.
type StoreOption ¶
type StoreOption func(*storeConfig) error
StoreOption configures a local Store.
func WithStoreLimits ¶
func WithStoreLimits(limits StoreLimits) StoreOption
WithStoreLimits replaces positive local-store limits.
type Suspension ¶
type Suspension struct {
Status Status `json:"status"`
Phase Phase `json:"phase"`
Wait *WaitCondition `json:"wait,omitempty"`
Block *Block `json:"block,omitempty"`
RetryRequired bool `json:"retry_required,omitempty"`
Reason string `json:"reason,omitempty"`
}
Suspension captures the state restored by Resume.
type Transition ¶
type Transition struct {
Revision Revision `json:"revision"`
At time.Time `json:"at"`
From Status `json:"from,omitempty"`
To Status `json:"to"`
Phase Phase `json:"phase"`
Cause Cause `json:"cause"`
AttemptID AttemptID `json:"attempt_id,omitempty"`
SignalID string `json:"signal_id,omitempty"`
Reason string `json:"reason,omitempty"`
}
Transition is operational audit data for one revision.
type WaitCondition ¶
type WaitCondition struct {
NotBefore *time.Time `json:"not_before,omitempty"`
Signal *SignalSpec `json:"signal,omitempty"`
}
WaitCondition is satisfied by NotBefore or Signal when both are present.
type WorkRequest ¶
type WorkRequest struct {
ExecutionID ID `json:"execution_id"`
AttemptID AttemptID `json:"attempt_id"`
Attempt int `json:"attempt"`
Target Target `json:"target"`
Input ai.JSON `json:"input,omitempty"`
Activation *Activation `json:"activation,omitempty"`
Limits Limits `json:"limits"`
Accounting Accounting `json:"accounting"`
Remaining Remaining `json:"remaining"`
}
WorkRequest is the immutable input for one bounded Worker invocation.
type WorkResult ¶
type WorkResult struct {
Value ai.JSON `json:"value,omitempty"`
Turns int `json:"turns"`
Usage ai.Usage `json:"usage"`
Progress Progress `json:"progress"`
}
WorkResult is durable Controller evidence and observed Worker accounting.
type Worker ¶
type Worker interface {
Run(context.Context, WorkRequest) (WorkResult, error)
}
Worker performs one bounded unit of application-defined work.
type YieldReason ¶
type YieldReason string
YieldReason says why Drive returned control to its caller.
const ( YieldTerminal YieldReason = "terminal" YieldWaiting YieldReason = "waiting" YieldPaused YieldReason = "paused" YieldBlocked YieldReason = "blocked" YieldInterrupted YieldReason = "interrupted" YieldGate YieldReason = "gate" YieldNoProgress YieldReason = "no_progress" YieldQuantum YieldReason = "quantum" YieldContext YieldReason = "context" YieldError YieldReason = "error" )
Drive yield reasons.