Help us shape the future of Go! Take the 2026 Go Developer Survey

harness

package
v0.15.1 Latest Latest
Warning

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

Go to latest
Published: Nov 1, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package harness runs a workflow of minimal markdown skills: a router picks the skills for a task, each skill is one narrow prompt with a success check, and a loop runs them in order, feeding each stage's output to the next and stopping at the first stage that fails its check after retries.

The three layers live in their own files: router.go, skill.go and loop.go; workflow.go parses the workflow file that ties them together. Each stage is an ordinary single tinyagent run.

Index

Constants

View Source
const (
	// MaxBehaviorCases bounds the cases in one behavior file.
	MaxBehaviorCases = 20
	// MaxBehaviorFileBytes caps a behavior file.
	MaxBehaviorFileBytes = 64 << 10
	// DefaultBehaviorTimeout bounds one case's command, in seconds; a case
	// may ask for more, up to the stage's check_timeout.
	DefaultBehaviorTimeout = 60
	// MaxBehaviorOutput caps each stream of observed output kept as evidence.
	MaxBehaviorOutput = 4 << 10
)

Behavior-check bounds.

View Source
const (
	StatusPassed      = "Passed"
	StatusFailed      = "Failed"
	StatusInterrupted = "Interrupted"
	// StatusBlocked means a stage ran out of retries because its success
	// check kept failing on its merits (a check command exited non-zero, the
	// model reported SUCCESS CHECK: FAIL, behavior cases failed): the task
	// as specified can't be satisfied without a human. StatusFailed is kept
	// for transient or infrastructure failures.
	StatusBlocked = "Blocked"
)

Workflow statuses.

View Source
const (
	MaxCheckOutput = 8 << 10
	MaxFeedback    = 4 << 10
)

MaxCheckOutput caps a check command's recorded output, and MaxFeedback the failure reason handed to a retry.

View Source
const (
	// DefaultRetries is how many times a failed stage is retried.
	DefaultRetries = 1
	// MaxRetries bounds retries per stage.
	MaxRetries = 5
	// DefaultStepLimit is each stage run's --step-limit unless overridden.
	DefaultStepLimit = 50
	// DefaultCheckTimeout bounds a check command, in seconds.
	DefaultCheckTimeout = 300
	// MaxStages bounds a workflow's length.
	MaxStages = 20
	// MaxWorkflowBytes caps the workflow file.
	MaxWorkflowBytes = 256 << 10
)

Defaults and bounds.

View Source
const ExitBlocked = 5

ExitBlocked is the process exit code for StatusBlocked.

View Source
const GateSpec = "spec"

GateSpec is the only stage gate: it checks a plan-tasks stage's output before the next stage (usually generate-code) starts.

View Source
const MaxSkillBytes = 256 << 10

MaxSkillBytes caps the size of one skill file.

View Source
const MinRouteScore = 2

MinRouteScore is how many distinct content words a task must share with an intent for the router to pick it.

Variables

This section is empty.

Functions

func LoadSkills

func LoadSkills(dir string) (map[string]*Skill, error)

LoadSkills reads every skill in dir: <name>.md files and <name>/SKILL.md folders. A name defined both ways, an invalid name or an unparseable file is an error.

func Prompt

func Prompt(idx, n int, st Stage, task, prev, prevName, cwd, feedback string) string

Prompt builds stage idx's task: the skill's instructions, its inputs and, on a retry, why the previous attempt failed.

func Unsatisfiable added in v0.12.0

func Unsatisfiable(c Check) bool

Unsatisfiable reports whether a failed check failed on its merits, so a stage that keeps failing it is blocked rather than failed: a check command that ran and exited non-zero, a model verdict of SUCCESS CHECK: FAIL, or behavior cases that ran and failed. A run error or limit hit, a missing verdict line, a check command that timed out or couldn't start, and an invalid behavior file are transient.

Types

type Attempt

