ir

package
v1.2.0 Latest Latest
Warning

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

Go to latest
Published: Jul 21, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package ir defines the canonical Intermediate Representation (IR) produced by compiling an AST. The IR is the sole source of truth for the runtime — it is execution-oriented, fully resolved, and independent of the DSL authoring surface.

Index

Constants

View Source
const (
	PolicyRequired   = "required"    // fail (resumable) if postcondition unmet; no recovery rungs
	PolicyRecover    = "recover"     // run recovery rungs, then fail if still unmet
	PolicyBestEffort = "best_effort" // warn + continue if postcondition unmet
)

Verified Action policy values (ADR-044).

View Source
const (
	SessionFresh              = types.SessionFresh
	SessionInherit            = types.SessionInherit
	SessionInheritIfAvailable = types.SessionInheritIfAvailable
	SessionArtifactsOnly      = types.SessionArtifactsOnly
	SessionFork               = types.SessionFork
)
View Source
const (
	RouterFanOutAll  = types.RouterFanOutAll
	RouterCondition  = types.RouterCondition
	RouterRoundRobin = types.RouterRoundRobin
	RouterLLM        = types.RouterLLM
	RouterFanOutEach = types.RouterFanOutEach
)
View Source
const (
	AwaitNone       = types.AwaitNone
	AwaitWaitAll    = types.AwaitWaitAll
	AwaitBestEffort = types.AwaitBestEffort
)
View Source
const (
	InteractionNone       = types.InteractionNone
	InteractionHuman      = types.InteractionHuman
	InteractionLLM        = types.InteractionLLM
	InteractionLLMOrHuman = types.InteractionLLMOrHuman
	InteractionReview     = types.InteractionReview
	InteractionAsync      = types.InteractionAsync
)
View Source
const (
	PostureHumanRequired  = "human_required"   // always wait for the human's merge action (default)
	PostureAgentVerdictOK = "agent_verdict_ok" // a high-confidence companion approval may auto-merge
)

Review-gate posture values (interaction: review).

View Source
const (
	MCPTransportUnknown = types.MCPTransportUnknown
	MCPTransportStdio   = types.MCPTransportStdio
	MCPTransportHTTP    = types.MCPTransportHTTP
	MCPTransportSSE     = types.MCPTransportSSE
)
View Source
const (
	FieldTypeString      = types.FieldTypeString
	FieldTypeBool        = types.FieldTypeBool
	FieldTypeInt         = types.FieldTypeInt
	FieldTypeFloat       = types.FieldTypeFloat
	FieldTypeJSON        = types.FieldTypeJSON
	FieldTypeStringArray = types.FieldTypeStringArray
)
View Source
const (
	CapWatchSubscribe   = "watch.subscribe"
	CapWatchUnsubscribe = "watch.unsubscribe"
)

Watch capability names. A node with watch.subscribe / watch.unsubscribe can opt its run into the runtime watch fan-out (MVP3b): once subscribed to a native-board issue, the run receives a queued message whenever that issue changes state. This is the source of truth for the strings; the claw tool registrar (pkg/backend/tool/claw_watch_tools.go) mirrors them. Currently wired for the claw backend only — see RegisterClawWatchTools.

View Source
const DefaultReviewMaxTurns = 8

DefaultReviewMaxTurns bounds the companion↔human dialogue so it always converges to an asymptote rather than re-pausing forever.

Variables

View Source
var AttachmentSubFields = map[string]struct{}{
	"path":   {},
	"url":    {},
	"mime":   {},
	"size":   {},
	"sha256": {},
}

AttachmentSubFields enumerates the sub-fields that may appear after `attachments.<name>.` in a template reference.

Example: `{{attachments.logo.url}}` has SubField "url".

KnownCapabilities is the set of capabilities the iterion runtime understands at compile time. The validator emits a C080 warning for any cap declared on an agent or judge that is not in this set — but does not reject it, so future capabilities can be registered out-of-tree without DSL changes.

Capability naming convention: lowercase `domain` or `domain.action`, e.g. `board.create`, `board.read`. Enforced by C081.

View Source
var KnownProviders = map[string]bool{
	"anthropic": true,
	"zai":       true,
	"openai":    true,
	"auto":      true,
}

KnownProviders is the set of credential-routing hints the runtime understands for the per-node `provider:` field (and its comma-separated fallback-chain form). Like KnownCapabilities this is a soft set: an unknown token is a warning (C087), not an error — a token may be meaningful to an out-of-tree backend, and env-ref forms (${VAR}) resolve only at run time. Mirrors the hint values matched by pkg/backend/delegate.anthropicCredEnvForCLI and the claw registry.

View Source
var ValidReasoningEfforts = map[string]bool{
	"low":    true,
	"medium": true,
	"high":   true,
	"xhigh":  true,
	"max":    true,

	"ultracode": true,
}

ValidReasoningEfforts is the set of accepted reasoning effort levels. Mirrors the Anthropic effort spec (platform.claude.com/docs/en/build-with-claude/effort) and the CLAUDE_CODE_EFFORT_LEVEL env var (code.claude.com/docs/en/model-config). Per-model availability is curated upstream in claw-code-go's ModelEntry; this set is the union across all models.

Functions

func ApplyBudgetOverrides added in v0.43.0

func ApplyBudgetOverrides(wf *Workflow, o BudgetOverrides)

ApplyBudgetOverrides mutates wf.Budget in place with any non-zero override. A nil wf.Budget is allocated only when at least one override is supplied; otherwise the workflow keeps its (possibly nil) budget so newSharedBudget continues to treat the run as unbudgeted. Mirrors recipe.applyBudget's precedence: non-zero override wins, zero inherits.

It must run AFTER the workflow is resolved (recipe/preset budget already folded in) but BEFORE the executor is built — the executor snapshots Budget at construction time, so a later mutation would be invisible to the model/cost layer.

func ExpandEnvWithDefault added in v0.39.0

func ExpandEnvWithDefault(s string) string

ExpandEnvWithDefault expands ${VAR} and ${VAR:-default} forms in s. Mirrors the shell parameter-expansion default-value syntax that stdlib os.ExpandEnv does not support: when ${VAR} is unset or empty, the part after :- is returned instead. Exported so the executor and other callers stay in sync with the validator's expansion semantics — anything that defaults a model spec or env-tunable field via `${VAR:-default}` in a recipe relies on this rather than the bare stdlib helper, which would expand `${X:-y}` to "" (treating the whole `X:-y` as the variable name).

Supports nested fallbacks (`${A:-${B:-c}}`): we parse `${...}` segments with brace-counting so nested defaults are resolved inside-out. os.Expand isn't recursive and would stop at the first `}`, leaving a trailing brace literal — so we cannot rely on it.

func IsEnvSubstitutedEffort added in v0.4.0

func IsEnvSubstitutedEffort(s string) bool

IsEnvSubstitutedEffort reports whether an effort literal is an env-substituted form (e.g. "${VAR}" or "${VAR:-default}") that must be resolved at runtime. The "$" guard is intentionally permissive — the runtime resolver handles malformed forms by falling back to the empty string.

func IsTerminalNode

func IsTerminalNode(n Node) bool

IsTerminalNode returns true if the node is a DoneNode or FailNode.

func NodeActiveMCPServers

func NodeActiveMCPServers(n Node) []string

NodeActiveMCPServers returns the ActiveMCPServers list for nodes that support it, or nil.

func NodeImplicitOutputFields added in v1.1.0

func NodeImplicitOutputFields(n Node) []string

NodeImplicitOutputFields returns the FIXED output field names of node kinds whose output shape is built in rather than schema-declared (await_answers → {answers}). nil for every other kind. Reference validation accepts exactly these fields and hard-errors on any other, keeping the per-kind knowledge here instead of inside the validators.

func NodeInputSchema

func NodeInputSchema(n Node) string

NodeInputSchema returns the InputSchema for nodes that support it, or "".

func NodeNeeds added in v0.39.0

func NodeNeeds(n Node) []string

NodeNeeds returns the resource names a node acquires before running (the `needs:` property). Nodes without a `needs:` declaration — and node kinds that don't support it (human/compute/done/fail) — return nil.

func NodeOutputSchema

func NodeOutputSchema(n Node) string

NodeOutputSchema returns the OutputSchema for nodes that support it, or "".

func NodePromptRefs

func NodePromptRefs(node Node) []string

NodePromptRefs returns all prompt reference names used by a node.

func NodePublish

func NodePublish(n Node) string

NodePublish returns the Publish field for nodes that support it, or "".

func NodePublishLabels added in v0.39.0

func NodePublishLabels(n Node) []string

NodePublishLabels returns the DSL `artifact_labels:` list for the publish-capable node types (Agent/Human/Tool/Compute), or nil. Judge nodes don't publish, so they're excluded.

func ResolveCursorValue added in v0.39.0

func ResolveCursorValue(def *CursorDef, raw string) (prompt string, ok bool, reason string)

ResolveCursorValue classifies raw against the cursor definition and returns the matching prompt fragment. Used by both the compile-time validator (to verify reachability — it ignores the prompt and only consults ok) and the runtime resolver (to obtain the fragment). reason is set when ok is false to drive precise C084 diagnostics; it is "" on success.

Numeric values clamp to [0,1]. When the cursor declares only values:, numeric inputs snap to an enum position. When it declares only bands:, enum inputs are rejected.

func ResolveEffortLiteral added in v0.4.0

func ResolveEffortLiteral(s string) string

