goal

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 23, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

Documentation

Overview

Package goal is Chatwright's goal/task/budget contract for goal-driven AI testing: the campaign's product-level intent (Goal), its trackable units of work (Task) with dependencies and prose success criteria, the limits that bound an autonomous run (Budgets), and the guarded state machine that tracks progress against them (CampaignState).

This package is a pure contract: no AI, no emulator, no I/O. It has no opinion on how a task is attempted — only on what a valid Goal looks like and which state transitions a campaign may legally make. The observe-plan-act-validate loop that drives an AI actor through a CampaignState (package actor) is a later slice.

Typical use:

g := goal.Goal{
	ID:    "listus-shopping-list",
	Title: "Exercise the shopping-list lifecycle",
	Tasks: []goal.Task{
		{ID: "onboarding", SuccessCriteria: "user completes language selection"},
		{ID: "add-items", DependsOn: []string{"onboarding"}, SuccessCriteria: "several items visible in the list"},
	},
	Budgets: goal.Budgets{MaxSteps: 80, MaxDuration: 10 * time.Minute, MaxRepeatedFailures: 3},
}
campaign, err := goal.NewCampaignState(g, time.Now)
// campaign.Activate("onboarding") ... campaign.Complete("onboarding") ...

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrEmptyTaskID means a Task's ID is empty or whitespace-only.
	ErrEmptyTaskID = errors.New("goal: task id is empty")
	// ErrDuplicateTaskID means two Tasks in the same Goal share an ID.
	ErrDuplicateTaskID = errors.New("goal: duplicate task id")
	// ErrUnknownDependency means a Task.DependsOn entry does not name a
	// Task ID present in the same Goal.
	ErrUnknownDependency = errors.New("goal: unknown dependency")
	// ErrDependencyCycle means the Task dependency graph contains a cycle.
	ErrDependencyCycle = errors.New("goal: dependency cycle")
	// ErrNegativeBudget means a Budgets field that must be zero (unlimited)
	// or positive was set negative.
	ErrNegativeBudget = errors.New("goal: budget must not be negative")
	// ErrNonPositiveCostBudget means Budgets.MaxCost was set to zero or a
	// negative value; leave it nil to mean "not budgeted".
	ErrNonPositiveCostBudget = errors.New("goal: max cost budget must be positive when set")
)

Goal.Validate errors.

View Source
var (
	// ErrNilClock means NewCampaignState was called without a clock
	// function.
	ErrNilClock = errors.New("goal: clock function is nil")
	// ErrUnknownTask means a task id does not belong to the campaign's Goal.
	ErrUnknownTask = errors.New("goal: unknown task id")
	// ErrTaskNotEligible means Activate was called on a Pending task whose
	// DependsOn tasks are not all Completed.
	ErrTaskNotEligible = errors.New("goal: task is not eligible (unmet dependencies)")
	// ErrTaskNotActivatable means Activate was called on a task that was
	// not Pending — including an already-Active or already-terminal task.
	ErrTaskNotActivatable = errors.New("goal: task is not activatable")
	// ErrTaskNotActive means Complete, Fail, Block or Skip was called on a
	// task that was not currently Active.
	ErrTaskNotActive = errors.New("goal: task is not active")
	// ErrCampaignStopped means a mutating method was called after the
	// campaign had already stopped.
	ErrCampaignStopped = errors.New("goal: campaign has already stopped")
	// ErrNegativeCost means RecordCost was called with a negative amount.
	ErrNegativeCost = errors.New("goal: cost amount must not be negative")
)

CampaignState errors.

Functions

This section is empty.

Types

type Budgets

type Budgets struct {
	// MaxSteps caps the number of steps CampaignState.RecordStep counts.
	// Zero means unlimited.
	MaxSteps int `json:"maxSteps"`

	// MaxDuration caps wall-clock time elapsed since the campaign started,
	// measured by the CampaignState's injected clock. Zero means unlimited.
	MaxDuration time.Duration `json:"maxDurationNanoseconds"`

	// MaxRepeatedFailures caps how many times CampaignState.RecordFailure
	// may be called for a single task before the campaign stops. Zero means
	// unlimited.
	MaxRepeatedFailures int `json:"maxRepeatedFailures"`

	// MaxCost optionally caps spend against the campaign (tokens, currency
	// or another caller-defined unit — whatever unit the caller accrues via
	// CampaignState.RecordCost). Nil means cost is not budgeted.
	MaxCost *float64 `json:"maxCost"`
}

Budgets bounds one campaign run. Every numeric field's zero value means "no limit"; a negative value is invalid. MaxCost is the one genuinely optional field: nil means cost is not budgeted at all.