type Attempt struct {
	Stage      int     `json:"stage"` // 1-based
	Skill      string  `json:"skill"`
	Attempt    int     `json:"attempt"` // 1-based
	Input      string  `json:"input"`
	Output     string  `json:"output"`
	ExitStatus string  `json:"exit_status"`
	Check      Check   `json:"check"`
	Steps      int     `json:"steps"`
	CostUSD    float64 `json:"cost_usd"`
	Trajectory string  `json:"trajectory,omitempty"`
	Log        string  `json:"log,omitempty"`
	DurationMS int64   `json:"duration_ms"`
	// Unverified is set when the stage's run finished with changes that
	// verify-on-stop could not see verified. It is reported only; it does
	// not change whether the stage passed.
	Unverified bool `json:"unverified,omitempty"`
}

Attempt is one run of one stage.

type BehaviorCase added in v0.12.0

type BehaviorCase struct {
	Name      string         `yaml:"name"`
	Criterion string         `yaml:"criterion"`
	Run       []string       `yaml:"run"`
	Stdin     string         `yaml:"stdin"`
	Timeout   int            `yaml:"timeout"`
	Expect    BehaviorExpect `yaml:"expect"`
}

BehaviorCase runs one command and checks what it actually printed.

type BehaviorExpect added in v0.12.0

type BehaviorExpect struct {
	ExitCode          *int     `yaml:"exit_code"`
	StdoutContains    []string `yaml:"stdout_contains"`
	StdoutNotContains []string `yaml:"stdout_not_contains"`
	StdoutMatches     []string `yaml:"stdout_matches"`
	StderrContains    []string `yaml:"stderr_contains"`
}

BehaviorExpect is the observable outcome a case requires. Exit code defaults to 0.

type BehaviorFile added in v0.12.0

type BehaviorFile struct {
	Version int            `yaml:"version"`
	Cases   []BehaviorCase `yaml:"cases"`
}

BehaviorFile is what a verify-behavior stage writes: the task's behavioral acceptance criteria as commands to run and the output each must produce. No cases means the task has no behavioral criteria.

type BehaviorResult added in v0.12.0

type BehaviorResult struct {
	Name      string   `json:"name"`
	Criterion string   `json:"criterion,omitempty"`
	Argv      []string `json:"argv"`
	Passed    bool     `json:"passed"`
	ExitCode  int      `json:"exit_code"`
	TimedOut  bool     `json:"timed_out,omitempty"`
	Stdout    string   `json:"stdout"`
	Stderr    string   `json:"stderr"`
	Failures  []string `json:"failures,omitempty"`
}

BehaviorResult is one case's evidence: what ran, what it printed and which expectations failed.

type Check

type Check struct {
	Method   string   `json:"method"` // "command" or "model"
	Passed   bool     `json:"passed"`
	Reason   string   `json:"reason,omitempty"`
	Argv     []string `json:"argv,omitempty"`
	ExitCode int      `json:"exit_code,omitempty"`
	TimedOut bool     `json:"timed_out,omitempty"`
	Output   string   `json:"output,omitempty"`
	// Skipped is set when a behavior stage found no behavioral criteria.
	Skipped bool `json:"skipped,omitempty"`
	// NoVerdict is set when a model-checked stage's output had no
	// SUCCESS CHECK line.
	NoVerdict bool `json:"no_verdict,omitempty"`
	// Evidence is each behavior case's observed output.
	Evidence []BehaviorResult `json:"evidence,omitempty"`
}

Check is the outcome of a stage's success check.

type GateDecision added in v0.12.0

type GateDecision struct {
	Stage   int      `json:"stage"` // 1-based
	Skill   string   `json:"skill"`
	Gate    string   `json:"gate"`
	Attempt int      `json:"attempt"` // 1 = first plan, 2 = the re-plan
	Passed  bool     `json:"passed"`
	Reason  string   `json:"reason,omitempty"`
	Missing []string `json:"missing,omitempty"`
}