ResolveEffortLiteral expands env-substituted forms ("${VAR}", "${VAR:-default}") against the process env and validates the result against ValidReasoningEfforts. Non-env-substituted values are returned unchanged. Invalid expansions return "" so callers can fall back to the provider's documented default.

func SplitProviderStep added in v0.39.0

func SplitProviderStep(token string) (hint, model string, hasModel bool)

SplitProviderStep splits one provider-chain token into its provider hint and optional per-element model on the FIRST colon (so a model id that itself contains a colon survives). hasModel reports whether a colon was present at all, letting callers distinguish "zai" (no model) from "zai:" (malformed empty model). It is the single source of truth for the `provider:model` element form, shared by the compiler (validateProviders) and the runtime (model.resolveProviderChain) so the two never drift.

Types

type AgentNode

type AgentNode struct {
	BaseNode
	LLMFields
	SchemaFields
	InteractionFields
	MCP              *MCPConfig // node-level MCP activation/filtering
	ActiveMCPServers []string   // populated after project config resolution
	Publish          string     // persistent artifact name (empty if not set)
	PublishLabels    []string   // DSL artifact_labels: applied to the published artifact
	Session          SessionMode
	Tools            []string // tool capability names
	ToolPolicy       []string // per-node tool policy patterns (nil = inherit workflow)
	Capabilities     []string // host-side capabilities (e.g. board.create); nil = inherit workflow
	Skills           []string // skill-library references; resolved names mirrored into .claude/skills/ (nil = inherit workflow)
	ToolMaxSteps     int      // max tool-use iterations (0 = not set)
	AwaitMode        AwaitMode
	Compaction       *Compaction  // per-node compaction overrides (nil = inherit workflow)
	Memory           *Memory      // per-node workspace memory opt-in (nil = disabled)
	Sandbox          *SandboxSpec // node-level sandbox override (nil = inherit workflow)
	Cursors          *CursorInvocation
	Compress         string   // compress output-compression mode: on|ultra|off ("" = inherit)
	Permission       string   // permission gate mode override: off|ask|deny ("" = inherit workflow)
	Needs            []string // resource names this node acquires before running (counting semaphores)
}

AgentNode is an LLM agent node with tools, structured I/O, and optional delegation.

func (*AgentNode) GetActiveMCPServers added in v0.39.0

func (n *AgentNode) GetActiveMCPServers() []string

func (*AgentNode) GetAwaitMode added in v0.39.0

func (n *AgentNode) GetAwaitMode() AwaitMode

func (*AgentNode) GetCapabilities added in v0.39.0

func (n *AgentNode) GetCapabilities() []string

func (*AgentNode) GetCompaction added in v0.39.0

func (n *AgentNode) GetCompaction() *Compaction

func (*AgentNode) GetCompress added in v0.39.0

func (n *AgentNode) GetCompress() string

func (*AgentNode) GetCursors added in v0.39.0

func (n *AgentNode) GetCursors() *CursorInvocation

func (*AgentNode) GetInteractionFields added in v0.39.0

func (n *AgentNode) GetInteractionFields() *InteractionFields

func (*AgentNode) GetLLMFields added in v0.39.0

func (n *AgentNode) GetLLMFields() *LLMFields

LLMNode accessor methods on *AgentNode.

func (*AgentNode) GetMemory added in v0.39.0

func (n *AgentNode) GetMemory() *Memory

func (*AgentNode) GetPermission added in v0.39.0

func (n *AgentNode) GetPermission() string

func (*AgentNode) GetPublish added in v0.39.0

func (n *AgentNode) GetPublish() string

func (*AgentNode) GetSchemaFields added in v0.39.0

func (n *AgentNode) GetSchemaFields() *SchemaFields

func (*AgentNode) GetSession added in v0.39.0

func (n *AgentNode) GetSession() SessionMode

func (*AgentNode) GetSkills added in v0.39.0

func (n *AgentNode) GetSkills() []string

func (*AgentNode) GetToolMaxSteps added in v0.39.0

func (n *AgentNode) GetToolMaxSteps() int

func (*AgentNode) GetTools added in v0.39.0

func (n *AgentNode) GetTools() []string

func (*AgentNode) NodeKind

func (n *AgentNode) NodeKind() NodeKind

NodeKind implements Node.

type Attachment added in v0.7.0

type Attachment struct {
	Name        string
	Type        AttachmentType
	Required    bool
	AcceptMIME  []string // nil = inherit server allowlist
	Description string
}

Attachment is a resolved attachment declaration. The bytes themselves are persisted by the run store; this struct only carries the schema (name, type, validation hints) consumed by the parser, runtime and studio frontend.

type AttachmentType added in v0.7.0

type AttachmentType int

AttachmentType enumerates the supported attachment binary types.

const (
	AttachmentFile AttachmentType = iota
	AttachmentImage
)

func (AttachmentType) String added in v0.7.0

func (a AttachmentType) String() string

type AwaitAnswersNode added in v1.1.0

type AwaitAnswersNode struct {
	BaseNode
	From    string        // optional node ref: only await questions posted by this node ("" = whole run)
	Timeout time.Duration // mandatory bound on the wait
}

AwaitAnswersNode blocks its branch until every pending async human question (posted via the ask_user_async tool by the From node — or by any node in the run when From is empty) has been answered, then completes with the collected answers as its output: {answers: [{interaction_id, node, question, answer}]}. The Timeout is mandatory (the "no silent infinity" invariant) and bounds the wait. The predicate is level-triggered against the interaction store, so answers that arrived while the process was down are honoured on resume.

func (*AwaitAnswersNode) NodeKind added in v1.1.0

func (n *AwaitAnswersNode) NodeKind() NodeKind

NodeKind implements Node.

type AwaitMode

type AwaitMode = types.AwaitMode

AwaitMode determines how a convergence point handles multiple incoming branches.

func NodeAwaitMode

func NodeAwaitMode(n Node) AwaitMode

NodeAwaitMode returns the AwaitMode for nodes that support it, or AwaitNone.

type BaseNode

type BaseNode struct {
	ID          string // unique identifier (= DSL name)
	Description string // optional human-readable label (surfaced in the run console)
}

BaseNode provides the common fields embedded in every concrete node.

func (BaseNode) NodeDescription added in v0.50.0

func (b BaseNode) NodeDescription() string

NodeDescription returns the node's optional human-readable label. Promoted onto every concrete node type via embedding.

func (BaseNode) NodeID

func (b BaseNode) NodeID() string

NodeID implements Node.

type Budget

type Budget struct {
	MaxParallelBranches int
	MaxDuration         string // e.g. "60m"
	MaxCostUSD          float64
	MaxTokens           int
	// WarnTokens is advisory-only: crossing it emits a budget_warning
	// (advisory) but never blocks execution. 0 = disabled.
	WarnTokens    int
	MaxIterations int
}

Budget defines execution limits for a workflow.

func (*Budget) ClampToCeiling added in v0.39.0

func (b *Budget) ClampToCeiling(ceiling *Budget)

ClampToCeiling lowers each numeric limit so it never EXCEEDS the corresponding ceiling (a non-zero ceiling field). Unlike applyBudgetOverrides (which lets a value rise), this only ever shrinks — it is the multitenant safeguard a cloud runner applies so a tenant's bot, however large its declared budget (especially `as X(unbounded)` whose fuel falls back to budget.MaxIterations), can never exceed the platform's hard ceiling. A zero ceiling field means "no platform limit on this dimension" and is ignored. A zero workflow field means "unlimited" and is RAISED to the ceiling (so an unbudgeted bot still inherits the platform cap). Duration is compared by parsed seconds; an unparseable value is replaced by the ceiling.

type BudgetOverrides added in v0.43.0

type BudgetOverrides struct {
	MaxCostUSD          float64
	MaxTokens           int
	MaxDuration         string
	MaxIterations       int
	MaxParallelBranches int
}

BudgetOverrides carries launch-time budget limits that override a workflow's declared `budget:` block at run time. Each field uses the "non-zero wins, zero inherits" convention shared with recipe budget overrides (see recipe.applyBudget). This lets any bot be re-budgeted at launch without editing its .bot — e.g.

iterion run bots/foo/main.bot --max-cost-usd 120 --max-duration 4h

or the equivalent `budget` object on POST /api/runs. Precedence is DSL budget: → recipe/preset → launch overrides (overrides win, which is the intent of an at-run override).

func (BudgetOverrides) IsZero added in v0.43.0

func (o BudgetOverrides) IsZero() bool

IsZero reports whether no override was supplied.

func (BudgetOverrides) Validate added in v0.43.0

func (o BudgetOverrides) Validate() error

Validate rejects a malformed MaxDuration early with an actionable error, rather than letting newSharedBudget silently drop an unparseable duration (which would look like the override had no effect).

type Compaction

type Compaction struct {
	Threshold      float64 // 0 = inherit (env / 0.85 default)
	PreserveRecent int     // 0 = inherit (default 4)
}

Compaction overrides the default compaction behavior. Threshold is applied as a fraction of the model's context window (0 means inherit). PreserveRecent caps the number of recent messages kept verbatim (0 means inherit).

type CompileResult

type CompileResult struct {
	Workflow    *Workflow
	Diagnostics []Diagnostic
}

CompileResult holds the compiled IR workflow and any diagnostics.

func Compile

func Compile(file *ast.File) *CompileResult

Compile transforms an AST File into a canonical IR Workflow. In V1, exactly one workflow per file is supported.

func (*CompileResult) HasErrors

func (r *CompileResult) HasErrors() bool

HasErrors returns true if any diagnostic is an error.

type ComputeExpr

