workflow

package
v2.2.0 Latest Latest
Warning

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

Go to latest
Published: Sep 13, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package workflow defines types and logic for PromptPack workflow state machines (RFC 0005).

A workflow is an event-driven state machine layered over a PromptPack's prompts. Each state references a prompt_task and defines transitions via named events.

Index

Constants

View Source
const (
	BudgetLimitTotalVisits = "max_total_visits"
	BudgetLimitToolCalls   = "max_tool_calls"
	BudgetLimitWallTimeSec = "max_wall_time_sec"
)

Budget limit names, used by BudgetExhaustedError.Limit.

View Source
const (
	ArtifactModeReplace = "replace"
	ArtifactModeAppend  = "append"
)

Artifact merge modes (schema $defs/ArtifactDef.mode).

View Source
const (
	PersistenceTransient  = "transient"
	PersistencePersistent = "persistent"
)

Persistence values.

View Source
const (
	OrchestrationInternal = "internal"
	OrchestrationExternal = "external"
	OrchestrationHybrid   = "hybrid"
	// OrchestrationComposition runs a declarative composition step-graph for the
	// state instead of an LLM-driven turn (RFC 0010).
	OrchestrationComposition = "composition"
)

Orchestration values.

View Source
const (
	// ControlUser yields the conversation to the user on entering the state.
	ControlUser = "user"
	// ControlAgent runs another agent round in the state without yielding,
	// for transient routing and processing states.
	ControlAgent = "agent"
)

Turn control values (schema $defs/WorkflowState.control, RFC 0014).

Orthogonal to Orchestration: that declares who INITIATES a transition, this declares who holds the turn AFTER one. Read a state's value through HoldsFloor, which documents how an absent value resolves here.

View Source
const ArtifactExecutorMode = "workflow-artifact"

ArtifactExecutorMode is the executor name for Mode-based routing.

View Source
const ArtifactToolName = "workflow__set_artifact"

ArtifactToolName is the qualified name of the workflow set_artifact tool.

View Source
const MaxHistoryLength = 1000

MaxHistoryLength is the maximum number of state transitions retained in context history. When exceeded, the oldest transitions are discarded.

View Source
const TransitionExecutorMode = "workflow-transition"

TransitionExecutorMode is the executor name used for Mode-based routing.

View Source
const TransitionToolName = "workflow__transition"

TransitionToolName is the qualified name of the workflow transition tool.

Variables

View Source
var (
	// ErrInvalidEvent is returned when an event is not defined for the current state.
	ErrInvalidEvent = errors.New("invalid event for current state")
	// ErrTerminalState is returned when trying to process an event in a terminal state.
	ErrTerminalState = errors.New("current state is terminal (no outgoing transitions)")
)
View Source
var (
	// ErrMaxVisitsExceeded is returned when a state's max_visits limit is reached
	// and no on_max_visits fallback is configured. The concrete error returned
	// is typically a *MaxVisitsExceededError wrapping this sentinel; callers
	// wanting structured details should use errors.As.
	ErrMaxVisitsExceeded = errors.New("max visits exceeded")

	// ErrBudgetExhausted is returned when a workflow-level budget limit is
	// reached. The concrete error returned is typically a *BudgetExhaustedError
	// wrapping this sentinel; callers wanting structured details should use
	// errors.As.
	ErrBudgetExhausted = errors.New("workflow budget exhausted")
)

Sentinel errors for workflow execution.

Functions

func ArtifactMode

func ArtifactMode(def *ArtifactDef) string

ArtifactMode returns an artifact slot's merge mode, applying the spec default of "replace" when the pack did not set one.

func BuildArtifactToolDescriptor

func BuildArtifactToolDescriptor(spec *Spec) *tools.ToolDescriptor

BuildArtifactToolDescriptor creates a tools.ToolDescriptor for the workflow__set_artifact tool, with artifact names as an enum constraint.

func BuildTransitionToolDescriptor

func BuildTransitionToolDescriptor(events []string) *tools.ToolDescriptor

BuildTransitionToolDescriptor creates a tools.ToolDescriptor for the workflow__transition tool with the given events as an enum constraint.

func HoldsFloor

func HoldsFloor(s *State) bool