type CampaignSnapshot

type CampaignSnapshot struct {
	GoalID     string
	Statuses   map[string]TaskStatus
	Steps      int
	Cost       float64
	Elapsed    time.Duration
	Failures   map[string]int
	Stopped    bool
	StopReason StopReason
}

CampaignSnapshot is a detached, point-in-time copy of a CampaignState's progress: safe to retain, log or compare after the originating CampaignState has moved on.

type CampaignState

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

CampaignState is the guarded runtime state machine for one Goal: task statuses, elapsed steps and duration, per-task failure counts, and the deterministic StopReason that ends the campaign. It performs no AI, networking or platform I/O — callers report progress in with RecordStep, RecordFailure and the task transition methods, and read state back out.

All methods are safe for concurrent use. Time comes from an injected clock (see NewCampaignState) rather than time.Now, so tests are deterministic and reproducible.

func NewCampaignState

func NewCampaignState(g Goal, now func() time.Time) (*CampaignState, error)

NewCampaignState validates g (see Goal.Validate) and starts a new campaign with every task Pending. now supplies the current time for step duration and budget checks; pass a fixed or fake clock in tests so duration-budget behaviour is deterministic. now must not be nil.

func (*CampaignState) Abort

func (c *CampaignState) Abort() error

Abort stops the campaign with StopError, after an unrecoverable runtime failure the caller cannot attribute to a budget or an explicit cancellation. It errors if the campaign has already stopped.

func (*CampaignState) Activate

func (c *CampaignState) Activate(id string) error

Activate transitions a Pending, dependency-satisfied task to Active. It errors if:

  • the campaign has already stopped (ErrCampaignStopped);
  • the task id is unknown (ErrUnknownTask);
  • the task is Pending but its dependencies are not all Completed (ErrTaskNotEligible);
  • the task is not Pending at all — including an already-Active or a terminal task (ErrTaskNotActivatable).

func (*CampaignState) Block

func (c *CampaignState) Block(id string) error

Block transitions an Active task to Blocked.

func (*CampaignState) Cancel

func (c *CampaignState) Cancel() error

Cancel stops the campaign with StopCancelled — an external decision to end the run early, distinct from any budget being exhausted. It errors if the campaign has already stopped.

func (*CampaignState) Complete

func (c *CampaignState) Complete(id string) error

Complete transitions an Active task to Completed.

func (*CampaignState) Cost

func (c *CampaignState) Cost() float64

Cost returns the total cost RecordCost has accrued so far.

func (*CampaignState) Eligible

func (c *CampaignState) Eligible(id string) (bool, error)

Eligible reports whether the task with the given id is currently Pending and every task it DependsOn is Completed — the guard Activate enforces. It errors if the task id is unknown.

func (*CampaignState) Fail

func (c *CampaignState) Fail(id string) error

Fail transitions an Active task to Failed.

func (*CampaignState) FailureCount

func (c *CampaignState) FailureCount(id string) int

FailureCount returns how many failures RecordFailure has counted against the given task id so far (zero for an unknown id, rather than an error — callers that need to distinguish "no failures" from "unknown task" should check TaskStatus first).

func (*CampaignState) RecordCost

func (c *CampaignState) RecordCost(amount float64) error

RecordCost accrues amount against the campaign's cost budget (tokens, currency or whatever unit Budgets.MaxCost was expressed in). Costs accumulate across calls for the life of the campaign, not per task — call it once per spend you want counted (e.g. once per actor Provider.Propose call, with that call's Usage.Cost). It stops the campaign deterministically with StopBudgetCost the moment accrued cost reaches a set, positive Budgets.MaxCost, and errors if the campaign has already stopped or amount is negative.

func (*CampaignState) RecordFailure

func (c *CampaignState) RecordFailure(id string) error

RecordFailure attributes one failed attempt to the task with the given id. Repeated failures against the same task accumulate across calls — the task does not need to be re-activated between them — and once the count reaches a positive Budgets.MaxRepeatedFailures the campaign stops with StopRepeatedFailure. It errors if the campaign has already stopped or the task id is unknown.

func (*CampaignState) RecordStep

func (c *CampaignState) RecordStep() error

RecordStep counts one action/step against the campaign's step and duration budgets. Call it once per recorded actor action or scenario step — never derive a step count from time.Now internally; this method's only notion of "now" is the injected clock. It stops the campaign deterministically (StopBudgetSteps, then StopBudgetDuration) the moment a positive budget is reached, and errors if the campaign has already stopped.

func (*CampaignState) Skip

func (c *CampaignState) Skip(id string) error

Skip transitions an Active task to Skipped.