GateDecision is one gate decision, recorded in Result.Gates and emitted as a stage.gate event.

type GateResult added in v0.12.0

type GateResult struct {
	Passed bool `json:"passed"`
	// Tasks is how many task blocks the plan had.
	Tasks   int      `json:"tasks"`
	Reason  string   `json:"reason,omitempty"`
	Missing []string `json:"missing,omitempty"`
}

GateResult is the outcome of SpecGate.

func SpecGate added in v0.12.0

func SpecGate(plan, cwd string) GateResult

SpecGate checks that a plan has at least one task and that every task has at least one acceptance-criteria bullet and a "Verify:" line with a runnable command in backticks. "Runnable" means the command's program (its first word after any VAR=value prefixes) is found on PATH, or, if it contains a slash, exists relative to cwd. The command is not run: the code it verifies doesn't exist yet.

type Options

type Options struct {
	Route Route
	Task  string
	Cwd   string // absolute working directory shared by every stage
	// ExtraArgs are forwarded to every stage run before the harness's own.
	ExtraArgs []string
	// Dir receives each attempt's trajectory and log.
	Dir      string
	RunAgent RunAgent
	// Emit, if set, receives workflow.* and stage.* events.
	Emit func(kind string, fields map[string]any)
	// Progress, if set, receives one line per stage attempt.
	Progress io.Writer
}

Options configure Run.

type Pipeline

type Pipeline struct {
	Name   string  `json:"name"`
	Intent string  `json:"intent,omitempty"`
	Stages []Stage `json:"stages"`
}

Pipeline is one named, resolved workflow.

type Result

type Result struct {
	SchemaVersion int       `json:"schema_version"`
	Kind          string    `json:"kind"` // "workflow"
	Workflow      string    `json:"workflow"`
	Route         Route     `json:"route"`
	Task          string    `json:"task"`
	Cwd           string    `json:"cwd"`
	Status        string    `json:"status"`
	FailedStage   string    `json:"failed_stage,omitempty"`
	Reason        string    `json:"reason,omitempty"`
	Output        string    `json:"output,omitempty"` // the last stage's output
	Stages        []Stage   `json:"stages"`
	Attempts      []Attempt `json:"attempts"`
	// Gates are the stage gate decisions, in order.
	Gates      []GateDecision `json:"gates,omitempty"`
	CostUSD    float64        `json:"cost_usd"`
	DurationMS int64          `json:"duration_ms"`
}

Result is a whole workflow run; it is what --output writes.

func Run

func Run(ctx context.Context, o Options) *Result

Run executes the routed stages in order with no pause between them. Stage k gets the task and stage k-1's output; a stage that fails its success check is retried up to its limit with the failure reason, and when retries run out the workflow stops without running later stages.

func (*Result) ExitCode

func (r *Result) ExitCode() int

ExitCode maps a result to the process exit code.

type Route

type Route struct {
	Pipeline *Pipeline `json:"-"`
	Name     string    `json:"name"`
	Kind     string    `json:"kind"` // "workflow" or "skill"
	// How is "named" (--workflow-name), "intent" (matched the task) or
	// "default" (the file's default).
	How   string `json:"how"`
	Score int    `json:"score,omitempty"`
}

Route is the router's decision: the pipeline to run and why.

type RouteError

type RouteError struct{ Msg string }

RouteError is returned when the router can't choose; no stage has run.

func (*RouteError) Error

func (e *RouteError) Error() string

type RunAgent

type RunAgent func(ctx context.Context, args []string, log io.Writer) error

RunAgent runs one child tinyagent with args, writing its output to log.

func ExecAgent

func ExecAgent(exe string) RunAgent

ExecAgent returns a RunAgent that runs exe as a child process; on cancellation the child gets an interrupt so it can save its trajectory.

type Settings