HoldsFloor reports whether the agent keeps the turn on entering s, rather than yielding the conversation to the user (RFC 0014's `control`).

True means the turn runs on: the tool loop swaps to this state's prompt and tools and takes another round, so a routing or processing state can speak without waiting for a user message.

An absent control does NOT resolve to "user" here, and that is deliberate

RFC 0014 defaults the field to "user" and describes that as "the behavior of every state before v1.7.0". That is not true of this runtime. In-turn state handoff shipped 2026-08-14 (#1747); RFC 0014 arrived with the v1.7.0 schema on 2026-08-31, seventeen days later. Before it the spec said nothing about who holds the turn after a transition — there was no default to deviate from — and this runtime already ran the destination state, which is what lets a pack route through several states and produce one reply.

Honoring the RFC default would silently invert that for every pack written before it, turning routing states into dead stops. So a DECLARED control is honored exactly as specified, and an absent one keeps the behavior packs already rely on.

This is a permanent, deliberate divergence, confined to what absent means. It is not an oversight and not a migration step, so do not "fix" it back to the spec default: doing so changes the behavior of every pack that never declares the field, which is all of them written before v1.7.0.

The RFC's claim that "user" was the behavior of every state before v1.7.0 is what this contradicts, and correcting that wording is AltairaLabs/promptpack-spec#79. The default itself is not in dispute.

A nil state yields: there is nothing to run on into.

func IsTerminal

func IsTerminal(s *State) bool

IsTerminal reports whether a state ends the workflow.

terminal is *bool on the generated type because the spec makes it optional, and absent means "not terminal" rather than "unset". A state with no outgoing events is terminal regardless of the flag.

func MaxVisitsOf

func MaxVisitsOf(s *State) int

MaxVisitsOf returns a state's visit cap, or 0 when uncapped.

func OrchestrationOf

func OrchestrationOf(s *State) string

OrchestrationOf returns a state's control mode, defaulting to OrchestrationInternal when the state does not declare one.

func RegisterArtifactTool

func RegisterArtifactTool(registry *tools.Registry, spec *Spec)

RegisterArtifactTool registers the workflow__set_artifact tool in the given registry. Only registers if the spec declares artifacts on any state.

func RegisterTransitionTool

func RegisterTransitionTool(registry *tools.Registry, state *State)

RegisterTransitionTool registers the workflow__transition tool in the given registry for the specified state's available events. Skips terminal states (no events) and externally orchestrated states.

Both the SDK (WorkflowCapability) and Arena (engine) use this function.

func SortedEvents

func SortedEvents(onEvent map[string]string) []string

SortedEvents returns a sorted copy of the event keys from an OnEvent map.

Types

type ArtifactDef

type ArtifactDef = packspec.ArtifactDef

ArtifactDef declares a named artifact slot on a workflow state.

Generated from the schema: an ALIAS for packspec.ArtifactDef.

Mode is a *string, not a string: the spec defaults it to "replace", so "absent" and "explicitly empty" are different facts and a plain field would collapse them. Read it through ArtifactMode, which applies the default.

type ArtifactExecutor

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

ArtifactExecutor implements tools.Executor for workflow__set_artifact. Unlike TransitionExecutor, artifact mutations are safe to apply immediately since they only modify the context map without closing/reopening conversations.

func NewArtifactExecutor

func NewArtifactExecutor(sm *StateMachine) *ArtifactExecutor

NewArtifactExecutor creates an ArtifactExecutor for the given state machine.

func (*ArtifactExecutor) Execute

Execute implements tools.Executor. Sets the artifact value on the state machine.

func (*ArtifactExecutor) Name

func (e *ArtifactExecutor) Name() string

Name implements tools.Executor.

type ArtifactSnapshot

type ArtifactSnapshot struct {
	FromState string            `json:"from_state"`
	ToState   string            `json:"to_state"`
	Event     string            `json:"event"`
	Values    map[string]string `json:"values"`
	Timestamp time.Time         `json:"timestamp"`
}

ArtifactSnapshot captures artifact values at a specific state transition.

type Budget

type Budget = packspec.WorkflowBudget

Budget defines workflow-level resource limits from the engine block. Generated from the schema: an ALIAS for packspec.WorkflowBudget. Optional limits are pointers so "no limit" is distinct from a limit of zero.

type BudgetExhaustedError

type BudgetExhaustedError struct {
	// Limit is one of BudgetLimitTotalVisits, BudgetLimitToolCalls,
	// BudgetLimitWallTimeSec.
	Limit string
	// Current is the observed value at the time the limit was hit.
	Current int
	// Max is the configured limit.
	Max int
	// CurrentState is the state the workflow was in when the budget tripped.
	CurrentState string
}

BudgetExhaustedError is the structured error returned from ProcessEvent when a workflow-level budget is reached. It wraps ErrBudgetExhausted so errors.Is still matches.

func (*BudgetExhaustedError) Error

func (e *BudgetExhaustedError) Error() string

Error returns a human-readable description.

func (*BudgetExhaustedError) Unwrap

func (e *BudgetExhaustedError) Unwrap() error

Unwrap returns the sentinel for errors.Is.

type Context

type Context struct {
	CurrentState    string             `json:"current_state"`
	History         []StateTransition  `json:"history"`
	Metadata        map[string]any     `json:"metadata,omitempty"`
	VisitCounts     map[string]int     `json:"visit_counts,omitempty"`     // RFC 0009: per-state visit counts
	TotalToolCalls  int                `json:"total_tool_calls,omitempty"` // RFC 0009: workflow-wide tool call count
	Artifacts       map[string]string  `json:"artifacts,omitempty"`        // RFC 0009: current artifact values
	ArtifactHistory []ArtifactSnapshot `json:"artifact_history,omitempty"` // RFC 0009: artifact values at each transition
	StartedAt       time.Time          `json:"started_at"`
	UpdatedAt       time.Time          `json:"updated_at"`
}

Context holds the runtime state of a workflow execution.

func NewContext

func NewContext(entryState string, now time.Time) *Context

NewContext creates a new Context initialized at the given entry state.

func (*Context) Clone

func (ctx *Context) Clone() *Context

Clone returns a deep copy of the Context. Nil collections on the source are preserved as nil on the clone so callers can distinguish "absent" from "present but empty" across the round-trip.

func (*Context) GetArtifact

func (ctx *Context) GetArtifact(name string) string

GetArtifact returns an artifact value, or empty string if not set.

func (*Context) IncrementToolCalls

func (ctx *Context) IncrementToolCalls(n int)

IncrementToolCalls adds n to the workflow-wide tool call counter.

func (*Context) LastTransition

func (ctx *Context) LastTransition() *StateTransition

LastTransition returns the most recent transition, or nil if none.

func (*Context) RecordTransition

func (ctx *Context) RecordTransition(from, to, event string, ts time.Time)

RecordTransition records a state transition and updates the current state.

func (*Context) SetArtifact

func (ctx *Context) SetArtifact(name, value, mode string)

SetArtifact sets an artifact value, respecting the mode (replace or append).

func (*Context) TotalVisits

func (ctx *Context) TotalVisits() int

TotalVisits returns the sum of all per-state visit counts.

func (*Context) TransitionCount

func (ctx *Context) TransitionCount() int

TransitionCount returns the number of transitions recorded.

type MaxVisitsExceededError

type MaxVisitsExceededError struct {
	// FromState is the state the transition was leaving.
	FromState string
	// OriginalTarget is the state whose max_visits was reached.
	OriginalTarget string
	// Event is the transition event that triggered the attempt.
	Event string
	// VisitCount is the number of times OriginalTarget had already been entered.
	VisitCount int
	// MaxVisits is the declared limit on OriginalTarget.
	MaxVisits int
}

MaxVisitsExceededError is the structured error returned from ProcessEvent when a state has reached its max_visits cap and no on_max_visits fallback is configured. It wraps ErrMaxVisitsExceeded so errors.Is still matches.

func (*MaxVisitsExceededError) Error

func (e *MaxVisitsExceededError) Error() string

Error returns a human-readable description.

func (*MaxVisitsExceededError) Unwrap

func (e *MaxVisitsExceededError) Unwrap() error

Unwrap returns the sentinel for errors.Is.

type Orchestration

type Orchestration string

Orchestration is the control mode for a workflow state.

type PendingTransition

type PendingTransition struct {
	Event          string           `json:"event"`
	ContextSummary string           `json:"context"`
	HostExtras     tools.HostExtras `json:"host_extras,omitempty"`
}

PendingTransition captures a deferred workflow transition from a tool call.

type Persistence

type Persistence string

Persistence is the storage hint for a workflow state.

type Spec

type Spec = packspec.WorkflowConfig

Spec is a pack's workflow state-machine specification.

Generated. It was hand-written only because its states map held a hand-written State; it carries no methods of its own.

func ParseConfig

func ParseConfig(raw interface{}) (*Spec, error)

ParseConfig parses an untyped workflow config (typically from config.Workflow which is stored as interface{}) into a typed Spec. Returns nil, nil when raw is nil.

type State

type State = packspec.WorkflowState

State is a single state within a workflow.

Generated. terminal, max_visits and orchestration are pointers on the generated type because the spec makes them optional — which is more correct than the values they replaced, since absent is now distinguishable from the zero. Use IsTerminal, MaxVisitsOf and OrchestrationOf rather than reading them directly; they resolve the documented default in one place.

type StateMachine

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

StateMachine manages workflow state transitions.

func NewStateMachine

func NewStateMachine(spec *Spec) *StateMachine

NewStateMachine creates a state machine from a workflow spec. It initializes the context to the entry state.

func NewStateMachineFromContext

func NewStateMachineFromContext(spec *Spec, ctx *Context) *StateMachine

NewStateMachineFromContext restores a state machine from persisted context.

func (*StateMachine) Artifacts

func (sm *StateMachine) Artifacts() map[string]string

Artifacts returns a snapshot of the current artifact values.

func (*StateMachine) AvailableEvents

func (sm *StateMachine) AvailableEvents() []string

AvailableEvents returns the set of valid events for the current state, sorted.

func (*StateMachine) Context

func (sm *StateMachine) Context() *Context

Context returns a snapshot of the current workflow context for persistence.

func (*StateMachine) CurrentPromptTask

func (sm *StateMachine) CurrentPromptTask() string

CurrentPromptTask returns the prompt_task for the current state.

func (*StateMachine) CurrentState

func (sm *StateMachine) CurrentState() string

CurrentState returns the name of the current state.

func (*StateMachine) IncrementToolCalls

func (sm *StateMachine) IncrementToolCalls(n int)

IncrementToolCalls adds n to the workflow-wide tool call counter. Thread-safe; intended to be called by the SDK after tool executions.

func (*StateMachine) IsTerminal

func (sm *StateMachine) IsTerminal() bool

IsTerminal returns true if the current state is terminal. A state is terminal when explicitly marked (Terminal: true) or when it has no outgoing transitions (backward compatible).

func (*StateMachine) ProcessEvent

func (sm *StateMachine) ProcessEvent(event string) (*TransitionResult, error)

ProcessEvent applies an event and transitions to the target state. Returns a TransitionResult describing the transition (including any max_visits redirect). Returns ErrMaxVisitsExceeded when the target state's visit limit is reached and no on_max_visits fallback is set. Returns ErrBudgetExhausted when a workflow-level budget limit is reached.

func (*StateMachine) SetArtifact

func (sm *StateMachine) SetArtifact(name, value string)

SetArtifact sets an artifact value on the workflow context. The mode is looked up from the spec's artifact definitions for the current state. Thread-safe.

func (*StateMachine) WithTimeFunc

func (sm *StateMachine) WithTimeFunc(fn TimeFunc) *StateMachine

WithTimeFunc sets a custom time function for deterministic tests.

type StateTransition

type StateTransition struct {
	From      string    `json:"from"`
	To        string    `json:"to"`
	Event     string    `json:"event"`
	Timestamp time.Time `json:"timestamp"`
}

StateTransition records a single state transition.

type TimeFunc

type TimeFunc func() time.Time

TimeFunc returns the current time. Override for deterministic tests.

type TransitionExecutor

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

TransitionExecutor implements tools.Executor for workflow__transition.

It defers the state transition (ProcessEvent) until CommitPending is called, ensuring the full pipeline turn completes before state changes. The optional OnCommit callback is invoked after a successful commit so consumers (Arena, SDK) can run their post-commit work (re-register tool descriptor, update scenario TaskType, emit observability events) from a single hook.

func NewTransitionExecutor

func NewTransitionExecutor(sm *StateMachine, spec *Spec) *TransitionExecutor

NewTransitionExecutor creates a TransitionExecutor for the given state machine.

func (*TransitionExecutor) ClearPending

func (e *TransitionExecutor) ClearPending()

ClearPending discards any pending transition without committing.

func (*TransitionExecutor) CommitPending

func (e *TransitionExecutor) CommitPending() (*TransitionResult, error)

CommitPending applies the pending transition by calling ProcessEvent. Returns nil, nil if no transition is pending. After commit, the pending state is cleared. Thread-safe. Fires OnCommit on success.

func (*TransitionExecutor) Execute

Execute implements tools.Executor.

The request is stored as pending and CommitPending applies it after the pipeline turn finishes. This is RFC 0005's deferred-commit pattern.

The LLM's `context` argument is stored on the PendingTransition and surfaced to the new conversation as the `workflow_context` template variable when the consumer opens it.

func (*TransitionExecutor) Name

func (e *TransitionExecutor) Name() string

Name implements tools.Executor. Returns the mode name for registry routing.

func (*TransitionExecutor) Pending

func (e *TransitionExecutor) Pending() *PendingTransition

Pending returns the current pending transition, or nil if none.

func (*TransitionExecutor) RegisterForState

func (e *TransitionExecutor) RegisterForState(registry *tools.Registry, state *State)

RegisterForState registers the workflow__transition tool in the given registry for the specified state, with Mode set for executor routing.

When called with a terminal state (Terminal: true OR no on_event), the previous descriptor — if any — is removed from the registry instead of re-registered. Without this, the LLM would still see workflow__transition in the tool list after entering a terminal state and could call it with the previous state's now-stale events. Externally orchestrated states skip both register and unregister: the caller owns the descriptor's lifecycle.

func (*TransitionExecutor) SetOnCommit

func (e *TransitionExecutor) SetOnCommit(fn func(*TransitionResult))

SetOnCommit registers a callback fired after every successful commit. Pass nil to clear.

Callbacks run while the executor's internal lock is held; they must not re-enter the executor's public methods (Execute / CommitPending / etc.).

func (*TransitionExecutor) SetOnCommitError

func (e *TransitionExecutor) SetOnCommitError(fn func(event string, err error))

SetOnCommitError registers a callback fired when ProcessEvent fails during CommitPending. Consumers wire their workflow observability error emit (e.g., workflow.max_visits_exceeded, workflow.budget_exhausted) through this hook so deferred-commit failures are observable. Pass nil to clear.

Same locking contract as SetOnCommit: the callback runs while the executor's internal lock is held; do not re-enter the executor.

func (*TransitionExecutor) StateMachine

func (e *TransitionExecutor) StateMachine() *StateMachine

StateMachine returns the underlying state machine for metadata access.

type TransitionResult

type TransitionResult struct {
	From           string           `json:"from"`
	To             string           `json:"to"`
	Event          string           `json:"event"`
	Redirected     bool             `json:"redirected,omitempty"`
	RedirectReason string           `json:"redirect_reason,omitempty"`
	OriginalTarget string           `json:"original_target,omitempty"`
	HostExtras     tools.HostExtras `json:"host_extras,omitempty"`
}

TransitionResult is returned by ProcessEvent to communicate what happened. Redirects (e.g., max_visits exceeded → on_max_visits) are successful transitions, not errors.

type ValidationResult

type ValidationResult struct {
	Errors   []string // Blocking: invalid references, missing fields
	Warnings []string // Non-blocking: PascalCase violations, circular refs
}

ValidationResult holds errors and warnings from workflow validation.

func Validate

func Validate(spec *Spec, promptKeys []string) *ValidationResult

Validate checks a Spec against the available prompt keys. It implements all 10 validation rules from RFC 0005.

func (*ValidationResult) HasErrors

func (r *ValidationResult) HasErrors() bool

HasErrors returns true if there are blocking validation errors.

Jump to

Keyboard shortcuts

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