type ComputeExpr struct {
	Key string    // output field name
	AST *expr.AST // parsed expression
	Raw string    // original source for diagnostics / unparse
}

ComputeExpr is a single field expression in a ComputeNode.

type ComputeNode

type ComputeNode struct {
	BaseNode
	SchemaFields
	Exprs         []*ComputeExpr // ordered field-name → parsed AST pairs
	Publish       string         // persistent artifact name (empty = not published)
	PublishLabels []string       // DSL artifact_labels: applied to the published artifact
	AwaitMode     AwaitMode
}

ComputeNode evaluates a set of named expressions over the standard reference namespaces (vars, input, outputs, artifacts, loop, run) and returns them as a structured output. It performs no LLM call and no shell-out; expressions are parsed at compile time and re-evaluated on each visit.

func (*ComputeNode) NodeKind

func (n *ComputeNode) NodeKind() NodeKind

NodeKind implements Node.

type CursorBandSpec added in v0.39.0

type CursorBandSpec struct {
	Lo     float64
	Hi     float64
	Prompt string
}

CursorBandSpec is one resolved entry of a numeric cursor: Lo..Hi (inclusive on both ends) → Prompt. Parsed from the AST band Range string ("0.0..0.33").

type CursorDef added in v0.39.0

type CursorDef struct {
	Name        string
	Description string
	Values      []CursorValue // enum form: ordered, numeric invocations snap to position
	Bands       []CursorBandSpec
}

CursorDef is the normalized IR form of a `cursor NAME:` declaration. Exactly one of Values / Bands is non-nil (validated by C085).

type CursorInvocation added in v0.39.0

type CursorInvocation struct {
	Enabled  bool
	Settings []CursorSetting
}

CursorInvocation is the IR form of an agent/judge `cursors:` block. Settings preserves declaration order; resolution sorts by cursor name alphabetically before composing the prompt suffix so identical activations produce identical prompts (prompt-cache friendly).

type CursorSetting added in v0.39.0

type CursorSetting struct {
	Key   string
	Value string
}

CursorSetting is one `name: value` pair inside a `cursors:` block. Value is stored raw (may contain ${VAR}); resolution happens at runtime against the workflow's Cursors map.

type CursorValue added in v0.39.0

type CursorValue struct {
	Name   string
	Prompt string
}

CursorValue is one ordered entry of an enum cursor.

type DataMapping

type DataMapping struct {
	Key  string // target input field name
	Refs []*Ref // parsed references from the template value
	Raw  string // original template string for debugging
}

DataMapping maps a target input field key to a parsed reference.

type DiagCode

type DiagCode string

DiagCode identifies the kind of compilation diagnostic.

const (
	DiagUnknownNode           DiagCode = "C001" // edge references unknown node
	DiagUnknownSchema         DiagCode = "C002" // node references unknown schema
	DiagUnknownPrompt         DiagCode = "C003" // node references unknown prompt
	DiagBadTemplateRef        DiagCode = "C004" // malformed template reference
	DiagDuplicateLoop         DiagCode = "C005" // conflicting loop definitions
	DiagNoWorkflow            DiagCode = "C006" // no workflow found in file
	DiagMultipleWorkflow      DiagCode = "C007" // multiple workflows (unsupported in V1)
	DiagMissingEntry          DiagCode = "C008" // entry node not found
	DiagMissingModelOrBackend DiagCode = "C018" // agent/judge has neither model nor backend
	DiagDuplicateMCPServer    DiagCode = "C024" // duplicate top-level mcp_server name
	DiagInvalidMCPServer      DiagCode = "C025" // invalid MCP server config
	DiagCodexDiscouraged      DiagCode = "C030" // codex backend is supported but discouraged
	DiagComputeNoExpr         DiagCode = "C039" // compute node has no expressions
	DiagBadExpr               DiagCode = "C040" // expression failed to parse
	DiagDuplicateNodeID       DiagCode = "C041" // two declarations share a node ID
	DiagReservedNodeName      DiagCode = "C042" // user node uses reserved name (done/fail)
	DiagInvalidSandboxMode    DiagCode = "C044" // sandbox mode value is not one of "", none, auto
	DiagSandboxAutoNoConfig   DiagCode = "C045" // sandbox: auto requested but no .devcontainer/devcontainer.json found
	DiagBudgetCostInvalid     DiagCode = "C046" // budget.max_cost_usd negative, NaN or Inf
	DiagResourceCapInvalid    DiagCode = "C194" // resources.<name> capacity ≤ 0
)
const (
	DiagUnknownCursor    DiagCode = "C083" // agent/judge references a cursor name not declared at workflow scope
	DiagInvalidCursorVal DiagCode = "C084" // cursor invocation value is invalid (not in enum / out of [0,1] / no matching band)
	DiagMalformedCursor  DiagCode = "C085" // cursor decl is malformed (missing values+bands, bad range, overlapping bands)
	DiagDuplicateCursor  DiagCode = "C086" // duplicate cursor name in workflow
)

Cursor diagnostic codes (slot C083–C085, reserved alongside the capability codes C080–C082 in validate_capabilities.go).

const (
	DiagUnknownWatchedNode      DiagCode = "C190" // supervisor watches a node id that isn't an agent node
	DiagMalformedSupervisor     DiagCode = "C191" // supervisor decl is malformed (bad cooldown duration)
	DiagDuplicateSupervisor     DiagCode = "C192" // duplicate supervisor name in workflow
	DiagUnknownSupervisorPrompt DiagCode = "C193" // supervisor system: references an undeclared prompt
)

Supervisor diagnostic codes (slot C190–C193, a fresh band above the current high-water mark).

