Documentation
¶
Overview ¶
Package agentstep is satelle's isolated-agent dispatch engine — the quality-management spine that runs a fresh-context agent over a workflow step and folds its result back in. It dispatches three kinds of isolated agent, all briefed and run through one seam (invoke.go): a GATE REVIEWER judging a status transition and returning a verdict, a NAMED EXECUTOR performing a step's work, and the STEP SUMMARISER narrating a transition. A reviewer is just one agent kind, so the engine (type Engine) is not named for it.
Reviewer path: the active workflow names a reviewer_skill per edge; the skill's markdown body rides as the agent's appended system prompt; the work item + requested transition go in on stdin; the agent prints one JSON object {decision, notes, reasoning}, parsed strictly into an accept/reject. Accept lets the caller enact; reject blocks and pushes the notes back to the executor. LLM gate and named-dispatch runs share Invoke (invoke.go) — one path that calls agentcli.Runner.Run (sty_ba860c8a).
The edge is gated only when the workflow names a reviewer_skill AND that skill's rubric is installed in the substrate. A named-but-absent rubric (e.g. the canonical default referencing a skill not yet embedded) is treated as advisory, so gating switches on exactly when the rubrics ship — the gateless baseline keeps working until then.
Index ¶
- Constants
- func GoverningWorkflow(workflows []docindex.Doc, item workitem.Item) (docindex.Doc, bool)
- func IsRateLimitOrUnavailable(err error, stdout []byte) bool
- func OrderedWorkflows(workflows []docindex.Doc, category string) []docindex.Doc
- func WorkflowConsistency(workflows []docindex.Doc, resolve func(skill string) bool) []string
- func WorkflowSkillProblems(w docindex.Doc, resolve func(skill string) bool) []string
- type Activity
- type ChildState
- type DocGetter
- type DocState
- type Engine
- func (g *Engine) DispatchExecutor(ctx context.Context, item workitem.Item, toStatus string) (verb.DispatchResult, error)
- func (g *Engine) Gate(ctx context.Context, item workitem.Item, toStatus string) (verb.GateDecision, error)
- func (g *Engine) Invoke(ctx context.Context, req InvokeRequest) InvokeResult
- func (g *Engine) MandatorySummary(ctx context.Context, item workitem.Item) bool
- func (g *Engine) Retrospect(ctx context.Context, item workitem.Item) (verb.DispatchResult, error)
- func (g *Engine) ReviewCreate(ctx context.Context, draft verb.CreateDraft) (verb.GateDecision, error)
- func (g *Engine) SetActivity(fn func(itemID string, a Activity))
- func (g *Engine) SetArtifactAttacher(...)
- func (g *Engine) SetChildrenResolver(fn func(ctx context.Context, parentID string) []ChildState)
- func (g *Engine) SetConstitution(body string)
- func (g *Engine) SetDocsResolver(fn func(ctx context.Context, itemID string) []DocState)
- func (g *Engine) SetInjectPrinciples(on bool)
- func (g *Engine) SetLogDir(dir string, cfg logfile.Config)
- func (g *Engine) SetNamedAgents(fn func(name string) (config.AgentBinding, bool))
- func (g *Engine) SetProgress(fn func(msg string))
- func (g *Engine) SetReviewerBinding(b config.AgentBinding)
- func (g *Engine) SetReviewerEnv(env map[string]string)
- func (g *Engine) SetReviewerModel(model string)
- func (g *Engine) SetReviewerTools(tools string)
- func (g *Engine) SetRunner(r agentcli.Runner)
- func (g *Engine) SetSecondaryResolver(...)
- func (g *Engine) SetTelemetry(fn TelemetryFunc)
- func (g *Engine) Summarise(ctx context.Context, item workitem.Item, from, to string) (verb.SummaryResult, error)
- func (g *Engine) WorkflowNameFor(ctx context.Context, category string) string
- func (g *Engine) WorkflowStates(ctx context.Context, name string) ([]string, bool)
- type Expect
- type InvokeRequest
- type InvokeResult
- type TelemetryFunc
Constants ¶
const WorkflowStampPrefix = wfgovern.WorkflowStampPrefix
WorkflowStampPrefix re-exports the stamp tag prefix (owned by wfgovern).
Variables ¶
This section is empty.
Functions ¶
func GoverningWorkflow ¶ added in v0.0.134
GoverningWorkflow re-exports wfgovern.GoverningWorkflow.
func IsRateLimitOrUnavailable ¶ added in v0.0.294
IsRateLimitOrUnavailable reports whether an isolated-agent failure should trigger secondary failover (sty_5bf61f89): rate limits, capacity, quota, and hard unavailability. Other errors stay fail-loud without fallback.
func OrderedWorkflows ¶
OrderedWorkflows re-exports wfgovern.OrderedWorkflows for callers that still import agentstep (web, CLI). Prefer wfgovern directly for new code.
func WorkflowConsistency ¶
WorkflowConsistency reports cross-workflow inconsistencies an agent should advise the user about (sty_4c0c7246): (1) OVER-CONFIGURATION — two or more REPO workflows claim the same category (or the wildcard) at the same precedence, so the tiebreak is arbitrary; (2) a workflow that REFERENCES a skill (an edge gate or a node @skill: prompt) which does not resolve in the substrate. Empty when the workflow set is consistent. resolve may be nil to skip the skill check.
func WorkflowSkillProblems ¶ added in v0.0.374
WorkflowSkillProblems reports, for ONE workflow, every skill it names that does not resolve in the substrate — edge gates, node @skill: prompts, and declared lifecycle hooks — plus any hook declaration defect.
This is the half of WorkflowConsistency that is meaningful per document. The ambiguity check is deliberately NOT here: it compares repo workflows against each other, so it is whole-set by nature and firing it on a single doc would misreport (sty_d59ec6a9 AC3).
Callers: WorkflowConsistency (whole set, where these are FAILs), and the authoring paths `workflow validate <name>` / `workflow create` (where they are WARNs — a repo mid-authoring writes the workflow before it writes the gate skills, so blocking there would make the ordinary sequence impossible).
Types ¶
type Activity ¶ added in v0.0.408
Activity is structured transition progress: which gate/phase is running, its 1-based index, and the size of the set (sty_598a8e1b).
type ChildState ¶
ChildState is one child story's id and status, injected into a parent/epic close payload.
type DocGetter ¶
type DocGetter interface {
Get(ctx context.Context, kind, name string) (docindex.Doc, error)
List(ctx context.Context, kind string) ([]docindex.Doc, error)
}
DocGetter is the read surface the engine needs over the authored-doc index (satisfied by *docindex.Store) — listing workflows (to resolve the one active for an item's category) and getting the reviewer skills / the baseline.
type DocState ¶ added in v0.0.274
type DocState struct {
Name string `json:"name"`
Type string `json:"type"`
Body string `json:"body,omitempty"`
Truncated bool `json:"truncated,omitempty"`
Binary bool `json:"binary,omitempty"`
ContentType string `json:"content_type,omitempty"`
Size int64 `json:"size,omitempty"`
SHA256 string `json:"sha256,omitempty"`
}
DocState is one story attachment injected into the transition payload. Binary attachments carry Binary=true with empty Body and must never consume docsPayloadCeiling (sty_40e5a305); fillPayloadDocs skips them entirely.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine judges status transitions against the active workflow's reviewer skills. A skill is either an LLM reviewer (its body rides as an isolated agent's system prompt) or a functional check (its frontmatter names a deterministic `check:` command the gate runs — the command's exit code is the verdict).
func New ¶
New builds a Engine over the agent runner and doc index. model "" inherits the agent's default; the tool grant is read-only.
func (*Engine) DispatchExecutor ¶
func (g *Engine) DispatchExecutor(ctx context.Context, item workitem.Item, toStatus string) (verb.DispatchResult, error)
DispatchExecutor implements verb.ExecutorDispatcher: when the TARGET state of an accepted transition is allocated to a NAMED agent (agent=<name>, neither "executor" nor "reviewer"), the binding's harness performs the step synchronously — prompt assembled from the item (title, body, acceptance criteria on stdin) plus the node's @skill rubric, tools/model/principles from the binding, nothing hardcoded (sty_fd427546). A missing binding or a failed run is an ERROR — the caller refuses the transition (broken definition never silently falls back in-loop, consistent with sty_d0d6bb67). agent=executor, agent-less and reviewer states dispatch nothing; a named binding whose harness is explicitly "in-loop" also stays with the orchestrator.
Flat dispatch (sty_05a5e203): this is the ONLY dispatch entering a state can cause, and it happens because the SPINE allocates the step — not because the state fires an agent of its own. Entry dispatch (on_enter_agent) is retired: steps never call steps, so an advisor is consulted by the orchestrator at a moment it chooses, and the route names which advisor that is.
func (*Engine) Gate ¶
func (g *Engine) Gate(ctx context.Context, item workitem.Item, toStatus string) (verb.GateDecision, error)
Gate judges item's transition to toStatus against every reviewer governing the edge — the workflow-named reviewers (one, or an ordered list) followed by the always-on system reviewer layer. Each reviewer runs in order and ALL must accept; the first reject short-circuits and blocks the edge. It returns the per-reviewer verdicts in run order plus a top-level verdict mirroring the deciding reviewer (the first reject, or the last when all accept), so single-reviewer callers keep their contract. Gated=false (enact directly) when no reviewer governs the edge.
func (*Engine) Invoke ¶ added in v0.0.189
func (g *Engine) Invoke(ctx context.Context, req InvokeRequest) InvokeResult
Invoke is the ONLY path that calls agentcli.Runner.Run for LLM gate/dispatch steps (sty_ba860c8a). It resolves prompt assembly from the binding, runs the agent, and for ExpectVerdict applies the retry + verdict parse loop. On a classified rate-limit/unavailable failure, retries once on the binding's secondary (per-binding secondary= or [defaults] secondary) when configured (sty_5bf61f89).
func (*Engine) MandatorySummary ¶ added in v0.0.137
MandatorySummary reports whether item's active workflow declares a MANDATORY step-summary node — used to gate the done-time missing-summary surfacing (sty_a1151fb0). Implements verb.StepSummariser.
func (*Engine) Retrospect ¶ added in v0.0.133
Retrospect dispatches the retrospective agent over a finished story: it pulls the story + its plan/summary/ledger by id, then emits 1–3 improvement PROPOSALS as backlog stories (its Bash(satelle:*) grant). Invoked per-story by `satelle story retrospect` — kept opt-in rather than auto-on-done so its cost (visible via `satelle story cost`, sty_a699ad14) is measured before it is made always-on. Returns the dispatch result (with captured output + token/wall-time cost) so the verb layer can record an agent_invocation for the cost view.
func (*Engine) ReviewCreate ¶
func (g *Engine) ReviewCreate(ctx context.Context, draft verb.CreateDraft) (verb.GateDecision, error)
ReviewCreate judges a draft work item's required structure before it is persisted, DETERMINISTICALLY (internal/structure) — a clear goal and at least one numbered, testable acceptance criterion. No LLM, no agent CLI: the contract is code, so it is harness-independent and never flaky. Always Gated (the structure is the one thing satelle enforces on creation).
func (*Engine) SetActivity ¶ added in v0.0.408
SetActivity wires the structured progress sink (CLI stamps the lease row).
func (*Engine) SetArtifactAttacher ¶ added in v0.0.351
func (g *Engine) SetArtifactAttacher(fn func(context.Context, workitem.Item, string, string, string) (string, string, error))
SetArtifactAttacher wires the verb-owned typed document writer used by structured step output contracts.
func (*Engine) SetChildrenResolver ¶
func (g *Engine) SetChildrenResolver(fn func(ctx context.Context, parentID string) []ChildState)
SetChildrenResolver wires the resolver that lists a parent's child stories (id + status) so a container close gate judges the children-resolved rule from the payload satelle builds — not an on-disk story mirror. Nil-safe: an unwired resolver simply injects no children.
func (*Engine) SetConstitution ¶ added in v0.0.189
SetConstitution sets the project constitution body injected order-zero into isolated agent briefings whenever principles ≠ none (design §5.3).
func (*Engine) SetDocsResolver ¶ added in v0.0.274
SetDocsResolver wires the resolver that lists an item's attachments so every isolated agent (reviewer, named executor, retrospective) receives plan/step summaries in the transition payload — the Bash-less read channel (sty_58fa970e). Nil-safe: an unwired resolver injects no docs.
func (*Engine) SetInjectPrinciples ¶
SetInjectPrinciples sets whether the resident principles ride in an isolated reviewer's system prompt, from the agents layer's resolved `reviewer` binding (sty_46a40208). Defaults ON; a repo disables it with inject_principles = false. Also mutates reviewerBinding (deprecated alias + principles selector).
func (*Engine) SetLogDir ¶
SetLogDir points the reviewer's transient-failure log at dir (the repo's <data_dir>/logs) and bounds it per cfg. When set, each transient reviewer failure — the failing subprocess's own output, e.g. a rate-limit message — is appended to reviewer.log so cross-session API contention is reviewable (sty_d71b0791), rotated daily + by size (sty_a67e6e8c). An empty dir disables logging.
func (*Engine) SetNamedAgents ¶
func (g *Engine) SetNamedAgents(fn func(name string) (config.AgentBinding, bool))
SetNamedAgents wires the resolver for NAMED agent bindings from the agents layer (.satelle/workflows/agents.toml [<name>] sections) — the WHO of a workflow node's agent=<name> allocation (sty_fd427546). Nil keeps every step in-loop.
func (*Engine) SetProgress ¶
SetProgress wires the sink for one-line gate progress messages (the CLI prints them to stderr). nil disables emission.
func (*Engine) SetReviewerBinding ¶ added in v0.0.189
func (g *Engine) SetReviewerBinding(b config.AgentBinding)
SetReviewerBinding stores the full resolved [reviewer] binding as the single resolution source for Invoke (sty_ba860c8a). Scalar caches are synced from it.
func (*Engine) SetReviewerEnv ¶ added in v0.0.129
SetReviewerEnv sets the reviewer binding's resolved env (config.ResolveAgentEnvs), applied to every isolated reviewer AND step-summariser subprocess this Engine runs — so a repo can point its reviews at an alternate model backend the same way a named executor does (sty_001558ce). Absent/empty leaves the child env at the inherited process env. Also mutates reviewerBinding.
func (*Engine) SetReviewerModel ¶
SetReviewerModel sets the reviewer's model from the agents layer (the resolved `reviewer` binding's `model`). It rides as `--model` to every isolated reviewer this Engine runs, so a repo can review on a different model (e.g. sonnet) without touching the executor. An empty value is ignored, keeping the agent CLI's default model (no `--model` flag emitted). Also mutates reviewerBinding.
func (*Engine) SetReviewerTools ¶
SetReviewerTools sets the reviewer's tool grant from the agents layer (the resolved `reviewer` binding). It governs every isolated LLM reviewer this Engine runs. The default remains the read-only grant; a repo may widen or narrow it in .satelle/workflows/agents.toml without touching the workflow. An empty value is ignored so callers can pass through an unset binding safely. Also mutates reviewerBinding.
func (*Engine) SetRunner ¶
SetRunner overrides the reviewer's agent-CLI runner — the agents layer's `reviewer` harness binding, resolved to a Runner. A nil runner is ignored, keeping the default configured at construction (the global `[agent] cli`).
func (*Engine) SetSecondaryResolver ¶ added in v0.0.294
func (g *Engine) SetSecondaryResolver(fn func(section string, b config.AgentBinding) (config.AgentBinding, string, bool))
SetSecondaryResolver wires rate-limit failover (sty_5bf61f89). The resolver returns (binding, name, ok) for a primary section + binding.
func (*Engine) SetTelemetry ¶ added in v0.0.138
func (g *Engine) SetTelemetry(fn TelemetryFunc)
SetTelemetry wires the sink the engine uses to record a dispatch-level telemetry event (agent-retry/agent-failure/agent-timeout). Pass nil to disable (the default) — every call site nil-checks via telemetryEvent.
func (*Engine) Summarise ¶
func (g *Engine) Summarise(ctx context.Context, item workitem.Item, from, to string) (verb.SummaryResult, error)
Summarise runs the read-only summariser over an enacted transition and returns its prose recap (empty when no summariser rubric is installed). The binding named on the step node (agent=<name>, default [reviewer]) supplies the harness and model so a cheap summariser can narrate without burning the deep judgment model. Grant stays read-only (observes, never mutates).
principles stays PrinciplesNone regardless of the binding's principles= (deliberate): the summariser is a rubric-only narrator; honouring session principles on every transition would inject the constitution into every recap and defeat a cheap high-frequency path (sty_8ee40f94).
TODO(sty_ba860c8a): fold onto Invoke once a soft-fail/empty-retry expect mode exists without ballooning ExpectVerdict/ExpectPerform. Today it still uses buildRequest+runOnce directly (AC1 carve-out).
func (*Engine) WorkflowNameFor ¶
WorkflowNameFor returns the name of the workflow that governs a story of the given category — the value stamped on the story at create. Empty when no workflow governs the category. Used by the create path to record the choice.
func (*Engine) WorkflowStates ¶
WorkflowStates returns the lifecycle states the named workflow declares — the nodes on its TRANSITIONS (an edge-less declared reviewer node like estimate/ step is a gate declaration, not a lifecycle state) — and whether the workflow resolves at all. The restamp validation seam (sty_ed3386cf): a story may only be re-stamped onto a workflow that declares its current status.
A lifecycle is a DERIVED ROUTE now, and a route's states depend on the story's category — which a name alone does not carry. So a name that resolves returns no states, and the caller skips the status check rather than stranding the story: the same contract an unparseable lifecycle always had here (sty_d953c5d8).
The derived route resolves by NAME, not by doc lookup (sty_81bb0dde). It is the one lifecycle that has no document of its own — it is built from the two route-source halves — so asking the index for a doc called "default" always misses, and `satelle story restamp` refused to stamp the very name the create path assigns. That is the exact round trip a stamp has to support: whatever WorkflowNameFor hands out, restamp must accept back.
type Expect ¶ added in v0.0.189
type Expect int
Expect selects the post-run contract for Invoke (sty_ba860c8a / design §4). ExpectVerdict runs the reviewer retry/parse loop; ExpectPerform is a single run.
func ExpectFromRole ¶ added in v0.0.189
ExpectFromRole maps a resolved binding role to an Expect. role=reviewer → verdict; everything else → perform.
type InvokeRequest ¶ added in v0.0.189
type InvokeRequest struct {
Binding config.AgentBinding // resolved binding (tools/model/env/settings/role/principles)
Section string // agents.toml section name (for role inference / logging)
Rubric string // skill body from the workflow node/edge
Payload any // marshalled to JSON stdin
Charter string // optional override; empty → charter from role/expect
// Expect selects the contract. Zero value is ExpectVerdict — callers that
// perform must set ExpectPerform explicitly.
Expect Expect
Timeout time.Duration // ≤0 → no per-run deadline (tests) or caller-supplied 0
// Runner overrides the runner built from Binding.Command. Reviewer path
// passes g.runner (bootstrap-resolved); named dispatch builds from the binding.
Runner agentcli.Runner
// Attempts bounds verdict retries (0 → engine default). Perform ignores this.
Attempts int
// Sink, when set, receives live subprocess stdout (named dispatch live log).
Sink io.Writer
// OnEvent observes provider-neutral progressive events. It is composed with
// the engine's interactive stderr progress consumer.
OnEvent agentcli.EventHandler
// Telemetry / progress context for the verdict retry loop (design note:
// Invoke needs labels beyond the minimal design sketch so retry telemetry
// and progress lines move intact from runReviewer).
StoryID string
Step string // to-status or step name
Skill string // rubric skill name for telemetry/logging
Actor string // ledger/telemetry actor (default "reviewer" for verdict)
}
InvokeRequest describes one isolated-agent call through the shared Invoke seam. Call sites (runReviewer, DispatchExecutor, Retrospect) fill this after their own pre-flight; Invoke owns prompt assembly, Runner.Run, and (for verdict) retry/parse.
type InvokeResult ¶ added in v0.0.189
type InvokeResult struct {
Stdout []byte
Usage agentcli.UsageResult
Command string // resolved harness command for ledger evidence
Decision *verb.GateDecision // non-nil when ExpectVerdict and parse succeeded
Err error
}
InvokeResult is the outcome of one Invoke call.
type TelemetryFunc ¶ added in v0.0.138
TelemetryFunc records one typed telemetry/quality event for storyID. Callers pass the event's outcome/kind and its typed data (never env/secrets — the implementation validates). Implemented by verb.AppendTelemetry and wired via SetTelemetry; best-effort by contract, so it takes no error return.