type Settings struct {
	Model        string   `yaml:"model" json:"model,omitempty"`
	Profile      string   `yaml:"profile" json:"profile,omitempty"`
	StepLimit    *int     `yaml:"step_limit" json:"step_limit,omitempty"`
	CostLimit    *float64 `yaml:"cost_limit" json:"cost_limit,omitempty"`
	TokenLimit   *int     `yaml:"token_limit" json:"token_limit,omitempty"`
	TimeLimit    *int     `yaml:"time_limit" json:"time_limit,omitempty"`
	Retries      *int     `yaml:"retries" json:"retries,omitempty"`
	CheckTimeout *int     `yaml:"check_timeout" json:"check_timeout,omitempty"`
}

Settings are the overridable per-stage settings; zero values inherit.

type Skill

type Skill struct {
	Name         string   `json:"name"`
	Title        string   `json:"title,omitempty"`
	Intent       string   `json:"intent"`
	Inputs       []string `json:"inputs"`
	SuccessCheck string   `json:"success_check,omitempty"`
	// Body is the file's Markdown with any YAML frontmatter removed; it
	// is the stage's instructions.
	Body string `json:"-"`
	Path string `json:"path"`
}

Skill is one parsed skill file: a heading, an intent line, declared inputs and a one-line success check. Everything else (model, limits, retries, check commands) comes from the workflow file's defaults.

func LoadSkillFile added in v0.12.0

func LoadSkillFile(path string) (*Skill, error)

LoadSkillFile reads and parses one skill file with the same rules LoadSkills applies: its name is the file stem, or the folder name for a <name>/SKILL.md, and must be a valid skill name.

func ParseSkill

func ParseSkill(name, path string, data []byte) (*Skill, error)

ParseSkill parses a skill file's content. name is the skill's id (its file stem, or its folder name for a SKILL.md). The success check may be empty; the workflow decides whether a stage needs one.

type Stage

type Stage struct {
	Skill *Skill `json:"skill"`
	// Check is a command whose exit status decides the stage's success
	// check; when empty the model's own verdict on the skill's success
	// check line decides.
	Check []string `json:"check,omitempty"`
	// Behavior, when set, is a path (relative to --cwd) where the stage
	// writes behavior cases; the harness runs them and decides the stage on
	// their observed output. See behavior.go.
	Behavior string `json:"behavior,omitempty"`
	// Gate, when "spec", checks the stage's output with SpecGate after it
	// passes; see gate.go.
	Gate         string            `json:"gate,omitempty"`
	CheckTimeout int               `json:"check_timeout"`
	Retries      int               `json:"retries"`
	Model        string            `json:"model,omitempty"`
	Profile      string            `json:"profile,omitempty"`
	StepLimit    int               `json:"step_limit"`
	CostLimit    float64           `json:"cost_limit,omitempty"`
	TokenLimit   int               `json:"token_limit,omitempty"`
	TimeLimit    int               `json:"time_limit,omitempty"`
	Inputs       map[string]string `json:"inputs,omitempty"`
}

Stage is one resolved workflow step.

type Workflow

type Workflow struct {
	Path      string
	SkillsDir string
	Default   string
	Skills    map[string]*Skill
	Pipelines map[string]*Pipeline
	// contains filtered or unexported fields
}

Workflow is a parsed, validated workflow file.

func Load

func Load(path string) (*Workflow, error)

Load parses and validates the workflow file at path and every skill in its skills directory. Any problem is returned before a model is called.

func (*Workflow) Route

func (w *Workflow) Route(task, name string) (Route, error)

Route picks what to run for task. An explicit name (a workflow or a skill) wins; otherwise the task is matched against every workflow's intent and every skill's intent line, and the single best match with at least MinRouteScore shared words is chosen. With no match (or a tie) the file's default is used if it has one; otherwise Route returns a RouteError rather than guessing.

Jump to

Keyboard shortcuts

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