const (
	DiagSessionAfterConvergence  DiagCode = "C009" // session: inherit or fork on convergence point
	DiagMultipleDefaultEdges     DiagCode = "C010" // multiple unconditional edges from same non-fan_out source
	DiagAmbiguousCondition       DiagCode = "C011" // ambiguous conditional edges from same source
	DiagMissingFallback          DiagCode = "C012" // conditional edges with no default fallback
	DiagConditionNotBool         DiagCode = "C013" // when field is not boolean in output schema
	DiagConditionFieldNotFound   DiagCode = "C014" // when field not found in source output schema
	DiagElseWithoutConditional   DiagCode = "C015" // else edge with no conditional (when) sibling
	DiagUnreachableNode          DiagCode = "C016" // node unreachable from entry
	DiagHistoryRefNotInLoop      DiagCode = "C017" // outputs.<node>.history but node not in a loop
	DiagUndeclaredCycle          DiagCode = "C019" // cycle without a declared loop (infinite loop risk)
	DiagRoundRobinTooFewEdges    DiagCode = "C020" // round_robin router with fewer than 2 outgoing edges
	DiagLLMRouterTooFewEdges     DiagCode = "C021" // llm router with fewer than 2 outgoing edges
	DiagLLMRouterConditionEdge   DiagCode = "C022" // llm router edge has a 'when' condition
	DiagRouterLLMOnlyProperty    DiagCode = "C023" // LLM-only property on non-llm router
	DiagFanOutEachMissingOver    DiagCode = "C113" // fan_out_each router without an 'over:' array source (was C102, clashed with DiagInvalidRTK on main)
	DiagFanOutEachOnlyProperty   DiagCode = "C114" // 'over'/'as'/'key'/'depends_on' property on a non-fan_out_each router (was C103)
	DiagFanOutEachEdges          DiagCode = "C115" // fan_out_each router must have exactly one outgoing template edge (was C104)
	DiagUseUnknownGroup          DiagCode = "C116" // use references a group that is not declared (error)
	DiagUseParamMismatch         DiagCode = "C117" // use provides an unknown param, or omits a declared one (error)
	DiagForeachConflictsLoop     DiagCode = "C118" // edge combines `as foreach` with `as <loop>` (error)
	DiagSubbotNoSource           DiagCode = "C119" // subbot node without a `source:` child .bot (error)
	DiagInvalidReasoningEffort   DiagCode = "C027" // invalid reasoning_effort value (was C024, clashed with DiagDuplicateMCPServer)
	DiagUltracodeModelGate       DiagCode = "C089" // reasoning_effort: ultracode on a model that isn't claude-opus-4-8 (warning)
	DiagInvalidLoopIterations    DiagCode = "C026" // loop max_iterations must be >= 1
	DiagDuplicateWithKey         DiagCode = "C028" // duplicate with-mapping key across edges to same target
	DiagUnknownRefNode           DiagCode = "C029" // outputs ref to non-existent node (was C030, clashed with DiagCodexDiscouraged)
	DiagRefFieldNotInSchema      DiagCode = "C031" // outputs ref field not in output schema
	DiagRefNodeNoSchema          DiagCode = "C032" // outputs ref field on node without output schema
	DiagUndeclaredVar            DiagCode = "C033" // vars ref to undeclared variable
	DiagInputFieldNotInSchema    DiagCode = "C034" // input ref field not in input schema
	DiagUnknownResourceInNeeds   DiagCode = "C195" // needs: references a resource not declared in resources:
	DiagUnknownArtifact          DiagCode = "C035" // artifacts ref to unpublished artifact
	DiagRefNodeNotReachable      DiagCode = "C036" // outputs ref to node not reachable before consumer
	DiagNodeMaxTokensVsBudget    DiagCode = "C037" // node-level max_tokens exceeds workflow.budget.max_tokens
	DiagUnsupportedMCPAuth       DiagCode = "C038" // MCP server Auth.Type not supported (only "oauth2" is wired)
	DiagMultipleElseEdges        DiagCode = "C123" // more than one else edge from the same source
	DiagElseWithUnconditional    DiagCode = "C124" // else edge alongside a bare unconditional sibling
	DiagInvalidCompaction        DiagCode = "C043" // compaction.threshold or compaction.preserve_recent out of range
	DiagMemoryNotSupported       DiagCode = "C047" // memory: enabled on a backend that does not consume it (only claw does today)
	DiagMemoryMissingScope       DiagCode = "C048" // memory: enabled without a scope: name
	DiagArtifactLabelsNoPublish  DiagCode = "C049" // artifact_labels: set on a node with no publish: (nothing to attach to)
	DiagMemoryInvalidVisibility  DiagCode = "C170" // memory: unknown visibility value
	DiagMemoryVisibilityConflict DiagCode = "C171" // memory: visibility: with the legacy project_root:
	DiagBadPromptInclude         DiagCode = "C055" // prompt {{include "..."}} marker could not be resolved

	// Attachments diagnostics
	DiagDuplicateAttachment       DiagCode = "C050" // attachment name declared more than once
	DiagAttachmentVarConflict     DiagCode = "C051" // attachment name collides with a declared var
	DiagInvalidAttachmentMIME     DiagCode = "C052" // accept_mime entry not in type/subtype form
	DiagUnknownAttachment         DiagCode = "C053" // {{attachments.X}} but X not declared
	DiagAttachmentSubfieldUnknown DiagCode = "C054" // attachments.<name>.<subfield> sub-field unknown

	// Browser-pane diagnostics (PR 3 of the browser-simulation
	// feature). Reserve C060+ for future browser/Playwright checks.
	DiagPlaywrightNeedsBrowserImage DiagCode = "C060" // Playwright MCP server requires a browser-capable sandbox image

	// Presets diagnostics (in-source `presets:` block).
	DiagPresetUnknownVar   DiagCode = "C070" // preset references a variable not declared in vars:
	DiagPresetTypeMismatch DiagCode = "C071" // preset value type does not match the declared variable type
	DiagDuplicatePreset    DiagCode = "C072" // preset name declared more than once

	// Secrets diagnostics (in-source `secrets:` block).
	DiagDuplicateSecret   DiagCode = "C090" // secret name declared more than once
	DiagSecretVarConflict DiagCode = "C091" // secret name collides with a declared var
	DiagInvalidSecretHost DiagCode = "C092" // secret egress host scoping ill-formed (Layer 2)
	DiagUnknownSecret     DiagCode = "C093" // {{secrets.X}} but X not declared
	DiagInvalidSecretFile DiagCode = "C094" // file secret declaration is malformed
	DiagSecretSubfield    DiagCode = "C095" // unsupported {{secrets.X.<subfield>}}

	// Unbounded-loop diagnostics (Turing-completeness Layer B).
	DiagUnboundedNoFuel DiagCode = "C097" // `as X(unbounded)` without a fuel ceiling (clause fuel or budget.max_iterations) (error)
	DiagUnboundedNoExit DiagCode = "C098" // unbounded loop whose back-edges have no sibling when-exit (warning)

	// Review-gate diagnostics (interaction: review).
	DiagReviewNeedsWorktree DiagCode = "C100" // interaction: review without worktree: auto — nothing to merge (error)
	DiagReviewURLUnknownRef DiagCode = "C101" // review_url references an output node that does not exist (warning)

	// Compress output-compression mode diagnostics.
	DiagInvalidCompress DiagCode = "C102" // compress: value not one of on|off|ultra (error)

	// Static cross-node typing diagnostics (Phase 2). These resist the
	// looseness that makes the rest of the validator a graph linter: they
	// fire ONLY on genuinely-typed slots (enum literals compared against an
	// enum-typed field, typed operands inside compute/when expressions),
	// never on template stringification. A json (= any) field or an
	// unknown ref always bails to "no opinion" so legitimate looseness
	// keeps passing.
	//
	// NOTE: an earlier draft also checked edge with-mapping keys/types
	// against the target node's input schema. That was dropped: the runtime
	// (engine.buildNodeInputRS) passes EVERY with-key through verbatim and
	// never validates node input against the declared input schema — the
	// schema is advisory, not a contract a with-mapping must satisfy — so
	// such a check rests on a false premise.
	//
	// C103-C106 belong to the Verified Action family (ADR-044, see
	// validate_verified_action.go); the enum-literal check below is C121 so
	// it joins the expr-type cluster (C107/C108/C120/C121) and does not
	// collide with DiagInvalidPolicy.
	DiagEnumLiteralMismatch     DiagCode = "C121" // comparison literal outside the target field's enum set (error)
	DiagExprOperandTypeMismatch DiagCode = "C107" // compute/when expression operands incompatible under the operator (warning)
	DiagWhenExprNotBoolish      DiagCode = "C108" // when-expression result clearly not bool-coercible (warning)
	DiagVarDefaultTypeMismatch  DiagCode = "C109" // a var's default literal type does not match its declared type (error)
	DiagInvalidPermission       DiagCode = "C110" // permission: value not one of off|ask|deny (error)
	DiagPermissionRulesNoGate   DiagCode = "C111" // allow/ask/deny rules declared but the resolved permission mode is "" or off (warning)
	DiagToolNodePermissionInert DiagCode = "C112" // permission: on a tool node — parsed but not enforced (warning)
	DiagIndexOnScalar           DiagCode = "C120" // subscript `[...]` applied to a statically-scalar value (warning) — C113-C119 taken by the fan_out_each/groups epic
	DiagInvalidNodeTimeout      DiagCode = "C122" // LLM node `timeout:` is not a valid Go duration (error) — C121 taken, C199 is skill-ref on main
	// Var enum constraints (`name: string [enum: "a", "b"] = "a"`).
	DiagVarEnumNonString    DiagCode = "C125" // enum constraint on a non-string var type (error)
	DiagVarDefaultNotInEnum DiagCode = "C126" // var default value not in the enum list (error)
	DiagVarEnumDuplicate    DiagCode = "C127" // duplicate enum values in a var constraint (warning; deduped)
	// Event-driven primitives (ADR-051): emit/wait nodes.
	DiagEventNoName     DiagCode = "C196" // emit/wait node with no `event:` name (error)
	DiagWaitNoTimeout   DiagCode = "C197" // wait node with no `timeout:` (error — the no-silent-infinity invariant)
	DiagEventNoListener DiagCode = "C198" // wait on an event no emit produces, or emit no wait consumes (warning — dangling event)
	// Skill library (ADR-059): `skills:` references on nodes / workflow.
	DiagInvalidSkillRef DiagCode = "C199" // malformed skill-library reference name (warning; existence is resolved at run time)
	// Async human interaction (ADR-081): interaction: async + await_answers
	// nodes. C240 band — C200–C230 are claimed by pkg/bundlelint's manifest
	// lint codes (same Cnnn namespace, guarded by TestDiagCodesAreUnique).
	DiagAsyncOnHuman          DiagCode = "C240" // interaction: async on a human node — only agent/judge can post async questions (error)
	DiagAwaitAnswersNoTimeout DiagCode = "C241" // await_answers node with no `timeout:` (error — the no-silent-infinity invariant)
	DiagAwaitAnswersBadFrom   DiagCode = "C242" // await_answers `from:` names a node that is missing or not interaction: async (warning — it can only ever time out)
)
const (
	DiagUnknownCapability   DiagCode = "C080" // unknown capability name (warning, registry is open)
	DiagMalformedCapability DiagCode = "C081" // capability name does not match the required shape
	DiagBoardCapInSandbox   DiagCode = "C082" // board.* capability requested while sandboxed (HTTP transport needed)
)

Capability diagnostics.

const (
	DiagUnknownProvider       DiagCode = "C087" // provider chain token outside the known set (warning)
	DiagProviderChainIgnored  DiagCode = "C088" // multi-provider chain on a backend that ignores the hint (warning)
	DiagMalformedProviderStep DiagCode = "C172" // provider chain element of the `provider:model` form with an empty provider or model part (warning)
)

Provider-routing diagnostics.

const (
	DiagInvalidPolicy        DiagCode = "C103" // policy value not in {required, recover, best_effort} (error)
	DiagRecoveryNoPostcond   DiagCode = "C104" // recovery configured without a postcondition (error)
	DiagRecoveryOnGate       DiagCode = "C105" // recovery rungs attached to a gate (recipe == postcondition) (error)
	DiagRecoveryWithoutRecov DiagCode = "C106" // recovery bounds present but policy != recover (warning, dead config)
)

Verified Action diagnostics (ADR-044). Deterministic ACTION nodes (tool) may carry a goal + postcondition + policy + recovery quad. The postcondition is what makes the recovery rungs SAFE — an agent cannot fake success past a deterministic property check. These diagnostics encode the anti-Goodhart firewall: recovery is for actions, never gates.

const (
	DiagCommandIgnored DiagCode = "C174" // `command:` set on a backend that does not consume it (warning)
)

Per-node CLI-command diagnostics.

type Diagnostic

type Diagnostic struct {
	Code     DiagCode
	Severity Severity
	Message  string
	NodeID   string
	EdgeID   string
	Hint     string
}

Diagnostic represents a compilation error or warning.

NodeID and EdgeID are best-effort attribution fields used by tooling (the studio renders them as inline badges). They may be empty when the diagnostic is global (e.g. "no workflow"). EdgeID follows the canonical "<from>-><to>" format the studio uses; when multiple edges share endpoints the first matching one wins.