func (*CampaignState) Snapshot

func (c *CampaignState) Snapshot() CampaignSnapshot

Snapshot returns a detached copy of the campaign's current progress.

func (*CampaignState) Steps

func (c *CampaignState) Steps() int

Steps returns the number of steps RecordStep has counted so far.

func (*CampaignState) StopReason

func (c *CampaignState) StopReason() (StopReason, bool)

StopReason returns the reason the campaign stopped and true, or ("", false) while the campaign is still running.

func (*CampaignState) Stopped

func (c *CampaignState) Stopped() bool

Stopped reports whether the campaign has stopped accepting mutations.

func (*CampaignState) TaskStatus

func (c *CampaignState) TaskStatus(id string) (TaskStatus, error)

TaskStatus returns the current status of the task with the given id, or an error wrapping ErrUnknownTask if no such task exists.

type Goal

type Goal struct {
	ID          string   `json:"id"`
	Title       string   `json:"title"`
	Description string   `json:"description"`
	Tasks       []Task   `json:"tasks"`
	Constraints []string `json:"constraints"`
	Budgets     Budgets  `json:"budgets"`
}

Goal is one campaign's product-level intent: a natural-language outcome broken into Tasks, plus the Constraints and Budgets that bound how an actor may pursue it. A Goal describes intent, never platform mechanics — see the goal-and-task-contract feature's goal-does-not-leak-platform-mechanics acceptance criterion.

func (Goal) Validate

func (g Goal) Validate() error

Validate checks that g is well-formed:

  • every Task has a non-empty, unique ID;
  • every Task.DependsOn entry resolves to another Task ID in g;
  • the dependency graph is acyclic;
  • Budgets are non-negative, and MaxCost, if set, is positive.

NewCampaignState calls Validate during construction, so a CampaignState can never exist over an invalid Goal. Callers may also call it directly — for example to validate an authored goal before scheduling a campaign.

type StopReason

type StopReason string

StopReason is why a CampaignState stopped accepting further mutations. Every stop names exactly one reason, chosen deterministically by the condition that caused it.

const (
	// StopGoalComplete means every task reached a terminal status — there
	// is no more eligible work left to activate. It does not by itself mean
	// every task succeeded; read individual TaskStatus values for that.
	StopGoalComplete StopReason = "goal-complete"
	// StopBudgetSteps means Budgets.MaxSteps was reached.
	StopBudgetSteps StopReason = "budget-steps"
	// StopBudgetDuration means Budgets.MaxDuration elapsed.
	StopBudgetDuration StopReason = "budget-duration"
	// StopRepeatedFailure means Budgets.MaxRepeatedFailures was reached for
	// one task.
	StopRepeatedFailure StopReason = "repeated-failure"
	// StopBudgetCost means Budgets.MaxCost was reached via RecordCost.
	StopBudgetCost StopReason = "budget-cost"
	// StopCancelled means CampaignState.Cancel was called.
	StopCancelled StopReason = "cancelled"
	// StopError means CampaignState.Abort was called after an unrecoverable
	// runtime failure.
	StopError StopReason = "error"
)

Stop reasons. See CampaignState.StopReason.

type Task

type Task struct {
	ID              string   `json:"id"`
	Title           string   `json:"title"`
	DependsOn       []string `json:"dependsOn"`
	SuccessCriteria string   `json:"successCriteria"`
	Milestones      []string `json:"milestones"`
}

Task is one trackable unit of work inside a Goal. Success is judged by prose SuccessCriteria — the contract never prescribes the bot commands or callback data used to satisfy it. DependsOn names other Task IDs in the same Goal that must be Completed before this task becomes eligible for CampaignState.Activate. Milestones names checkpoints this task's completion may reach; the reporting layer, not this package, interprets them.

type TaskStatus

type TaskStatus string

TaskStatus is a task's position in its guarded lifecycle:

pending -> active -> completed | failed | blocked | skipped

Only CampaignState mutates a task's status, and only along that guard: a task must be activated before it can reach any terminal status, and every terminal status is final.

const (
	TaskPending   TaskStatus = "pending"
	TaskActive    TaskStatus = "active"
	TaskCompleted TaskStatus = "completed"
	TaskFailed    TaskStatus = "failed"
	TaskBlocked   TaskStatus = "blocked"
	TaskSkipped   TaskStatus = "skipped"
)

Task lifecycle statuses. See TaskStatus.

func (TaskStatus) Terminal

func (s TaskStatus) Terminal() bool

Terminal reports whether s is one of the lifecycle's terminal outcomes. Once a task reaches a terminal status no further transition is possible.

Jump to

Keyboard shortcuts

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