Hint is a one-line, user-facing fix suggestion when one is known. The authoritative documentation still lives in `docs/diagnostics.md`; Hint is for UIs that want a quick tooltip without round-tripping to docs.

func (Diagnostic) Error

func (d Diagnostic) Error() string

type DoneNode

type DoneNode struct {
	BaseNode
	AwaitMode AwaitMode // convergence strategy when multiple branches arrive
}

DoneNode is a terminal success node.

func (*DoneNode) NodeKind

func (n *DoneNode) NodeKind() NodeKind

NodeKind implements Node.

type Edge

type Edge struct {
	From string // source node ID
	To   string // target node ID

	// Condition (optional). Condition is a field name from the source
	// node's output schema. Negated inverts the check. Mutually exclusive
	// with Expression: the compiler chooses one form per edge.
	Condition string
	Negated   bool

	// Expression (optional). When non-nil, this parsed expression replaces
	// Condition/Negated and is evaluated against the source node's output
	// (exposed as `input`/`outputs.<self>`), the run vars, artifacts, and
	// loop/run namespaces.
	Expression    *expr.AST
	ExpressionSrc string // original source string preserved for unparse/debug

	// IsElse marks the explicit fallback edge (`src -> dst else`): taken
	// only when no conditional sibling matched. Runtime-wise it plays
	// the same role as a bare unconditional edge among conditional
	// siblings — the compiler validates the stricter contract (C015/
	// C039/C040) and IsConditional stays false (else is guardless).
	IsElse bool

	// Loop reference (optional). LoopName references a Loop in Workflow.Loops.
	LoopName string

	// Foreach reference (optional). ForeachName references a Foreach in
	// Workflow.Foreaches: a (back-)edge that iterates its body over a
	// collection, in order. Mutually exclusive with LoopName.
	ForeachName string

	// Data mappings (optional). Each entry maps a target input field
	// to a resolved reference expression.
	With []*DataMapping
}

Edge represents a directed transition between two nodes, with optional condition, loop reference, and data mappings.

func (*Edge) IsBoundedIteration added in v0.39.0

func (e *Edge) IsBoundedIteration() bool

IsBoundedIteration reports whether an edge is a bounded iteration back-edge — either a named loop (max_iterations) or a foreach (collection-bounded). Such edges are cycles by design and are not default fall-through edges.

func (*Edge) IsConditional

func (e *Edge) IsConditional() bool

IsConditional reports whether an edge carries any predicate (simple boolean field or parsed expression). Used by validators and the runtime to distinguish guarded edges from unconditional fallbacks.

type EmitNode added in v0.39.0

type EmitNode struct {
	BaseNode
	Event string         // event name to publish
	With  []*DataMapping // payload fields (immutable, resolved per visit)
}

EmitNode publishes a named event with an immutable payload into the run-scoped event registry (ADR-051). It performs no LLM call and no shell-out; the payload is resolved from the With data-mappings on each visit.

func (*EmitNode) NodeKind added in v0.39.0

func (n *EmitNode) NodeKind() NodeKind

NodeKind implements Node.

type FailNode

type FailNode struct {
	BaseNode
	AwaitMode AwaitMode // convergence strategy when multiple branches arrive
}

FailNode is a terminal failure node.

func (*FailNode) NodeKind

func (n *FailNode) NodeKind() NodeKind

NodeKind implements Node.

type FieldType

type FieldType = types.FieldType

FieldType enumerates the V1 schema field types.

type Foreach added in v0.39.0

type Foreach struct {
	Name           string
	Item           string // element binding identifier (informational)
	CollectionRaw  string // collection template source, e.g. "{{outputs.list.items}}"
	CollectionRefs []*Ref // pre-parsed refs resolved to a []any at runtime
}

Foreach defines a named sequential iteration over a collection. A back-edge `... as foreach <name>(item in <collection>)` re-enters its body once per element, in order. The runtime advances an index (sharing rs.loopCounters under the foreach name) and exposes the current element via the `each.<name>` namespace ({{each.<name>.item|index|count|first|last}}).

type HumanNode

type HumanNode struct {
	BaseNode
	SchemaFields
	InteractionFields
	Publish       string
	PublishLabels []string // DSL artifact_labels: applied to the published artifact
	MinAnswers    int      // minimum answers required
	Instructions  string   // prompt reference for human instructions
	Model         string   // model for LLM-based interaction modes
	SystemPrompt  string   // prompt reference for LLM-based interaction modes
	AwaitMode     AwaitMode

	// Review-gate fields (interaction: review). The gate runs a
	// companion-driven multi-turn dialogue that walks the human through
	// testing the change, then squash-merges the worktree during the pause.
	ReviewURL     string // raw template (e.g. "{{outputs.provision.url}}") for the review env; resolved at runtime
	ReviewURLRefs []*Ref // parsed refs in ReviewURL (compile-time validation)
	Posture       string // PostureHumanRequired (default) | PostureAgentVerdictOK
	MergeStrategy string // "squash" (default) | "merge"
	MergeInto     string // "current" (default) | "none" | <branch>
	MaxTurns      int    // dialogue asymptote backstop (0 → DefaultReviewMaxTurns)
}

HumanNode is a human pause/resume node.

func (*HumanNode) NodeKind

func (n *HumanNode) NodeKind() NodeKind

NodeKind implements Node.

type InteractionFields

type InteractionFields struct {
	Interaction       InteractionMode // interaction handling mode
	InteractionPrompt string          // prompt reference guiding LLM for llm_or_human decisions
	InteractionModel  string          // model for llm/llm_or_human modes (fallback to Model)
}

InteractionFields groups interaction-related fields.

type InteractionMode

type InteractionMode = types.InteractionMode

InteractionMode controls how a node handles user interaction requests. Available on agent, judge, and human nodes.

func NodeInteraction

func NodeInteraction(n Node) InteractionMode

NodeInteraction returns the Interaction field for nodes that support it, or InteractionNone.

type JudgeNode

type JudgeNode struct {
	BaseNode
	LLMFields
	SchemaFields
	InteractionFields
	MCP              *MCPConfig
	ActiveMCPServers []string
	Publish          string
	Session          SessionMode
	Tools            []string
	ToolPolicy       []string // per-node tool policy patterns (nil = inherit workflow)
	Capabilities     []string // host-side capabilities (e.g. board.read); nil = inherit workflow
	Skills           []string // skill-library references; resolved names mirrored into .claude/skills/ (nil = inherit workflow)
	ToolMaxSteps     int
	AwaitMode        AwaitMode
	Compaction       *Compaction  // per-node compaction overrides (nil = inherit workflow)
	Memory           *Memory      // per-node workspace memory opt-in (nil = disabled)
	Sandbox          *SandboxSpec // node-level sandbox override (nil = inherit workflow)
	Cursors          *CursorInvocation
	Compress         string   // compress output-compression mode: on|ultra|off ("" = inherit)
	Permission       string   // permission gate mode override: off|ask|deny ("" = inherit workflow)
	Needs            []string // resource names this node acquires before running (counting semaphores)
}

JudgeNode is a verdict-producing LLM node (typically no tools).

func (*JudgeNode) GetActiveMCPServers added in v0.39.0

func (n *JudgeNode) GetActiveMCPServers() []string

func (*JudgeNode) GetAwaitMode added in v0.39.0

func (n *JudgeNode) GetAwaitMode() AwaitMode

func (*JudgeNode) GetCapabilities added in v0.39.0

func (n *JudgeNode) GetCapabilities() []string

func (*JudgeNode) GetCompaction added in v0.39.0

func (n *JudgeNode) GetCompaction() *Compaction

func (*JudgeNode) GetCompress added in v0.39.0

func (n *JudgeNode) GetCompress() string

func (*JudgeNode) GetCursors added in v0.39.0

func (n *JudgeNode) GetCursors() *CursorInvocation

func (*JudgeNode) GetInteractionFields added in v0.39.0

func (n *JudgeNode) GetInteractionFields() *InteractionFields

func (*JudgeNode) GetLLMFields added in v0.39.0

func (n *JudgeNode) GetLLMFields() *LLMFields

LLMNode accessor methods on *JudgeNode.

func (*JudgeNode) GetMemory added in v0.39.0

func (n *JudgeNode) GetMemory() *Memory

func (*JudgeNode) GetPermission added in v0.39.0

func (n *JudgeNode) GetPermission() string

func (*JudgeNode) GetPublish added in v0.39.0

func (n *JudgeNode) GetPublish() string

func (*JudgeNode) GetSchemaFields added in v0.39.0

func (n *JudgeNode) GetSchemaFields() *SchemaFields

func (*JudgeNode) GetSession added in v0.39.0

func (n *JudgeNode) GetSession() SessionMode

func (*JudgeNode) GetSkills added in v0.39.0

func (n *JudgeNode) GetSkills() []string

func (*JudgeNode) GetToolMaxSteps added in v0.39.0

func (n *JudgeNode) GetToolMaxSteps() int

func (*JudgeNode) GetTools added in v0.39.0

func (n *JudgeNode) GetTools() []string

func (*JudgeNode) NodeKind

func (n *JudgeNode) NodeKind() NodeKind

NodeKind implements Node.

type LLMFields

type LLMFields struct {
	Model           string   // model identifier (env refs already noted)
	Backend         string   // execution backend name (empty = direct LLM call); may contain ${VAR} env refs
	Provider        string   // credential routing hint(s): single ("anthropic"/"zai"/"openai"/""=auto) or an ordered fallback chain ("anthropic,zai,openai"); may contain ${VAR} env refs
	Command         string   // per-node CLI binary override, honored by claude_code; may contain ${VAR}
	SystemPrompt    string   // prompt reference name
	UserPrompt      string   // prompt reference name
	MaxTokens       int      // per-node cap on output tokens (0 = backend default)
	ReasoningEffort string   // reasoning effort level: "low", "medium", "high", "xhigh", "max"
	Timeout         string   // per-node wall-clock timeout as a Go duration ("20m", "1200s"); empty = no per-node bound; may contain ${VAR} env refs
	Readonly        bool     // when true, node is not considered mutating for workspace safety
	FullAccess      bool     // when true, lift the codex backend sandbox to danger-full-access (network + out-of-workspace writes); off by default; other backends ignore it
	Images          []string // node-level `images:` — input image paths (templated) forwarded to the codex backend as `-i` for image-to-image; other backends ignore it
}

LLMFields groups fields shared by LLM-capable nodes (Agent, Judge, Router-LLM).

type LLMNode added in v0.39.0

type LLMNode interface {
	Node
	GetLLMFields() *LLMFields
	GetSchemaFields() *SchemaFields
	GetInteractionFields() *InteractionFields
	GetAwaitMode() AwaitMode
	GetSession() SessionMode
	GetPublish() string
	GetTools() []string
	GetToolMaxSteps() int
	GetCapabilities() []string
	GetSkills() []string
	GetActiveMCPServers() []string
	GetCompaction() *Compaction
	GetMemory() *Memory
	GetCursors() *CursorInvocation
	GetCompress() string
	GetPermission() string
}

LLMNode is satisfied by *AgentNode and *JudgeNode — the two node kinds that carry the complete LLM field set (LLMFields, SchemaFields, InteractionFields plus tools, capabilities, MCP, memory, compaction, cursors, compress). It lets the field accessors below and the validators in validate*.go / mermaid.go iterate over both uniformly instead of repeating a `case *AgentNode … case *JudgeNode …` ladder at every read site.

RouterNode embeds LLMFields too but deliberately does NOT satisfy LLMNode (it has no Publish/Session/Memory/…); call sites that also handle RouterLLM keep an explicit `case *RouterNode`. HumanNode keeps its own explicit branches as well. Because the methods are declared on *AgentNode / *JudgeNode (not on an embedded carrier), adding LLMNode changes neither the struct layout nor the JSON encoding — the field-literal construction used across the test suite keeps compiling.

type Loop

type Loop struct {
	Name          string
	MaxIterations int
	// MaxIterationsExpr carries the raw template source when the cap
	// was declared as `as <name>("{{outputs.X.cap}}")`. Empty for
	// literal-int caps. Refs are pre-parsed at compile time so the
	// runtime lookup is a pure string interpolation against rs.
	MaxIterationsExpr     string
	MaxIterationsExprRefs []*Ref
	// Unbounded marks `as <name>(unbounded)`: the loop has no user iteration
	// cap. It still terminates — the runtime bounds it by FuelCap (the
	// effective fuel ceiling) and by a liveness monitor (no-progress halt).
	// The cycle is still *declared*, so C019 stays silent. FuelCap is the
	// resolved per-loop fuel: the clause's own fuel, else budget.max_iterations.
	Unbounded bool
	FuelCap   int
	// Body is the set of node IDs that participate in the loop's cycle —
	// each node from which the loop's edge target is reachable and which
	// can reach the loop's edge source (i.e. nodes on a path that closes
	// the iteration). Computed at compile time. The runtime resets the
	// loop's counter when a non-loop edge enters a body node from a
	// non-body source, so the budget becomes per-entry rather than
	// global to the whole run (a fix loop nested inside a package loop
	// gets a fresh budget every package).
	Body map[string]bool
	// Entries is the set of node IDs that serve as the loop's entry
	// point — i.e. the targets of the loop-bearing back-edges. Used by
	// the runtime to scope the counter-reset rule precisely to "we are
	// re-entering the loop at its top", instead of "we are entering
	// any body node from outside the body". The looser rule misfires
	// when the body is computed too narrowly (e.g. a nested loop whose
	// non-loop forward+reverse BFS yields only the back-edge endpoints
	// — see recovery_loop in bots/secured-renovacy/main.bot: the body
	// was {alt_review, review_commit_auto}, so the edge
	// fix_X → review_commit_auto reset the counter every cycle and
	// review_commit_auto's iteration_path stuck at recovery_loop=0).
	Entries map[string]bool
}

Loop defines a named bounded loop. Multiple edges can reference the same loop; the runtime shares a single counter per loop name.

type MCPAuth

type MCPAuth struct {
	// Type is the authentication scheme. The only supported value is
	// "oauth2"; other values produce a C-code diagnostic.
	Type string

	// AuthURL is the OAuth authorization endpoint the user's browser
	// visits to consent.
	AuthURL string

	// TokenURL is the back-channel endpoint that issues access and
	// refresh tokens.
	TokenURL string

	// RevokeURL is the optional RFC 7009 revocation endpoint.
	RevokeURL string

	// ClientID is the OAuth client identifier registered with the
	// provider.
	ClientID string

	// Scopes is the set of OAuth scopes requested at authorization.
	Scopes []string
}

MCPAuth describes how to authenticate against an MCP server. Only the OAuth2 authorization-code + PKCE flow is wired today; `Type` is reserved for future schemes (bearer, mTLS, ...).

type MCPConfig

type MCPConfig struct {
	AutoloadProject *bool
	Inherit         *bool
	Servers         []string
	Disable         []string
}

MCPConfig represents workflow-level or node-level MCP activation/filtering.

type MCPServer

type MCPServer struct {
	Name      string
	Transport MCPTransport
	Command   string
	Args      []string
	URL       string
	Headers   map[string]string
	// Env carries extra environment variables for a stdio server process.
	// The DSL `mcp_server` block has no `env:`; this is populated only for
	// plugin-contributed servers (e.g. firecrawl's FIRECRAWL_API_URL /
	// FIRECRAWL_API_KEY), whose manifest env is resolved at catalog-build
	// time. Without threading it here the self-host routing is lost and the
	// server would fall back to its public API.
	Env  map[string]string
	Auth *MCPAuth
}

MCPServer is a reusable MCP server declaration or resolved catalog entry.

type MCPTransport

type MCPTransport = types.MCPTransport

MCPTransport identifies the transport used by an MCP server.

type Memory added in v0.39.0

type Memory struct {
	Enabled          bool
	Scope            string
	Autoload         []string
	Read             bool
	Write            bool
	PreCompactInject bool
	// ProjectRoot, when true, re-roots the scope under the run's
	// `RepoRoot` (the source-of-truth field stored on the run record)
	// instead of the per-run workDir. Lets a dispatcher-spawned bot
	// running in `<repo>/.iterion/dispatcher/workspaces/<id>` share a
	// scope (e.g. session-continuity memory) with a whats-next run
	// that lives at the repo root.
	ProjectRoot bool
	// Visibility selects the sharing axis (bot | project | cross_project
	// | user | org | global). Empty keeps the legacy per-bot/per-project
	// behaviour; when set, Scope is the space name and the runtime
	// resolves the tenant/user/project identity.
	Visibility string
}

Memory opts a node into the iterion workspace memory tree at ~/.iterion/projects/<encoded-workdir>/memory/<Scope>/. The scope is a feature-bound subfolder (e.g. "session-continuity", "whats-next"). Autoload lists glob patterns relative to the scope whose content is mirrored into the system prompt at node start; default is the scope's INDEX.md only (keeps the LLM index-first, pulling richer files via memory_read on demand).

PreCompactInject re-injects the autoload set before claw's heuristic compaction so its content survives summarisation.

type MermaidView

type MermaidView int

MermaidView controls the level of detail in the generated diagram.

const (
	// MermaidCompact shows nodes with kind icons and simple edge labels.
	MermaidCompact MermaidView = iota
	// MermaidDetailed shows nodes with full metadata and annotated edges.
	MermaidDetailed
	// MermaidFull shows all available metadata including schemas fields,
	// prompts, tools, budget, variables, and loops.
	MermaidFull
)

type Node

type Node interface {
	NodeID() string
	NodeKind() NodeKind
}

Node is the IR node interface. Concrete types: AgentNode, JudgeNode, RouterNode, HumanNode, ToolNode, DoneNode, FailNode.

type NodeKind

type NodeKind int

NodeKind discriminates the type of node.

const (
	NodeAgent        NodeKind = iota // LLM agent
	NodeJudge                        // verdict-producing LLM node
	NodeRouter                       // deterministic routing (no LLM)
	NodeHuman                        // human pause/resume
	NodeTool                         // direct command execution (no LLM)
	NodeCompute                      // deterministic expression evaluation (no LLM, no shell)
	NodeEmit                         // publishes a run-scoped event (no LLM, no shell)
	NodeWait                         // blocks until a run-scoped event (no LLM, no shell)
	NodeAwaitAnswers                 // blocks until pending async human questions are answered (no LLM, no shell)
	NodeSubbot                       // runs another .bot as a nested run
	NodeDone                         // terminal: success
	NodeFail                         // terminal: failure
)

func (NodeKind) String

func (k NodeKind) String() string

type Preset added in v0.39.0

type Preset struct {
	Name string
	// Values are variable overrides applied to the run (defaults < preset <
	// --var). Keys not declared by the workflow's `vars:` are dropped by the
	// engine's resolveVars, same as a stray --var.
	Values map[string]any
	// DisplayName is the operator-facing label (e.g. "Improve Quality (SRE)");
	// falls back to Name when empty. File-based presets only.
	DisplayName string
	// Description is a one-line summary surfaced in the studio Launch picker.
	// File-based presets only.
	Description string
	// Prompt is the bias fragment appended to every LLM node's system prompt
	// under a "## Focus" section at run time (see delegate.Task.PresetFragment).
	// Supports `{{vars.X}}` template refs, resolved per node. File-based only.
	Prompt string
	// Skills lists bundle skill names this preset makes relevant (e.g.
	// "lang-js-fallow"). All bundle skills are mirrored regardless; this list
	// is surfaced as a hint in the "## Focus" section and in the studio.
	// File-based presets only.
	Skills []string
}

Preset is a resolved named "sous-bot": a launch-time specialization of a bot. Values are variable overrides stored with their coerced Go types (string, int64, float64, bool) matching the declared Var's type; the runtime overlays them onto the default vars before applying any `--var` flag. Prompt/Skills/DisplayName/Description are populated only for file-based presets (a bundle's `presets/<name>.md`); in-source `presets:` blocks leave them empty (var-only). Operators select a preset at run time via `--preset <name>` or the studio Launch picker.

type Prompt

type Prompt struct {
	Name         string
	Body         string // raw template text
	TemplateRefs []*Ref // references found in the body
}

Prompt is a resolved prompt declaration. TemplateRefs contains all references extracted from the prompt body.

type RecoverySpec added in v0.39.0

type RecoverySpec struct {
	MaxRepairAttempts int      // rung 3 (self-repair) bound
	MaxAgentAttempts  int      // rung 4 (agent recovery) bound; 0 = OFF
	Model             string   // recovery LLM spec (empty = node/workflow default)
	AgentTools        []string // rung-4 toolset (empty = node capabilities)
}

RecoverySpec is the compiled bound on a Verified Action node's recovery rungs (ADR-044). Rungs only run under Policy == "recover".

type Ref

type Ref struct {
	Kind RefKind
	Path []string // dotted path segments after the namespace
	Raw  string   // original template expression, e.g. "{{outputs.node.field}}"
	// Unquoted indicates the author requested raw substitution by writing
	// `{{!input.X}}` (bang prefix). The runtime substitutes the value
	// verbatim into shell tool commands instead of running it through
	// shellEscape — useful when the substituted value is itself a shell
	// snippet that must be re-interpreted (e.g. a command line emitted
	// upstream by a stack-detection agent). Trades shell-injection
	// containment for re-interpretability; only use on trusted inputs.
	Unquoted bool
}

Ref is a single normalized reference extracted from a template expression. Examples:

{{vars.x}}                → Kind=RefVars, Path=["x"]
{{outputs.node}}          → Kind=RefOutputs, Path=["node"]
{{outputs.node.field}}    → Kind=RefOutputs, Path=["node","field"]
{{input.field}}           → Kind=RefInput, Path=["field"]
{{artifacts.name}}        → Kind=RefArtifacts, Path=["name"]

func ParseRefs

func ParseRefs(s string) ([]*Ref, error)

ParseRefs extracts all {{...}} template references from a string. Returns the parsed Ref values. Returns an error if a template expression is malformed or nests another {{ inside an open block.

Nested templates like "{{outer{{inner}}}}" used to silently match the first `}}` as the closing fence, producing "outer{{inner" as the expression and a confusing "unknown namespace" error. The pre-scan for a nested `{{` now surfaces the real problem.

type RefKind

type RefKind int

RefKind discriminates the namespace of a reference.

const (
	RefVars        RefKind = iota // {{vars.x}}
	RefInput                      // {{input.field}}
	RefOutputs                    // {{outputs.node}} or {{outputs.node.field}}
	RefArtifacts                  // {{artifacts.name}}
	RefAttachments                // {{attachments.name[.path|.url|.mime|.size|.sha256]}}
	RefLoop                       // {{loop.<name>.iteration}} / .max / .previous_output[.field]
	RefRun                        // {{run.id}}
	RefSecrets                    // {{secrets.<name>}} — renders the placeholder; materialised at exec
	RefEach                       // {{each.<name>.item|index|count|first|last}} — sequential foreach binding
)

func (RefKind) String

func (rk RefKind) String() string

type RouterMode

type RouterMode = types.RouterMode

type RouterNode

type RouterNode struct {
	BaseNode
	LLMFields              // only populated for RouterLLM mode
	RouterMode  RouterMode // fan_out_all, condition, round_robin, llm, or fan_out_each
	RouterMulti bool       // LLM router: select multiple targets (default: one)

	// Data-driven fan-out (RouterFanOutEach only). At runtime the engine
	// resolves Over to an array and re-executes the single outgoing
	// template subgraph once per element, binding the element (and its
	// index) onto this router's per-branch output under ItemBinding /
	// "item" / "index" / "count".
	Over        string // raw array-source template, e.g. "{{outputs.decompose.tickets}}"
	OverRefs    []*Ref // parsed refs from Over (resolved at runtime)
	ItemBinding string // per-item binding name (default "item")

	// Optional DAG scheduling (RouterFanOutEach only). When KeyField is set,
	// each item is identified by item[KeyField] and depends on the ids listed
	// in item[DepsField]; the engine schedules branches in topological order,
	// running independent items in parallel (bounded by max_parallel_branches)
	// and holding a dependent until all its deps have finished. Empty deps =>
	// fully parallel (identical to plain fan_out_each); a linear chain => fully
	// sequential. Empty KeyField => no DAG, plain fan-out.
	KeyField  string // item field holding its unique id
	DepsField string // item field holding the array of ids it depends on

	Needs []string // resource names this node acquires before running (counting semaphores)
}

RouterNode is a routing node with 4 modes: fan_out_all, condition, round_robin, llm. LLMFields are only populated when RouterMode == RouterLLM.

func (*RouterNode) NodeKind

func (n *RouterNode) NodeKind() NodeKind

NodeKind implements Node.

type SandboxBuild added in v0.4.0

type SandboxBuild struct {
	Dockerfile string
	Context    string
	Args       map[string]string
}

SandboxBuild describes a Dockerfile-based image build (Phase 2).

type SandboxNetwork added in v0.4.0

type SandboxNetwork struct {
	Mode    string   // "allowlist" | "denylist" | "open" | ""
	Preset  string   // "iterion-default" or named preset
	Rules   []string // glob patterns + "!exclusions"
	Inherit string   // "merge" (default) | "replace" | "append" — node scope only
}

SandboxNetwork is the IR representation of a sandbox.network: block (Phase 3).

type SandboxSpec added in v0.4.0

type SandboxSpec struct {
	// Mode is one of "" (inherit), "none" (explicit opt-out),
	// "auto" (read .devcontainer/devcontainer.json), or "inline"
	// (use the sibling fields). Phase 0 accepts "", "none", and
	// "auto"; "inline" lands in Phase 1.
	Mode string

	// Image is the container image reference. Phase 1.
	Image string

	// Build, when non-nil, asks the driver to build an image at run
	// start. Phase 2.
	Build *SandboxBuild

	// Mounts adds extra bind mounts (devcontainer mount syntax).
	// Phase 2.
	Mounts []string

	// Env is the containerEnv map. Phase 1.
	Env map[string]string

	// User is the devcontainer remoteUser override. Phase 1.
	User string

	// PostCreate is the devcontainer postCreateCommand. Phase 2.
	PostCreate string

	// WorkspaceFolder overrides the in-sandbox workspace path
	// (default `/workspace`). Phase 1.
	WorkspaceFolder string

	// HostState controls auto-binding of the host's `~/.iterion`
	// (run store) and `~/.claude` (Claude Code OAuth + sessions)
	// into the sandbox so persistent memory survives across runs.
	// "" (default → "auto") | "auto" | "none". See pkg/runtime/sandbox.go
	// for the injection logic.
	HostState string

	// Network controls egress filtering. Phase 3.
	Network *SandboxNetwork
}

SandboxSpec is the IR representation of a `sandbox:` block on a workflow or node. It is the bridge between the DSL surface (parsed in pkg/dsl/parser, stored on ast.WorkflowDecl/AgentDecl/...) and the runtime sandbox abstraction in pkg/sandbox.

We mirror pkg/sandbox.Spec but keep the two types distinct so the IR remains free of runtime-level dependencies (drivers, factories, network proxies). pkg/runtime converts an IR SandboxSpec to a pkg/sandbox.Spec at engine start time via [ToSandbox].

Phase 0 only wires the simple `sandbox: <ident>` form (none|auto) — Mode is the only meaningful field. The richer fields (Image, Build, Mounts, Network, ...) are populated by Phase 1+ when the block-form parser ships.

func (*SandboxSpec) IsActive added in v0.4.0

func (s *SandboxSpec) IsActive() bool

IsActive reports whether the spec requests an active sandbox mode (auto or inline). Convenience for diagnostics that only fire when a non-trivial mode is chosen.

type Schema

type Schema struct {
	Name   string
	Fields []*SchemaField
}

Schema is a resolved schema with its fields.

type SchemaField

type SchemaField struct {
	Name       string
	Type       FieldType
	EnumValues []string // non-nil only if enum constraint present
}

SchemaField is a single field in a schema.

type SchemaFields

type SchemaFields struct {
	InputSchema  string // schema reference name (empty if not set)
	OutputSchema string // schema reference name (empty if not set)
}

SchemaFields groups input/output schema references.

type Secret added in v0.39.0

type Secret struct {
	Name        string
	Value       string
	As          string
	MountPath   string
	Env         string
	Optional    bool // file secret: skip the mount (no error) when unresolved
	Hosts       []string
	Description string
}

Secret is a resolved workflow secret declaration. Value is the raw value expression (typically "${ENV}" / a {{vars.X}} reference), resolved to the real plaintext at run start by the runtime; the agent only ever sees either a placeholder (As=value) or the mounted file path (As=file). Hosts scopes which egress destinations the secret may be materialised toward (Layer 2).

func (*Secret) IsFile added in v0.39.0

func (s *Secret) IsFile() bool

type SessionMode

type SessionMode = types.SessionMode

type Severity

type Severity int

Severity indicates the severity of a diagnostic.

const (
	SeverityError Severity = iota
	SeverityWarning
)

func (Severity) String

func (s Severity) String() string

type SubbotNode added in v0.39.0

type SubbotNode struct {
	BaseNode
	Source       string         // path/ref to the child .bot (relative to the parent workdir)
	With         []*DataMapping // vars passed to the child run (key = child var name)
	OutputSchema string         // schema reference describing the child's terminal output
	Needs        []string       // resource names acquired before running the child
	// Isolated asserts the child does NOT mutate the parent's shared workspace,
	// letting the workspace-safety guard fan this subbot out in parallel. Mirror
	// of AgentNode/JudgeNode Readonly. Default false = conservatively mutating.
	Isolated bool
}

SubbotNode runs another .bot as a nested run. The runtime resolves With into the child's input vars, invokes the host-supplied SubbotRunner (which compiles + runs the child in the same store), and maps the child's terminal output to outputs.<subbot>.<field>. The child is a real run, so unlike a fan-out branch it may contain loops.

func (*SubbotNode) NodeKind added in v0.39.0

func (n *SubbotNode) NodeKind() NodeKind

NodeKind implements Node.

type Supervisor added in v0.39.0

type Supervisor struct {
	Name     string
	Watches  []string
	Model    string
	System   string // prompt reference name
	Cooldown time.Duration
	MaxEvals int
}

Supervisor is the normalized IR form of a `supervisor NAME:` declaration. The system prompt is carried as a reference name (resolved against Workflow.Prompts at spawn time, like agent system prompts); Cooldown is the parsed duration (0 = engine default).

type ToolNode

type ToolNode struct {
	BaseNode
	SchemaFields
	Command       string   // command to execute, may contain {{...}} template refs
	CommandRefs   []*Ref   // parsed template references in Command (resolved at runtime)
	Script        string   // script body (interpreter snippet); mutually exclusive with Command
	ScriptRefs    []*Ref   // parsed template references in Script
	Language      string   // interpreter for Script: "js"|"py"|"sh"|"bash" (empty defaults to "sh")
	Publish       string   // persistent artifact name (empty = not published)
	PublishLabels []string // DSL artifact_labels: applied to the published artifact
	Session       SessionMode
	AwaitMode     AwaitMode
	Sandbox       *SandboxSpec // node-level sandbox override (nil = inherit workflow)
	Compress      string       // compress output-compression mode: on|ultra|off ("" = inherit)
	Permission    string       // permission gate mode override: off|ask|deny ("" = inherit workflow)

	// Verified Action quad (ADR-044). All optional; a node with an empty
	// Postcondition runs the recipe with exit-code = success (unchanged).
	Goal          string        // natural-language outcome (fuel for recovery rungs)
	Postcondition string        // cheap deterministic check (shell, exit 0 = met); single source of truth
	PostcondRefs  []*Ref        // parsed template refs in Postcondition (resolved at runtime)
	Policy        string        // "required" | "recover" | "best_effort" (defaulted at compile time)
	Recovery      *RecoverySpec // bounded recovery rung config (nil = no rungs)

	Needs []string // resource names this node acquires before running (counting semaphores)

	// ParallelSafe asserts that concurrent fan-out replays of this tool write
	// only to disjoint, item-keyed targets and never race one another on the
	// shared workspace, letting the workspace-safety guard fan the tool out in
	// parallel (max_parallel_branches > 1). It is scoped to a fan_out_each
	// template — the one place a single node is replayed over items; it has no
	// effect on a static fan_out_all / llm-router (distinct branches, no item
	// key). Unlike a subbot's Isolated, the tool still writes to the shared
	// workspace — it just partitions those writes; unlike an agent/judge
	// Readonly, it is not read-only. Default false = conservatively mutating.
	ParallelSafe bool
}

ToolNode executes a shell command or higher-level script directly (no LLM).

A node carries EITHER Command (raw shell snippet, executed via `sh -c`) OR Script (interpreter snippet, written to a temp file and executed via the interpreter named by Language). Setting both is a compile-time validation error; setting neither is also an error.

func (*ToolNode) GetPermission added in v0.39.0

func (n *ToolNode) GetPermission() string

GetPermission returns the node-level permission gate mode override ("" = inherit workflow). ToolNode does not implement LLMNode, but exposes this accessor for symmetry with AgentNode/JudgeNode.

func (*ToolNode) NodeKind

func (n *ToolNode) NodeKind() NodeKind

NodeKind implements Node.

type Var

type Var struct {
	Name       string
	Type       VarType
	EnumValues []string // non-nil only if enum constraint present (string vars)
	HasDefault bool
	Default    any // string, int64, float64, or bool
}

Var is a resolved workflow variable with its type and optional default.

type VarType

type VarType int

VarType enumerates variable types.

const (
	VarString VarType = iota
	VarBool
	VarInt
	VarFloat
	VarJSON
	VarStringArray
)

func (VarType) AsFieldType added in v0.39.0

func (vt VarType) AsFieldType() (FieldType, bool)

AsFieldType maps a VarType to the equivalent schema FieldType. ok is false for VarJSON (which is "any" — no single field type) so callers can bail to "no opinion" rather than treat it as a concrete type. The two enums are parallel; this is the one canonical mapping between them.

func (VarType) String

func (vt VarType) String() string

type WaitNode added in v0.39.0

type WaitNode struct {
	BaseNode
	SchemaFields               // optional OutputSchema typing the received payload
	Event        string        // event name to wait for
	Timeout      time.Duration // mandatory bound on the wait
}

WaitNode blocks its branch until the named event is emitted in the same run, then completes with the event payload as its output (ADR-051). The Timeout is mandatory (the "no silent infinity" invariant) and bounds the wait.

func (*WaitNode) NodeKind added in v0.39.0

func (n *WaitNode) NodeKind() NodeKind

NodeKind implements Node.

type Workflow

type Workflow struct {
	Name            string
	Entry           string                 // entry node ID
	Nodes           map[string]Node        // node ID → node
	Edges           []*Edge                // ordered list of edges
	Schemas         map[string]*Schema     // schema name → resolved schema
	Prompts         map[string]*Prompt     // prompt name → resolved prompt
	Vars            map[string]*Var        // var name → resolved variable
	Secrets         map[string]*Secret     // secret name → resolved secret declaration
	Presets         map[string]Preset      // preset name → resolved preset values (var name → typed value)
	Attachments     map[string]*Attachment // attachment name → resolved attachment
	Loops           map[string]*Loop       // loop name → loop definition
	Foreaches       map[string]*Foreach    // foreach name → sequential-iteration definition
	Budget          *Budget                // workflow budget (nil if not set)
	Resources       map[string]int         // named counting semaphores (resource name → capacity); nil = none
	ResourceMembers map[string][]string    // resource name → named-instance lease pool (capacity = len); nil = counting-only
	Compaction      *Compaction            // workflow-level compaction overrides (nil = no override)
	MCP             *MCPConfig             // workflow-level MCP activation/filtering
	DefaultBackend  string                 // workflow-level default backend (empty = not set)
	ToolPolicy      []string               // workflow-level tool policy patterns (nil = open)
	Capabilities    []string               // workflow-level default host capabilities (nil = inherit none)
	Skills          []string               // workflow-level default skill-library references (nil = none)
	Interaction     *InteractionMode       // workflow-level default interaction mode (nil = not set)
	Worktree        string                 // "auto" runs in a per-run git worktree; "" or "none" runs in-place
	Compress        string                 // compress output-compression mode: on|ultra|off ("" = unset)
	Permission      string                 // permission gate mode: off|ask|deny ("" = unset → off)
	PermissionAllow []string               // allow rules (Claude-Code `Tool(pattern)` syntax, e.g. "Bash(go test:*)")
	PermissionAsk   []string               // ask rules
	PermissionDeny  []string               // deny rules
	Sandbox         *SandboxSpec           // workflow-level sandbox spec (nil = inherit global / no sandbox)
	// Cursors map of cursor name → resolved definition. Populated from
	// top-level `cursor NAME:` declarations. Agent/judge `cursors:`
	// invocations are resolved against this map at runtime.
	Cursors map[string]*CursorDef
	// Supervisors are top-level `supervisor NAME:` declarations: concurrent
	// node-watchers the engine spawns at run start (not graph nodes). See
	// docs/supervisors.md.
	Supervisors []*Supervisor
	// MCPServers contains the explicit top-level declarations from the .bot file.
	MCPServers map[string]*MCPServer
	// ActiveMCPServers and ResolvedMCPServers are populated after project config
	// resolution, not by the compiler itself.
	ActiveMCPServers   []string
	ResolvedMCPServers map[string]*MCPServer
}

Workflow is the top-level IR unit. It contains everything needed to execute a workflow: resolved nodes, edges, schemas, prompts, vars, loops and budget.

func (*Workflow) ToMermaid

func (w *Workflow) ToMermaid(view MermaidView) string

ToMermaid renders the workflow IR as a Mermaid flowchart string.

Jump to

Keyboard shortcuts

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