Documentation
¶
Overview ¶
ABOUTME: Resolves the on-disk location of the integrity-protected activity log. ABOUTME: Threat model for the relocation lives in CLAUDE.md (#213).
ABOUTME: AgentBackend interface and config types for pluggable execution backends. ABOUTME: Supports native (agent.Session), Claude Code (CLI subprocess), and ACP (Agent Client Protocol) backends.
ABOUTME: Pipeline-level token, cost, and wall-time ceilings enforced between nodes. ABOUTME: Halts execution with OutcomeBudgetExceeded when any configured limit is breached.
ABOUTME: Checkpoint serialization for pipeline execution resume support. ABOUTME: Tracks completed nodes, retry counts, and context state as JSON on disk.
ABOUTME: Thread-safe key-value store shared across all pipeline nodes during execution. ABOUTME: Provides Get/Set/Merge/Snapshot operations and separate internal state for engine bookkeeping. ABOUTME: Supports per-node namespace scoping via ScopeToNode — dirty keys are copied into node.<id>.<key> ABOUTME: after each node completes, preserving individual node outputs without breaking global backward compat.
ABOUTME: Adapter that converts Dippin IR (from dippin-lang parser) to Tracker's Graph model. ABOUTME: Provides FromDippinIR() to enable tracker to execute .dip files natively.
ABOUTME: Implicit edge synthesis for the dippin adapter. ABOUTME: Converts parallel/fan-in IR config into explicit graph edges.
ABOUTME: Loads .dipx bundles produced by dippin v0.24+ — verifies hashes, ABOUTME: converts pre-parsed IR to tracker Graphs, returns content-addressed identity.
Package pipeline implements the core execution engine for multi-agent LLM workflows. Pipelines are directed graphs of nodes (agents, humans, tools, parallel fan-out) connected by conditional edges.
Pipeline Formats ¶
Pipelines can be defined in two formats:
.dip (Dippin format) — the current format, parsed by dippin-lang. Use FromDippinIR to convert parsed IR to a Graph.
.dot (DOT/Graphviz format) — deprecated, will be removed in v1.0. Use ParseDOT for backward compatibility only.
New pipelines should use .dip format exclusively.
ABOUTME: Core pipeline execution engine that traverses graphs, executes handlers, and manages control flow. ABOUTME: Supports edge selection (conditions, labels, weights), retries, goal gates, and checkpoint resume.
ABOUTME: Checkpoint and graph utility methods extracted from engine.go. ABOUTME: Handles checkpoint load/save, downstream clearing, goal gates, and restart limits.
ABOUTME: Edge selection logic extracted from engine.go to reduce function complexity. ABOUTME: Implements priority-based edge routing: condition > label > suggested > weight > lexical.
ABOUTME: Extracted helper methods for Engine.Run to reduce cyclomatic/cognitive complexity. ABOUTME: Handles node preparation, execution, outcome processing, retries, restarts, and checkpoint resume.
ABOUTME: Event types emitted during pipeline execution for UI and logging. ABOUTME: Mirrors the Layer 2 EventHandler pattern with pipeline-specific event types.
ABOUTME: JSONL activity log writer — appends every event as a JSON line to a file. ABOUTME: Captures pipeline, agent, and LLM trace events for a complete audit trail in <runDir>/activity.jsonl.
ABOUTME: Logging event handler that prints pipeline lifecycle events to an io.Writer. ABOUTME: Provides human-readable output for CLI and debugging use cases.
ABOUTME: Variable interpolation for ${namespace.key} syntax in prompts and attributes. ABOUTME: Supports three namespaces: ctx (pipeline context), params (subgraph parameters), graph (graph attributes).
ABOUTME: Fidelity modes control how much prior context gets injected into node prompts. ABOUTME: Provides parsing, degradation chain, resolution from attrs, and context compaction.
ABOUTME: Git-backed artifact tracking for pipeline runs. ABOUTME: Initializes the artifact dir as a git repo and commits after each terminal-outcome node.
ABOUTME: Git environment preflight — runs before any node executes. ABOUTME: Honors workflow `requires:` declarations and the --git= policy flag.
ABOUTME: Core data model for pipeline graphs: Graph, Node, Edge structs. ABOUTME: Provides shape-to-handler mapping and graph traversal helpers.
ABOUTME: Handler interface and registry for pipeline node execution dispatch. ABOUTME: Each node shape maps to a handler; the registry resolves and executes them.
ABOUTME: Tracker-specific lint rules (TRK1XX). Encodes tracker's runtime ABOUTME: defaults — 64KB tool output cap, tail-window capture semantics — ABOUTME: that don't belong in dippin-lang itself but warrant validate-time ABOUTME: warnings because tracker owns the runtime.
ABOUTME: Typed view over Node.Attrs for each handler kind — replaces ad-hoc ABOUTME: map[string]string parsing scattered across handlers and engine code.
ABOUTME: OverrideDetail describes a single validation-override event captured at edge selection. ABOUTME: Actor enum identifies who took the override edge; ErrValidationOverridden is the CLI exit sentinel.
ABOUTME: Parses Graphviz DOT format into the pipeline Graph model. ABOUTME: Uses gographviz for parsing and extracts nodes, edges, and attributes.
ABOUTME: Named retry policies with configurable backoff strategies for pipeline nodes. ABOUTME: Provides resolution logic that checks node attrs, graph attrs, and falls back to defaults.
ABOUTME: CSS-like model stylesheet parser for per-node LLM configuration. ABOUTME: Supports universal (*), class (.name), and ID (#name) selectors with specificity ordering.
ABOUTME: Handler that executes a referenced sub-pipeline as a single node step. ABOUTME: Enables composition of pipelines via the "subgraph" node shape.
ABOUTME: TerminalStatus named string type for EngineResult.Status taxonomy. ABOUTME: Carries IsSuccess() helper used by CLI exit-code, audit, and JSON consumers.
ABOUTME: Structured execution trace recording for pipeline runs. ABOUTME: Captures node execution timing, handler outcomes, edge selections, and errors.
ABOUTME: Variable expansion and context injection for pipeline node attributes. ABOUTME: Expands $goal, graph-level variables, and appends prior node outputs to LLM prompts.
ABOUTME: Validates pipeline graph structure for correctness before execution. ABOUTME: Tracker-specific checks such as shapes and conditional routing always run. ABOUTME: Structural checks that dippin-lang already covers, including duplicate-edge checks, are skipped when DippinValidated=true.
ABOUTME: Semantic validation for pipeline graphs beyond structural checks. ABOUTME: Verifies handler registration, condition syntax, and node attribute types.
Index ¶
- Constants
- Variables
- func ApplyGraphParamOverrides(g *Graph, overrides map[string]string) error
- func AutoFix(g *Graph) []string
- func CompactContext(ctx *PipelineContext, completedNodes []string, fidelity Fidelity, ...) map[string]string
- func CompactContextWithPinnedKeys(ctx *PipelineContext, completedNodes []string, fidelity Fidelity, ...) map[string]string
- func EvaluateCondition(expr string, ctx *PipelineContext) (bool, error)
- func ExpandGraphVariables(text string, vars map[string]string) string
- func ExpandPromptVariables(prompt string, ctx *PipelineContext) string
- func ExpandVariables(text string, ctx *PipelineContext, params map[string]string, ...) (string, error)
- func ExponentialBackoff(attempt int, base time.Duration) time.Duration
- func ExtractDeclaredWrites(writes []string, rawJSON string) (updates map[string]string, extras []string, err error)
- func ExtractJSONFromText(text string) (string, bool)
- func ExtractParamsFromGraphAttrs(graphAttrs map[string]string) map[string]string
- func GitProbeEnv() []string
- func GitSafeEnv() []string
- func GraphParamAttrKey(key string) string
- func GraphVarMap(ctx *PipelineContext) map[string]string
- func HasBornHEAD(ctx context.Context, workDir string) (bool, error)
- func InjectPipelineContext(prompt string, ctx *PipelineContext) string
- func IsToolCommandSafeCtxKey(key string) bool
- func LinearBackoff(attempt int, base time.Duration) time.Duration
- func LintTrackerRules(g *Graph) []string
- func LoadDipxBundle(ctx context.Context, path string) (*Graph, map[string]*Graph, BundleInfo, []validator.Diagnostic, error)
- func ParseDeclaredKeys(attr string) []string
- func ParseSubgraphParams(paramsStr string) map[string]string
- func ParseToolList(csv string) []string
- func Preflight(ctx context.Context, cfg PreflightConfig) error
- func SafetyLatches(ctx context.Context, workDir string) error
- func SaveCheckpoint(cp *Checkpoint, path string) error
- func SecureActivityLogPath(runID string) (string, error)
- func ShapeToHandler(shape string) (string, bool)
- func ValidPreflight(v GitPreflight) bool
- func Validate(g *Graph) error
- func ValidateSemantic(g *Graph, registry *HandlerRegistry) (errors error, warnings []string)
- func ValidateToolLists(allowed, disallowed []string) error
- func WorkdirHasContent(workDir string) (bool, error)
- func WriteStageArtifacts(rootDir, nodeID, prompt, response string, outcome Outcome) error
- func WriteStatusArtifact(rootDir, nodeID string, outcome Outcome) error
- type ACPConfig
- type Actor
- type AgentBackend
- type AgentNodeConfig
- type AgentRunConfig
- type BudgetBreach
- type BudgetBreachKind
- type BudgetGuard
- type BudgetLimits
- type BundleInfo
- type Checkpoint
- func (cp *Checkpoint) ClearCompleted(nodeID string)
- func (cp *Checkpoint) GetEdgeSelection(nodeID string) (string, bool)
- func (cp *Checkpoint) IncrementRetry(nodeID string)
- func (cp *Checkpoint) IsCompleted(nodeID string) bool
- func (cp *Checkpoint) MarkCompleted(nodeID string)
- func (cp *Checkpoint) RetryCount(nodeID string) int
- func (cp *Checkpoint) SetEdgeSelection(nodeID, edgeTo string)
- type ChildRunContext
- type ClaudeCodeConfig
- type ConditionEval
- type CostSnapshot
- type DecisionDetail
- type Edge
- type Engine
- type EngineOption
- func WithArtifactDir(dir string) EngineOption
- func WithBaselineUsage(baseline *UsageSummary) EngineOption
- func WithBudgetGuard(guard *BudgetGuard) EngineOption
- func WithBundleIdentity(id string) EngineOption
- func WithCheckpointPath(path string) EngineOption
- func WithGitArtifacts(enabled bool) EngineOption
- func WithInitialContext(ctx map[string]string) EngineOption
- func WithPipelineEventHandler(h PipelineEventHandler) EngineOption
- func WithSteeringChan(ch <-chan map[string]string) EngineOption
- func WithStylesheetResolution(enabled bool) EngineOption
- type EngineResult
- type Fidelity
- type GitPreflight
- type Graph
- func FromDippinIR(workflow *ir.Workflow) (*Graph, error)
- func InjectParamsIntoGraph(g *Graph, params map[string]string) (*Graph, error)
- func LoadDippinWorkflow(source, filename string) (*Graph, []validator.Diagnostic, error)
- func LoadDippinWorkflowFromIR(workflow *ir.Workflow, filename string) (*Graph, []validator.Diagnostic, error)
- func NewGraph(name string) *Graph
- func ParseDOT(dot string) (*Graph, error)deprecated
- type Handler
- type HandlerRegistry
- type HumanNodeConfig
- type JSONLEventHandler
- func (h *JSONLEventHandler) Close() error
- func (h *JSONLEventHandler) HandlePipelineEvent(evt PipelineEvent)
- func (h *JSONLEventHandler) SetBundleIdentity(id string)
- func (h *JSONLEventHandler) SnapshotErr() error
- func (h *JSONLEventHandler) WriteAgentEvent(...)
- func (h *JSONLEventHandler) WriteBundleMismatchForced(runID, originalIdentity, currentIdentity string)
- func (h *JSONLEventHandler) WriteLLMEvent(kind, provider, model, toolName, preview string)
- type LoggingEventHandler
- type MCPServerConfig
- type MarkerDetail
- type Node
- type Outcome
- type OverrideDetail
- type ParallelNodeConfig
- type PermissionMode
- type PipelineContext
- func (c *PipelineContext) ClearDirty()
- func (c *PipelineContext) DiffFrom(baseline map[string]string) map[string]string
- func (c *PipelineContext) Get(key string) (string, bool)
- func (c *PipelineContext) GetInternal(key string) (string, bool)
- func (c *PipelineContext) GetScoped(nodeID, key string) (string, bool)
- func (c *PipelineContext) Merge(updates map[string]string)
- func (c *PipelineContext) MergeWithoutDirty(updates map[string]string)
- func (c *PipelineContext) ScopeToNode(nodeID string)
- func (c *PipelineContext) Set(key, value string)
- func (c *PipelineContext) SetInternal(key, value string)
- func (c *PipelineContext) Snapshot() map[string]string
- type PipelineEvent
- type PipelineEventHandler
- type PipelineEventHandlerFunc
- type PipelineEventType
- type PreflightConfig
- type ProviderUsage
- type RegistryFactory
- type RetryConfig
- type RetryPolicy
- type RouteDetail
- type SessionStats
- type StyleRule
- type Stylesheet
- type SubgraphHandler
- type TerminalStatus
- type ToolNodeConfig
- type Trace
- type TraceEntry
- type TruncationDetail
- type UsageSummary
- type ValidationError
Constants ¶
const ( ContextKeyOutcome = "outcome" ContextKeyPreferredLabel = "preferred_label" ContextKeyGoal = "graph.goal" ContextKeyLastResponse = "last_response" ContextKeyHumanResponse = "human_response" ContextKeyToolStdout = "tool_stdout" ContextKeyToolStderr = "tool_stderr" // ContextKeyToolMarker holds the routing marker extracted from a tool // node's stdout via the marker_grep attr (#210). The value is the last // regex match in stdout (capture group 1 if the regex has groups, full // match otherwise). LLM-origin (the tool subprocess emitted it), so // NOT in the tool_command safe-key allowlist; conditions can read it, // tool_command interpolation cannot. ContextKeyToolMarker = "tool_marker" // ContextKeyToolMarkerError carries the regex-compile error message // when marker_grep is configured with an invalid pattern. The runtime // owns this key; declared writes cannot collide with it (same model // as writes_error / writes_warning). ContextKeyToolMarkerError = "tool_marker_error" // ContextKeyToolRoute is the convention-based routing channel // (issue #212): the tool handler scans captured stdout for lines // matching `^\s*_TRACKER_ROUTE=(.+?)\s*$` and populates this key // with the LAST match's captured value (anchored, so an arbitrary // "_TRACKER_ROUTE" substring in the middle of another line does // not match). The complement to marker_grep (#210): same routing // channel idea, but author opts in by emitting the sentinel line // instead of declaring a regex on the node. LLM-origin (the // subprocess emitted the value), so NOT in the tool_command // safe-key allowlist and reserved from declared writes. ContextKeyToolRoute = "tool_route" ContextKeySuggestedNextNodes = "suggested_next_nodes" // ContextKeyResponsePrefix is prepended to a node ID to form a per-node // response key (e.g. "response.mynode"). Downstream nodes can reference // specific upstream outputs without relying on last_response being current. ContextKeyResponsePrefix = "response." // ContextKeyTurnLimitMsg holds a diagnostic message when an agent exhausts // its turn limit or enters a tool call loop. Present only in failure outcomes // from turn-limit exhaustion; absent on normal success. ContextKeyTurnLimitMsg = "turn_limit_msg" // ContextKeyEpisodeSummary stores the most recent codergen session's episode // summary (tool attempts + outcomes). ContextKeyEpisodeSummary = "episode_summary" // ContextKeyEpisodeSummaries stores the running list of prior episode // summaries encoded as a JSON array of strings. ContextKeyEpisodeSummaries = "episode_summaries" // Interview mode context keys. Overridable via questions_key/answers_key // node attributes in .dip files. These are the defaults when the attrs // are not specified. ContextKeyInterviewQuestions = "interview_questions" ContextKeyInterviewAnswers = "interview_answers" // ContextKeyNodePrefix is the prefix for per-node scoped context keys. // After each node completes, ScopeToNode copies dirty keys into this namespace // so downstream nodes can read e.g. "node.MyAgent.last_response" without // colliding with the global "last_response" written by later nodes. ContextKeyNodePrefix = "node." )
Built-in context keys used by the engine and handlers.
const ActivityLogSentinel = "\x1f\x1e"
ActivityLogSentinel is the two-byte prefix the runtime writes ahead of every JSONL line in the secure activity log. The bytes are ASCII Unit Separator (0x1F) and Record Separator (0x1E) — control characters that a normal text-mode subprocess effectively never emits. Their presence is the runtime's "I wrote this" mark; their absence on a line in the secure file is the injection signal.
This is detection, not authentication: the bytes are not secret and an attacker who reads tracker's source (open source) can emit them too. See the "Activity log integrity" section of CLAUDE.md for the threat model and the intentional scope of this defense.
const DefaultTruncateLimit = 500
DefaultTruncateLimit is the maximum character length for context values in truncate fidelity mode. Truncation is character-based (not token-based).
const EdgePriorityOverride = "override"
EdgePriorityOverride identifies an edge selected at advanceToNextNode that carries Edge.Override == true. Emitted as the EdgePriority on the EventDecisionEdge event for the override-edge selection. EventValidationOverridden rides alongside (not instead of) the DecisionEdge event for these traversals.
Untyped string constant to match the existing edge_priority values ("condition", "label", "suggested", "weight", "lexical") which are inlined as bare string literals at the call sites in engine_edges.go.
const (
InternalKeyArtifactDir = "_artifact_dir"
)
Internal context keys used by the engine for bookkeeping.
Variables ¶
var ( ErrNilWorkflow = errors.New("nil workflow") ErrMissingStart = errors.New("workflow missing Start node") ErrMissingExit = errors.New("workflow missing Exit node") ErrUnknownNodeKind = errors.New("unknown node kind") ErrUnknownConfig = errors.New("unknown config type") ErrInvalidSteerContextKey = errors.New("steer_context key contains ':' which breaks block-form round-trip through the .dip formatter") ErrMissingManagerLoopCfg = errors.New("manager_loop node is missing required ir.ManagerLoopConfig") // ErrParenthesizedParsedCondition is returned by convertEdge when a Parsed-only // ir.Condition formats to an expression containing parentheses. The pipeline // edge evaluator (pipeline/condition.go) does not support parens — it tokenizes // on plain strings.Split("||") and strings.Split("&&"), so `a || (b && c)` // would become tokens like `(b` and `c)`. In EvaluateCondition, those are not // hard runtime errors: they are treated as unknown variable names, a warning is // logged, and they evaluate as empty strings, which can silently produce false // or otherwise incorrect results. The adapter rejects these expressions up // front to avoid that mis-evaluation. Authors should populate Condition.Raw // with a flat form (e.g. `a=1 || b=2 || c=3`) or simplify the Parsed tree so // no parens are emitted. ErrParenthesizedParsedCondition = errors.New("formatted Parsed condition uses parentheses, which the pipeline edge evaluator does not support") )
var ( // ErrGitNotInstalled — git missing from PATH and the workflow requires it. ErrGitNotInstalled = errors.New("git not installed") // ErrGitWorkdirNotRepo — workdir is not inside a git repository and the workflow requires it. ErrGitWorkdirNotRepo = errors.New("workdir is not a git repository") // ErrGitUnbornHEAD — workdir is inside a git work tree but HEAD is unborn // (no commits yet). Distinct from ErrGitWorkdirNotRepo: `git rev-parse // --is-inside-work-tree` returns true here, but `git worktree add ... // HEAD`, `git log`, `git merge` all fail until at least one commit // exists. Surfaced at preflight so requires:git workflows fail fast // instead of mid-run after burning LLM turns. ErrGitUnbornHEAD = errors.New("workdir is a git repository with no commits (unborn HEAD)") // ErrGitAutoInitRefused — --git=init requested but a safety latch fired (home, root, nested). ErrGitAutoInitRefused = errors.New("auto-init refused by safety latch") )
var ErrValidationOverridden = errors.New("run completed via validation_overridden")
ErrValidationOverridden is the sentinel error returned by interpretRunResult when --fail-on-override is set and the run terminated as validation_overridden. The cobra entry checks errors.Is(err, ErrValidationOverridden) and exits with code 2 (distinct from generic-fail exit 1).
Functions ¶
func ApplyGraphParamOverrides ¶
ApplyGraphParamOverrides applies runtime overrides to graph-level params. Each override key must already exist in graph attrs as "params.<key>".
func AutoFix ¶
AutoFix applies automatic corrections to a graph and returns descriptions of each fix applied. Currently fixes conditional nodes missing fail edges by adding a self-referencing retry edge.
func CompactContext ¶
func CompactContext(ctx *PipelineContext, completedNodes []string, fidelity Fidelity, artifactDir string, runID string) map[string]string
CompactContext reads the checkpoint context and optionally artifact files from disk, returning a compacted version based on the fidelity level.
func CompactContextWithPinnedKeys ¶
func CompactContextWithPinnedKeys(ctx *PipelineContext, completedNodes []string, fidelity Fidelity, artifactDir string, runID string, pinnedKeys []string) map[string]string
CompactContextWithPinnedKeys is like CompactContext but keeps the provided keys in medium/truncate modes in addition to the built-in medium key set.
func EvaluateCondition ¶
func EvaluateCondition(expr string, ctx *PipelineContext) (bool, error)
EvaluateCondition evaluates a condition expression against the pipeline context. Empty or whitespace-only conditions always return true. Parsing priority: || (lowest) then && (higher) then individual clauses.
func ExpandGraphVariables ¶
ExpandGraphVariables substitutes $key references in text with values from graph-level attributes. The vars map should come from GraphVarMap. For example, graph[target_name="foo"] expands $target_name to "foo". This applies to any node attribute (prompt, tool_command, etc.) so all handlers get uniform variable expansion.
func ExpandPromptVariables ¶
func ExpandPromptVariables(prompt string, ctx *PipelineContext) string
func ExpandVariables ¶
func ExpandVariables( text string, ctx *PipelineContext, params map[string]string, graphAttrs map[string]string, strict bool, toolCommandMode ...bool, ) (string, error)
ExpandVariables replaces ${namespace.key} patterns with values from the provided sources. Supports three namespaces:
- ctx: runtime context (from PipelineContext)
- params: subgraph parameters (passed explicitly)
- graph: graph-level attributes (from Graph.Attrs)
In lenient mode (strict=false), undefined variables expand to empty string. In strict mode (strict=true), undefined variables return an error.
When toolCommandMode is true (optional variadic parameter), only allowlisted ctx.* keys can be expanded — all others return an error to prevent LLM output injection into shell commands.
Examples:
${ctx.human_response} → value from PipelineContext
${params.model} → value from subgraph params
${graph.goal} → value from graph attributes
func ExponentialBackoff ¶
ExponentialBackoff returns 2^attempt * base with ±25% jitter, capped at 60s.
func ExtractDeclaredWrites ¶
func ExtractDeclaredWrites(writes []string, rawJSON string) (updates map[string]string, extras []string, err error)
ExtractDeclaredWrites parses rawJSON as a top-level JSON object and extracts each declared key as a context update value.
String fields are written as plain strings. Non-string fields are written as compact JSON text (for example arrays, objects, numbers, booleans, null).
func ExtractJSONFromText ¶
ExtractJSONFromText tries to find a valid JSON OBJECT embedded in text. It iterates ```...``` fenced blocks first (returning the first whose content parses as a JSON object — so a non-JSON fence ahead of a valid JSON fence doesn't block discovery), then falls back to scanning for top-level {…} spans (preferring the first balanced span that parses, not the outermost-brace shortcut, which fails when prose contains stray brace pairs around real JSON).
Returns the extracted JSON string and true if a valid JSON object was found, or ("", false) otherwise.
Intended for use by pipeline handlers; the Go-package boundary requires it to be exported, but it isn't part of the stable embedder API.
func ExtractParamsFromGraphAttrs ¶
ExtractParamsFromGraphAttrs returns params declared in graph attrs under the "params." prefix.
func GitProbeEnv ¶
func GitProbeEnv() []string
GitProbeEnv returns the safe env with locale forced to C so any stderr we parse (`not a git repository`, `is-inside-work-tree`) is stable regardless of the operator's LANG/LC_* settings. Use this for the preflight + doctor probes that classify git output; the artifact repo's user-visible operations stay on GitSafeEnv so commits/branches still respect the user's locale for messages they actually see.
func GitSafeEnv ¶
func GitSafeEnv() []string
GitSafeEnv returns a copy of the current environment with sensitive variables (API keys, secrets, tokens, passwords) stripped before passing to a git subprocess. Exported so external callers (tracker doctor's probeGitForDoctor) can match the runtime preflight's sanitized-environment posture without duplicating the helper. Honors the TRACKER_PASS_ENV=1 escape hatch.
func GraphParamAttrKey ¶
GraphParamAttrKey returns the graph.Attrs key used to store workflow param `key` (i.e. "params."+key). Callers should use this helper rather than hard-coding the prefix so the shape stays consistent if it ever changes.
func GraphVarMap ¶
func GraphVarMap(ctx *PipelineContext) map[string]string
GraphVarMap extracts graph-level variables from the pipeline context as a $key → value map. Call once per node and pass the result to ExpandGraphVariables to avoid repeated Snapshot() copies.
func HasBornHEAD ¶
HasBornHEAD reports whether HEAD in workDir points at a real commit. Thin public alias of hasBornHEAD; exported so the doctor's git-requires preview can model the same born-HEAD check the runtime preflight does. Returns (false, nil) for an unborn repo — error is reserved for unexpected I/O.
func InjectPipelineContext ¶
func InjectPipelineContext(prompt string, ctx *PipelineContext) string
InjectPipelineContext appends relevant pipeline context values to the prompt so the LLM can see prior node outputs, human responses, etc.
func IsToolCommandSafeCtxKey ¶
IsToolCommandSafeCtxKey reports whether key is on the tool_command safe-key allowlist. The allowlist exists so that LLM-origin ctx.* keys cannot be expanded into shell input. Workflow authors that declare a `writes:` key colliding with this list would funnel LLM output into a reserved name and bypass the sanitization gate; declared-writes processing uses this to reject such collisions before they're written.
func LinearBackoff ¶
LinearBackoff returns (attempt+1) * base with ±25% jitter, capped at 60s. Like ExponentialBackoff, attempt is 0-indexed: attempt 0 = 1*base.
func LintTrackerRules ¶
LintTrackerRules runs tracker-specific lint rules (TRK1XX). DIP1XX lint (DIP101–DIP137, etc.) is owned by dippin-lang and runs at .dip load time via LoadDippinWorkflow → validator.Lint; tracker does not duplicate it, so this is the only lint entry point tracker should expose.
func LoadDipxBundle ¶
func LoadDipxBundle(ctx context.Context, path string) (*Graph, map[string]*Graph, BundleInfo, []validator.Diagnostic, error)
LoadDipxBundle opens a .dipx file, verifies all SHA-256 hashes via dipx.Open (strict mode), converts the entry workflow and every transitively- referenced subgraph from pre-parsed IR to tracker Graphs, and returns the graphs plus a BundleInfo carrying the bundle's content-addressed identity.
The subgraphs map is keyed by canonical bundle path (matching manifest.Files entries). dipx has already verified ref closure and acyclicity, so no recursive walk is needed on tracker's side.
After IR-to-Graph conversion, every subgraph_ref attr on every loaded graph is rewritten from the author's source ref (e.g., "sub.dip") to the canonical bundle path (e.g., "workflows/sub.dip") so refs match the subgraphs map keys.
Diagnostics from the bundled IR (lint warnings; DIP126 "subgraph ref does not exist" is filtered since dipx already verified ref closure) are returned for the caller to log. The library deliberately does not write to os.Stderr here so embedded callers can route output through their own logger; the CLI wrapper prints them to stderr, mirroring the .dip path.
func ParseDeclaredKeys ¶
ParseDeclaredKeys splits a comma-separated attr value into trimmed keys.
func ParseSubgraphParams ¶
ParseSubgraphParams parses a comma-separated string of key=value pairs into a map. Format: "key1=val1,key2=val2" Returns an empty map if the input is empty.
func ParseToolList ¶
ParseToolList splits a comma-separated tool list, trimming whitespace.
func Preflight ¶
func Preflight(ctx context.Context, cfg PreflightConfig) error
Preflight runs the dependency checks declared by the workflow header against the environment, honoring the resolved policy. Returns nil on pass / bypass / downgraded-to-warning. Returns a typed error on hard fail.
Safe to call multiple times — only side effect is the optional `git init` triggered by --git=init.
func SafetyLatches ¶
SafetyLatches returns a wrapped ErrGitAutoInitRefused when auto-init would be unsafe at workDir, or nil if it would proceed. Exported so the tracker doctor preview check can model --git=init --allow-init behavior without duplicating the latch logic. Doctor callers can pass context.Background() or a real ctx; cancellation aborts the git subprocess.
Refusal cases — see safetyLatches for the full list. This is a thin public alias.
func SaveCheckpoint ¶
func SaveCheckpoint(cp *Checkpoint, path string) error
SaveCheckpoint writes the checkpoint to disk as JSON, creating directories as needed.
func SecureActivityLogPath ¶
SecureActivityLogPath returns the absolute path where the runtime writes the integrity-protected activity log for runID. The path is outside any directory a tool subprocess sees as cmd.Dir, so the most common LLM-tool-mistake attack vectors (relative-path shell redirection, find-in-cwd globs) cannot reach it.
Resolution order:
- $TRACKER_AUDIT_DIR/<runID>/activity.jsonl — explicit operator override.
- $XDG_STATE_HOME/tracker/runs/<runID>/activity.jsonl — when XDG_STATE_HOME is set.
- $HOME/.local/state/tracker/runs/<runID>/activity.jsonl — Linux + macOS default.
- %LOCALAPPDATA%\tracker\runs\<runID>\activity.jsonl — Windows fallback.
- os.TempDir()/tracker-audit/<runID>/activity.jsonl — last-resort when no $HOME (containers, restricted envs).
runID must be a single clean path element — no separators, no "..", no ".". This guards against tampered checkpoints that could try to escape the secure base via path traversal (the resume path reads runID from <workDir>/.tracker/runs/<runID>/checkpoint.json, which is attacker-reachable).
$TRACKER_AUDIT_DIR and $XDG_STATE_HOME must be absolute paths. A relative value would re-anchor the "secure" path to the process CWD — defeating the relocation entirely — so a relative env value is silently ignored and the resolver falls through to the next candidate.
func ShapeToHandler ¶
ShapeToHandler returns the handler name for a DOT node shape. Returns ("", false) if the shape is not recognized.
func ValidPreflight ¶
func ValidPreflight(v GitPreflight) bool
ValidPreflight reports whether v is a recognized policy value. The empty string is valid and resolves to auto.
func Validate ¶
Validate checks a parsed Graph for structural correctness. Returns nil if the graph has no errors. Warning-only results return nil so that callers treating non-nil as fatal do not block valid graphs. Use ValidateAll to retrieve both errors and warnings.
func ValidateSemantic ¶
func ValidateSemantic(g *Graph, registry *HandlerRegistry) (errors error, warnings []string)
ValidateSemantic checks a Graph for semantic correctness against a handler registry. It always runs tracker-runtime-specific checks that dippin-lang cannot perform — handler registration (tracker owns the registry) and edge condition syntax (tracker owns the runtime evaluator dialect) — plus tracker-specific lint rules (TRK1XX).
Typed node-attribute checks (max_retries, cache_tool_results, context_compaction, context_compaction_threshold) are dippin's domain: these are typed fields on *ir.Workflow, validated by dippin's parser and lint (e.g. DIP116 for compaction_threshold range). They run here only when the graph did NOT come from a .dip source (DOT inputs and programmatically-constructed graphs in library callers and tests) — otherwise tracker would duplicate dippin's work and risk diverging from `dippin doctor`. DIP1XX lint flows separately through Graph.LintWarnings populated at load time.
func ValidateToolLists ¶
ValidateToolLists returns an error if both allowed and disallowed are set.
func WorkdirHasContent ¶
WorkdirHasContent reports whether workDir contains any entry other than `.git`. Thin public alias of workdirHasContent; exported so the doctor's --git=init --allow-init preview can model the auto-init workdir-content latch without duplicating the rule.
func WriteStageArtifacts ¶
func WriteStatusArtifact ¶
Types ¶
type ACPConfig ¶
type ACPConfig struct {
Agent string // explicit agent binary: "claude-agent-acp", "codex-acp", "gemini" (overrides provider mapping)
}
ACPConfig holds ACP-backend-specific settings.
type Actor ¶
type Actor string
Actor identifies who took a validation-override edge. Stored on OverrideDetail.Actor. Defined as a named string type so JSON marshals as the bare string and the constant set is grep-able.
const ( ActorHuman Actor = "human" // human-driven interviewer (TUI or non-TUI console) ActorAutopilot Actor = "autopilot" // any autopilot variant (LLM-backed or deterministic auto-approve) ActorWebhook Actor = "webhook" // external callback via WebhookInterviewer ActorUnknown Actor = "unknown" // third-party or future Interviewer with no recognized Actor() value )
type AgentBackend ¶
type AgentBackend interface {
Run(ctx context.Context, cfg AgentRunConfig, emit func(agent.Event)) (agent.SessionResult, error)
}
AgentBackend executes an agent session and streams events.
type AgentNodeConfig ¶
type AgentNodeConfig struct {
Backend string
WorkingDir string
McpServers string // raw JSON; parsed by the handler
AllowedTools string
DisallowedTools string
MaxBudgetUSD float64
PermissionMode string
ACPAgent string
// ToolAccess restricts the agent's tool surface. When non-empty (any
// value), the runtime registers zero tools, sets ToolChoice=none on
// LLM requests, scrubs tool-naming text from the system prompt, and
// rejects Params bypass keys. Canonical: case-insensitive, whitespace-
// trimmed. Fail-closed for typos. See agent.SessionConfig.ToolAccess.
// Issue: github.com/2389-research/tracker#258.
ToolAccess string
AutoStatus bool
ReflectOnError bool // initialized to true by AgentConfig; explicit "false" disables
ReflectOnErrorSet bool // true when the attr was present on the node
VerifyAfterEdit bool
VerifyAfterEditSet bool
VerifyCommand string
MaxVerifyRetries int
PlanBeforeExecute bool
PlanBeforeExecuteSet bool
Model string
Provider string
SystemPrompt string
MaxTurns int
CommandTimeout time.Duration
ReasoningEffort string
ResponseFormat string
ResponseSchema string
// WritablePaths bounds the file paths this agent's tools may write,
// as author-chosen globs resolved against the session root. Non-empty
// triggers the runtime fs-jail (Linux Landlock for Bash subprocess +
// openat2 for in-process Write/Edit/ApplyPatch). Distinguishing absent
// from present-but-empty requires WritablePathsSet — the configureJail
// gate refuses-to-start when Set && len == 0 so a malformed/whitespace
// attr can never silently degrade to unbounded. See issue #272.
WritablePaths []string
// WritablePathsSet records whether the writable_paths attr was present
// on the node. Distinguishes "absent" (Set=false, jail disabled) from
// "present but parses to no entries" (Set=true, fail-CLOSED at the
// codergen configureJail gate per issue #272). Mirrors the three-state
// pattern of ReflectOnErrorSet et al. above.
WritablePathsSet bool
CacheToolResults bool
CacheToolResultsSet bool
// ContextCompaction carries the raw attr value. "auto" enables automatic
// compaction; any other non-empty value (e.g. "none") disables it; ""
// means the attr was absent. Kept as string so future modes can be added
// without a type change.
ContextCompaction string
ContextCompactionSet bool
CompactionThreshold float64
}
AgentNodeConfig is a typed view over a codergen (agent) node's attributes.
For the subset of attrs that support graph-level defaults (llm_model, llm_provider, reasoning_effort, verify_after_edit, verify_command, max_verify_retries, plan_before_execute, cache_tool_results, context_compaction), AgentConfig resolves the value from graphAttrs first, then lets node.Attrs override when the same key is set on the node. The remaining fields are node-only and have no graph fallback.
Unless documented otherwise on the specific field, fields absent from their applicable source use the Go zero value. ReflectOnError is the one exception: it defaults to true in the returned struct even when the attr is absent, matching the runtime default. The companion *Set bool on three- state booleans distinguishes "explicitly configured" from "absent" so consumers that want to treat absence as "leave the existing value" can.
type AgentRunConfig ¶
type AgentRunConfig struct {
Prompt string
SystemPrompt string
Model string
Provider string
WorkingDir string
MaxTurns int
Timeout time.Duration
Extra any // backend-specific: *ClaudeCodeConfig for claude-code backend
// ToolAccess restricts the agent's tool surface. When non-empty, every
// backend must enforce: native via agent.SessionConfig.IsToolAccessRestricted;
// claude-code via the DisallowedTools list set by applyClaudeCodeToolAccess;
// ACP refuses session creation (no verified deny-equivalent — see
// backend_acp.go for the refusal site). Issue: github.com/2389-research/tracker#258.
ToolAccess string
}
AgentRunConfig carries common config all backends need.
type BudgetBreach ¶
type BudgetBreach struct {
Kind BudgetBreachKind
Message string
}
BudgetBreach describes the outcome of a guard check.
type BudgetBreachKind ¶
type BudgetBreachKind int
BudgetBreachKind classifies which limit was hit.
const ( BudgetOK BudgetBreachKind = iota BudgetTokens BudgetCost BudgetWallTime )
func (BudgetBreachKind) String ¶
func (k BudgetBreachKind) String() string
String returns a human-readable label for a breach kind. Used as the halt reason in EngineResult.BudgetLimitsHit.
type BudgetGuard ¶
type BudgetGuard struct {
// contains filtered or unexported fields
}
BudgetGuard evaluates BudgetLimits against a UsageSummary snapshot and a run start time. The zero value is not usable; construct via NewBudgetGuard.
func NewBudgetGuard ¶
func NewBudgetGuard(limits BudgetLimits) *BudgetGuard
NewBudgetGuard constructs a BudgetGuard with the given limits. Returns nil when limits.IsZero() so callers can use the nil-guard pattern to skip checks when no limits are configured.
func (*BudgetGuard) Check ¶
func (g *BudgetGuard) Check(usage *UsageSummary, started time.Time) BudgetBreach
Check reports whether the given usage snapshot breaches any configured limit. A nil guard and a nil usage are both safe and return BudgetOK. Token and cost ceilings are inclusive — the exact limit is not a breach.
type BudgetLimits ¶
BudgetLimits configures hard ceilings for a pipeline run. A zero-value field means "no limit" for that dimension.
func (BudgetLimits) IsZero ¶
func (l BudgetLimits) IsZero() bool
IsZero reports whether every limit is unset.
type BundleInfo ¶
BundleInfo carries the metadata extracted from a loaded .dipx bundle. Identity is the canonical "sha256:<64 hex>" form of the bundle's content-addressed hash (SHA-256 of manifest.json bytes-as-stored). EntryPath is the canonical bundle-relative path of the entry workflow.
type Checkpoint ¶
type Checkpoint struct {
RunID string `json:"run_id"`
CurrentNode string `json:"current_node"`
CompletedNodes []string `json:"completed_nodes"`
RetryCounts map[string]int `json:"retry_counts"`
Context map[string]string `json:"context"`
Timestamp time.Time `json:"timestamp"`
RestartCount int `json:"restart_count"`
// EdgeSelections stores the selected outgoing edge target for each
// completed node (nodeID -> selected edge To). Used on resume to
// replay routing decisions instead of re-evaluating stale conditions.
EdgeSelections map[string]string `json:"edge_selections,omitempty"`
// FallbackTaken tracks which goal-gate nodes have already used their
// one-shot fallback/escalation route. Persisted in checkpoint JSON so
// the guard survives checkpoint save/restore cycles.
FallbackTaken map[string]bool `json:"fallback_taken,omitempty"`
// BundleIdentity is the content-addressed identity of the .dipx bundle
// the run was started against ("sha256:<hex>"). Empty for runs started
// from a plain .dip file. Used for strict resume verification.
BundleIdentity string `json:"bundle_identity,omitempty"`
// ValidationOverrides persists the override sticky list across resume and
// bundle export. Appended at the flip-point in advanceToNextNode whenever
// an override edge is traversed; never cleared by clearDownstream or
// handleLoopRestart. omitempty for backwards compat with pre-v0.35
// checkpoints (absent = "no overrides happened").
ValidationOverrides []OverrideDetail `json:"validation_overrides,omitempty"`
// contains filtered or unexported fields
}
Checkpoint captures the execution state of a pipeline run for resume support.
func LoadCheckpoint ¶
func LoadCheckpoint(path string) (*Checkpoint, error)
LoadCheckpoint reads a checkpoint from a JSON file on disk.
func (*Checkpoint) ClearCompleted ¶
func (cp *Checkpoint) ClearCompleted(nodeID string)
ClearCompleted removes a node from the completed set so it will re-execute.
func (*Checkpoint) GetEdgeSelection ¶
func (cp *Checkpoint) GetEdgeSelection(nodeID string) (string, bool)
GetEdgeSelection returns the stored edge selection for a node, if any.
func (*Checkpoint) IncrementRetry ¶
func (cp *Checkpoint) IncrementRetry(nodeID string)
IncrementRetry increments the retry counter for the given node by one.
func (*Checkpoint) IsCompleted ¶
func (cp *Checkpoint) IsCompleted(nodeID string) bool
IsCompleted returns true if the given node ID has been marked as completed.
func (*Checkpoint) MarkCompleted ¶
func (cp *Checkpoint) MarkCompleted(nodeID string)
MarkCompleted adds the given node ID to the completed nodes list. Duplicate IDs are ignored.
func (*Checkpoint) RetryCount ¶
func (cp *Checkpoint) RetryCount(nodeID string) int
RetryCount returns the number of retries recorded for the given node. Returns 0 if the node has no retry history or if the map is nil.
func (*Checkpoint) SetEdgeSelection ¶
func (cp *Checkpoint) SetEdgeSelection(nodeID, edgeTo string)
SetEdgeSelection records the selected outgoing edge for a completed node.
type ChildRunContext ¶
type ChildRunContext struct {
// BudgetGuard is the parent engine's budget guard. Child runs should
// pass it via WithBudgetGuard so the same limits enforce within the
// child. Nil when the parent has no budget configured.
BudgetGuard *BudgetGuard
// Baseline is an immutable snapshot of the parent's aggregated usage
// at the moment the child was launched. Child runs should pass it via
// WithBaselineUsage so the child's budget check folds baseline + its
// own trace aggregate before comparing to limits. Without this, a
// nested budget check would only see child-local spend and the
// effective ceiling inside a subgraph would grow by the parent's
// already-consumed amount.
Baseline *UsageSummary
}
ChildRunContext is the execution context a handler may need when it launches a child run (subgraph, manager_loop). Carries the parent engine's BudgetGuard and a snapshot of usage already consumed so the child can enforce limits combined with the parent's running total. Retrieved via ChildRunContextFromContext.
func ChildRunContextFromContext ¶
func ChildRunContextFromContext(ctx context.Context) *ChildRunContext
ChildRunContextFromContext returns the ChildRunContext stashed on ctx by the engine before dispatching a handler, or nil when no such value exists (top-level contexts outside a running engine).
type ClaudeCodeConfig ¶
type ClaudeCodeConfig struct {
MCPServers []MCPServerConfig
AllowedTools []string
DisallowedTools []string
MaxBudgetUSD float64
PermissionMode PermissionMode
}
ClaudeCodeConfig holds Claude-Code-specific settings.
type ConditionEval ¶
ConditionEval records a single conditional edge whose condition evaluated false during routing. Used as the payload of EventConditionalFallthrough so diagnostics can show which routing intents missed before the fallback fired.
type CostSnapshot ¶
type CostSnapshot struct {
TotalTokens int
TotalCostUSD float64
ProviderTotals map[string]ProviderUsage
WallElapsed time.Duration
Estimated bool
}
CostSnapshot is the payload for EventCostUpdated and EventBudgetExceeded events. It is a point-in-time view of the run's aggregate token usage, cost, and wall-clock elapsed time. Estimated is true when any session contributing to this snapshot was heuristic-derived (e.g. ACP rune-count estimator); per-provider detail is carried inside ProviderTotals via ProviderUsage.Estimated.
type DecisionDetail ¶
type DecisionDetail struct {
// Edge selection fields.
EdgeFrom string `json:"edge_from,omitempty"`
EdgeTo string `json:"edge_to,omitempty"`
EdgePriority string `json:"edge_priority,omitempty"` // "condition", "label", "suggested", "weight", "lexical", "override"
// Condition evaluation fields.
EdgeCondition string `json:"edge_condition,omitempty"`
ConditionMatch bool `json:"condition_match,omitempty"`
// Node outcome fields.
OutcomeStatus string `json:"outcome_status,omitempty"`
ContextUpdates map[string]string `json:"context_updates,omitempty"`
// Context snapshot at the decision point (routing-relevant keys).
ContextSnapshot map[string]string `json:"context_snapshot,omitempty"`
// Restart/loop fields.
RestartCount int `json:"restart_count,omitempty"`
ClearedNodes []string `json:"cleared_nodes,omitempty"`
// Session stats from handler outcome.
TokenInput int `json:"token_input,omitempty"`
TokenOutput int `json:"token_output,omitempty"`
// ConditionsTried lists the conditional edges that were evaluated
// false on the way to a fallback selection. Populated only on
// EventConditionalFallthrough events. Each entry pairs the target
// node with the literal condition string so a diagnostics consumer
// can render "your routing intents X, Y, Z all missed" without
// re-evaluating expressions.
ConditionsTried []ConditionEval `json:"conditions_tried,omitempty"`
}
DecisionDetail carries structured data about a pipeline decision point. It is attached to PipelineEvent via the Decision field for audit trail events.
type Edge ¶
type Edge struct {
From string
To string
Label string
Condition string
// Override marks the edge as a validation-override path. When the engine
// traverses an override edge from a wait.human gate, the run's terminal
// EngineResult.Status becomes OutcomeValidationOverridden (audit-only;
// routing is unaffected). Valid only on edges from wait.human-handler
// nodes; enforced by the adapter at graph construction time.
Override bool
Attrs map[string]string
}
Edge represents a directed connection between two nodes.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine executes a pipeline graph by traversing nodes, dispatching handlers, selecting edges, and managing retries and checkpoints.
func NewEngine ¶
func NewEngine(graph *Graph, registry *HandlerRegistry, opts ...EngineOption) *Engine
NewEngine creates a pipeline engine for the given graph and handler registry.
type EngineOption ¶
type EngineOption func(*Engine)
EngineOption configures optional Engine behavior.
func WithArtifactDir ¶
func WithArtifactDir(dir string) EngineOption
WithArtifactDir sets the base directory for pipeline run artifacts. Node artifacts are written to <artifactDir>/<nodeID>/ instead of the working directory.
func WithBaselineUsage ¶
func WithBaselineUsage(baseline *UsageSummary) EngineOption
WithBaselineUsage pre-loads the engine's BudgetGuard with usage already consumed by a parent run. Used by subgraph execution so the child's guard check sees parent spend + child trace combined, preventing the "subgraph sandbox" escape where an operator's --max-tokens / --max-cost ceiling would otherwise be silently non-binding for nodes nested in a subgraph. Nil baselines are no-ops.
func WithBudgetGuard ¶
func WithBudgetGuard(guard *BudgetGuard) EngineOption
WithBudgetGuard attaches a BudgetGuard evaluated after every terminal node outcome. Nil guards are no-ops.
func WithBundleIdentity ¶
func WithBundleIdentity(id string) EngineOption
WithBundleIdentity stamps every PipelineEvent the engine emits with the given content-addressed identity string (typically "sha256:<hex>"). Used to thread .dipx bundle identity into the activity log so every line of activity.jsonl carries provenance. Empty string (the default) is a no-op and matches the behavior for plain .dip runs.
func WithCheckpointPath ¶
func WithCheckpointPath(path string) EngineOption
WithCheckpointPath enables checkpoint save/resume at the given file path.
func WithGitArtifacts ¶
func WithGitArtifacts(enabled bool) EngineOption
WithGitArtifacts enables git-backed artifact tracking. When enabled, the artifact dir is initialized as a git repo at run start, and each terminal node outcome produces one commit capturing the artifact state at that point. Checkpoint saves made via saveCheckpointWithTag (not all saveCheckpoint call sites) also create a lightweight git tag of the form checkpoint/<runID>/<nodeID> pointing at the most recent node-outcome commit, intended as the basis for future checkpoint-replay support (Layer 2 of issue #77 — not wired up by this option).
Requires git in PATH. Silently no-ops if artifactDir is not set.
func WithInitialContext ¶
func WithInitialContext(ctx map[string]string) EngineOption
WithInitialContext pre-populates the pipeline context with the given values. Used by subgraph execution to pass parent context into child pipelines.
func WithPipelineEventHandler ¶
func WithPipelineEventHandler(h PipelineEventHandler) EngineOption
WithPipelineEventHandler sets the event handler for pipeline lifecycle events.
func WithSteeringChan ¶
func WithSteeringChan(ch <-chan map[string]string) EngineOption
WithSteeringChan provides a channel for injecting context updates into the pipeline between node executions. The engine drains pending updates after each node's outcome is applied, making steered values visible to edge selection and the next node's prompt expansion. Nil channels are no-ops.
func WithStylesheetResolution ¶
func WithStylesheetResolution(enabled bool) EngineOption
WithStylesheetResolution enables model stylesheet resolution on nodes before execution.
type EngineResult ¶
type EngineResult struct {
RunID string
Status TerminalStatus
CompletedNodes []string
Context map[string]string
Trace *Trace
Usage *UsageSummary
BudgetLimitsHit []string // populated when a BudgetGuard halted the run
// ValidationOverrides is the list of override edges traversed during this
// run, in chronological order. Populated for every terminal path (success,
// fail, budget, validation_overridden) so failure-after-override forensics
// still see the override. Empty for runs with no override edges.
//
// The terminal-status rule writes Status=OutcomeValidationOverridden when
// len(ValidationOverrides) > 0 AND the run reached the success exit;
// failure paths return fail/budget regardless of override presence.
ValidationOverrides []OverrideDetail
}
EngineResult holds the final outcome of a pipeline execution run.
type Fidelity ¶
type Fidelity string
Fidelity represents a context fidelity level for pipeline execution.
const ( // FidelityFull includes complete context from checkpoint (default for first execution). FidelityFull Fidelity = "full" // FidelitySummaryHigh includes all context keys plus trimmed artifact responses. FidelitySummaryHigh Fidelity = "summary:high" // FidelitySummaryMedium includes key decisions only: outcome, last_response, human_response. FidelitySummaryMedium Fidelity = "summary:medium" // FidelitySummaryLow includes one-line per completed node. FidelitySummaryLow Fidelity = "summary:low" // FidelityCompact includes only current task context, no prior node history. FidelityCompact Fidelity = "compact" // FidelityTruncate drops oldest context entries to fit a configurable token budget. FidelityTruncate Fidelity = "truncate" )
func DegradeFidelity ¶
DegradeFidelity returns the next lower fidelity level. Truncate is the floor and degrades to itself.
func ParseFidelity ¶
ParseFidelity parses a string into a Fidelity value, returning an error for unrecognized values.
type GitPreflight ¶
type GitPreflight string
GitPreflight is the resolved preflight policy passed to Preflight. The empty string ("") resolves to "auto".
const ( GitPreflightAuto GitPreflight = "" GitPreflightOff GitPreflight = "off" GitPreflightWarn GitPreflight = "warn" GitPreflightRequire GitPreflight = "require" GitPreflightInit GitPreflight = "init" )
type Graph ¶
type Graph struct {
Name string
Nodes map[string]*Node
Edges []*Edge
Attrs map[string]string
StartNode string
ExitNode string
// NodeOrder preserves the declaration order of nodes from the source file.
// Used by the TUI to display nodes in a sensible order (declaration order)
// rather than BFS order which puts "Done" in the middle.
NodeOrder []string
// DippinValidated is set to true when the graph was produced from a .dip
// source that has already passed dippin-lang's structural validator
// (DIP001–DIP009). Tracker's own validateGraph skips checks that overlap
// with those diagnostics, preventing false positives and divergence between
// `dippin doctor` and `tracker validate`.
//
// CONTRACT: this flag reflects the graph's state at the moment dippin
// validation ran. If the graph is subsequently mutated (nodes or edges
// added/removed programmatically), callers MUST clear this flag or
// re-run dippin validation before calling Validate again — otherwise
// tracker's structural checks will be skipped for a shape that has
// changed since dippin last validated it.
DippinValidated bool
// LintWarnings carries pre-formatted warning lines from dippin-lang's
// lint pass (DIP1XX). Populated by LoadDippinWorkflowFromIR for .dip /
// .dipx sources and empty for DOT graphs. tracker.ValidateAll surfaces
// these alongside its own structural warnings so callers (validate /
// simulate / doctor) see a single warnings list without re-running any
// DIP-coded check on the tracker side. Format matches tracker's
// single-line convention ("warning[DIPxxx]: ...") to render cleanly
// inside bulleted "Validation Warnings" output.
LintWarnings []string
// contains filtered or unexported fields
}
Graph represents a parsed pipeline as a directed graph.
func FromDippinIR ¶
FromDippinIR converts a Dippin IR Workflow to a Tracker Graph. The resulting Graph is semantically equivalent to one produced by ParseDOT for the same workflow, enabling transparent interoperability.
Field mappings:
- IR Workflow.Name → Graph.Name
- IR Workflow.Start → Graph.StartNode
- IR Workflow.Exit → Graph.ExitNode
- IR Workflow.Defaults → Graph.Attrs (flattened)
- IR Node → Graph.Node (with kind → shape mapping)
- IR Edge → Graph.Edge (with condition serialization)
Returns an error if:
- workflow is nil
- Start or Exit are empty
- A node has an unknown NodeKind
func InjectParamsIntoGraph ¶
InjectParamsIntoGraph creates a new graph with variable expansion applied to all node attributes. This is used by the subgraph handler to inject params before execution.
func LoadDippinWorkflow ¶
func LoadDippinWorkflow(source, filename string) (*Graph, []validator.Diagnostic, error)
LoadDippinWorkflow parses a dippin-lang source, then delegates to LoadDippinWorkflowFromIR for validation, lint, and conversion to a Graph. This ensures all .dip entry points apply consistent validation semantics.
filename is used for error messages (e.g., "inline.dip" or "/path/to/file.dip"). Returns the graph and any validation/lint diagnostics (warnings only). Validation errors are returned as fatal errors.
func LoadDippinWorkflowFromIR ¶
func LoadDippinWorkflowFromIR(workflow *ir.Workflow, filename string) (*Graph, []validator.Diagnostic, error)
LoadDippinWorkflowFromIR runs dippin's structural validator (DIP001–DIP009) and linter (DIP101–DIP115) on an already-parsed IR workflow, then converts it to tracker's Graph representation and marks it dippin-validated.
Diagnostics returned cover both validate and lint passes; validation errors are fatal, lint warnings are non-fatal. This function exists separately from LoadDippinWorkflow so that callers which already hold a parsed *ir.Workflow (e.g., the .dipx bundle loader, which gets one back from dipx.Open) can reuse the validate/lint/convert tail without re-parsing source.
func (*Graph) AddEdge ¶
AddEdge adds a directed edge to the graph. No referential integrity check is performed; use Validate to enforce that endpoints exist.
func (*Graph) AddNode ¶
AddNode adds a node to the graph and resolves its handler from its shape. If the node has an Mdiamond shape, it is set as the start node. If the node has an Msquare shape, it is set as the exit node. Duplicate node IDs silently replace the previous node; use Validate to enforce uniqueness.
func (*Graph) IncomingEdges ¶
IncomingEdges returns all edges terminating at the given node ID. Returns a copy to prevent callers from mutating internal state.
func (*Graph) OutgoingEdges ¶
OutgoingEdges returns all edges originating from the given node ID. Returns a copy to prevent callers from mutating internal state.
func (*Graph) RequiredDeps ¶
RequiredDeps returns the parsed comma-separated list from g.Attrs["requires"]. Whitespace around each entry is trimmed; empty entries are dropped; duplicates are removed in declaration order. Returns nil for empty/missing attrs.
The "requires" attr is populated by the dippin adapter from the workflow header's `requires:` field (dippin-lang v0.26.0+). The adapter's extractRequires also deduplicates, so RequiredDeps is defensive: a caller that synthesizes a Graph directly (or reads pre-v0.29.0 attrs) still gets a clean list. pipeline.Preflight consumes this list and emits one warning per unrecognized dep — without dedup, duplicates would surface duplicate warnings.
type Handler ¶
type Handler interface {
Name() string
Execute(ctx context.Context, node *Node, pctx *PipelineContext) (Outcome, error)
}
Handler defines the interface for pipeline node execution. Each handler has a unique name and an Execute method that processes a node within a pipeline context.
type HandlerRegistry ¶
type HandlerRegistry struct {
// contains filtered or unexported fields
}
HandlerRegistry stores named handlers and dispatches execution to the appropriate handler based on a node's Handler field.
func NewHandlerRegistry ¶
func NewHandlerRegistry() *HandlerRegistry
NewHandlerRegistry creates an empty handler registry.
func (*HandlerRegistry) Execute ¶
func (r *HandlerRegistry) Execute(ctx context.Context, node *Node, pctx *PipelineContext) (Outcome, error)
Execute looks up the handler for the given node and delegates execution to it. Returns an error if no handler is registered for the node's Handler field.
func (*HandlerRegistry) Get ¶
func (r *HandlerRegistry) Get(name string) Handler
Get returns the handler registered under the given name, or nil if not found.
func (*HandlerRegistry) Has ¶
func (r *HandlerRegistry) Has(name string) bool
Has reports whether a handler with the given name is registered.
func (*HandlerRegistry) Register ¶
func (r *HandlerRegistry) Register(h Handler)
Register adds a handler to the registry, keyed by its Name(). If a handler with the same name already exists, it is overwritten.
type HumanNodeConfig ¶
type HumanNodeConfig struct {
Mode string // "" or "choice" (default — presents outgoing edge labels), "yes_no", "interview", "freeform"
DefaultChoice string // "default_choice" attr, falling back to "default"
Prompt string
QuestionsKey string
AnswersKey string
Timeout time.Duration
TimeoutAction string // "fail", "default", or "" (unset — treated as "default")
// Writes carries the raw "writes:" attr; consumers that need the parsed
// key list should still call pipeline.ParseDeclaredKeys.
Writes string
}
HumanNodeConfig is a typed view over a wait.human node's attributes. DefaultChoice resolves "default_choice" with fallback to "default" so callers don't have to check two keys.
type JSONLEventHandler ¶
type JSONLEventHandler struct {
// contains filtered or unexported fields
}
JSONLEventHandler appends every pipeline event as a JSON line to a file. The runtime writes to an integrity-protected path outside any directory a tool subprocess sees as cmd.Dir (see SecureActivityLogPath); every line is prefixed with ActivityLogSentinel so post-hoc readers can flag injection. At Close() a sentinel-stripped snapshot is copied to the legacy path under artifactDir so bundle export (#213) and any pre-existing tooling that reads <runDir>/activity.jsonl still works.
artifactDir is retained on the handler solely as the destination for that snapshot — live writes during the run never go to artifactDir. Safe for concurrent use from multiple goroutines.
func NewJSONLEventHandler ¶
func NewJSONLEventHandler(artifactDir string) *JSONLEventHandler
NewJSONLEventHandler creates a JSONL event logger. The live log lands at the SecureActivityLogPath for the run's runID; on Close a stripped snapshot is written to <artifactDir>/<runID>/activity.jsonl. The file is opened lazily on first event so callers that never feed events produce no on-disk footprint.
func (*JSONLEventHandler) Close ¶
func (h *JSONLEventHandler) Close() error
Close flushes the secure activity log, writes a sentinel-stripped snapshot to <artifactDir>/<runID>/activity.jsonl for bundle/export consumers, and closes the underlying file. The snapshot is best-effort: snapshot errors don't break Close (the secure file is the authoritative record) but they're stashed on h.snapshotErr so callers that care about bundle/export coverage can inspect them via SnapshotErr().
func (*JSONLEventHandler) HandlePipelineEvent ¶
func (h *JSONLEventHandler) HandlePipelineEvent(evt PipelineEvent)
HandlePipelineEvent implements PipelineEventHandler.
func (*JSONLEventHandler) SetBundleIdentity ¶
func (h *JSONLEventHandler) SetBundleIdentity(id string)
SetBundleIdentity sets the .dipx bundle identity ("sha256:<hex>") that will be stamped onto subsequent WriteAgentEvent and WriteLLMEvent writes. Empty (the default) is a no-op so plain .dip runs see no change. Called once at run-start after the handler is constructed.
Note: events that flow through HandlePipelineEvent already get stamped at the engine and registry levels; this setter only affects the JSONL writes that bypass those chokepoints (agent and llm events).
func (*JSONLEventHandler) SnapshotErr ¶
func (h *JSONLEventHandler) SnapshotErr() error
SnapshotErr returns the error (if any) from the most recent Close-time snapshot mirror to the legacy run-dir path. Callers that depend on the legacy snapshot for bundle export or external tooling can check this after Close. Nil when the snapshot succeeded or was skipped (no artifactDir / no events). The secure file remains authoritative regardless of this value.
func (*JSONLEventHandler) WriteAgentEvent ¶
func (h *JSONLEventHandler) WriteAgentEvent(evtType, nodeID, toolName, toolOutput, toolError, text, errMsg, provider, model string)
WriteAgentEvent logs an agent event to the activity log. The caller is responsible for passing the event; the handler writes it to the same JSONL file as pipeline events. The nodeID identifies which pipeline node (branch) produced this event.
func (*JSONLEventHandler) WriteBundleMismatchForced ¶
func (h *JSONLEventHandler) WriteBundleMismatchForced(runID, originalIdentity, currentIdentity string)
WriteBundleMismatchForced records a forced bundle-identity override on resume. Called once at run-start (before the engine fires any pipeline events) when --force-bundle-mismatch allowed resume despite a mismatch between the checkpoint's stored identity and the current bundle's identity. Both identities are preserved in the log entry so post-hoc auditors can see what was overridden.
The entry's bundle_identity field is stamped with the CURRENT identity (what the run actually executes against), so post-hoc scans grouping activity.jsonl lines by bundle see this override clustered with the rest of the run.
runID is needed to open the activity log file lazily — this is the first event the handler ever writes, so the file isn't open yet (HandlePipelineEvent's lazy openFile hasn't run). Pass the resume run ID here.
func (*JSONLEventHandler) WriteLLMEvent ¶
func (h *JSONLEventHandler) WriteLLMEvent(kind, provider, model, toolName, preview string)
WriteLLMEvent logs an LLM trace event to the activity log.
type LoggingEventHandler ¶
LoggingEventHandler writes pipeline events to a Writer in a human-readable format. Batches rapid-fire "previously completed" events from checkpoint resume into a single summary line so the console isn't flooded on resume.
func (*LoggingEventHandler) HandlePipelineEvent ¶
func (h *LoggingEventHandler) HandlePipelineEvent(evt PipelineEvent)
HandlePipelineEvent formats and writes a pipeline event to the configured Writer.
type MCPServerConfig ¶
MCPServerConfig defines an MCP server to attach to a session.
func ParseMCPServers ¶
func ParseMCPServers(raw string) ([]MCPServerConfig, error)
ParseMCPServers parses the mcp_servers attr format: one server per line, name=command arg1 arg2. Splits on first = only.
type MarkerDetail ¶
type MarkerDetail struct {
Pattern string `json:"pattern"`
CapturedTail string `json:"captured_tail,omitempty"`
Error string `json:"error,omitempty"`
}
MarkerDetail is the payload for EventToolMarkerMissing. Pattern is the raw regex declared on the node's marker_grep attribute; CapturedTail is up to 256 bytes from the end of the captured stdout for diagnostic context (the operator needs to see what didn't match without the audit log carrying arbitrarily large tool output). Error is non-empty when the failure was a regex-compile error rather than a missing-match — the node fails either way, but the surface in `tracker diagnose` differs (broken pipeline definition vs. unexpected tool output).
type Node ¶
Node represents a single step in the pipeline.
func (*Node) AgentConfig ¶
func (n *Node) AgentConfig(graphAttrs map[string]string) AgentNodeConfig
AgentConfig returns the typed agent config for the node, merging graphAttrs defaults with node.Attrs overrides. Graph-level values apply to all agent nodes unless a node explicitly overrides the same attr. Unparseable numeric strings fall back to the zero value (matching the previous permissive behavior of the handler apply* methods).
func (*Node) HumanConfig ¶
func (n *Node) HumanConfig() HumanNodeConfig
HumanConfig returns the typed human-gate config for the node.
func (*Node) ParallelConfig ¶
func (n *Node) ParallelConfig() ParallelNodeConfig
ParallelConfig returns the typed parallel/fan-in config for the node.
func (*Node) RetryConfig ¶
func (n *Node) RetryConfig(graphAttrs map[string]string) RetryConfig
RetryConfig returns the typed retry config parsed from node and graph attributes. Node-level values win over graph-level defaults for each field independently. Unparseable node values fall through to the graph default rather than silently dropping the whole field — matching the previous cascading behavior in Engine.maxRetries.
func (*Node) ToolConfig ¶
func (n *Node) ToolConfig() ToolNodeConfig
ToolConfig returns the typed tool config for the node.
type Outcome ¶
type Outcome struct {
Status string
ContextUpdates map[string]string
PreferredLabel string
SuggestedNextNodes []string
Stats *SessionStats // optional, populated by codergen handler
// ChildUsage is the aggregated usage of a child run that executed under
// this node (subgraph, manager_loop). When non-nil, Trace.AggregateUsage
// folds it into totals and per-provider rollups so the parent trace
// reflects spend that happened inside the child. Required for
// BudgetGuard enforcement to see child spend once control returns to
// the parent.
ChildUsage *UsageSummary
// Truncations records output streams that exceeded their per-stream
// cap during this node's execution. The engine emits one
// EventToolOutputTruncated per entry so `tracker diagnose` and the
// audit log can correlate routing misses with truncation (issue #208).
// Currently populated only by the tool handler.
Truncations []TruncationDetail
// MissingMarker records a marker_grep regex that produced no match
// (issue #210). When non-nil the engine emits EventToolMarkerMissing
// before the engine's normal post-node accounting; the handler also
// sets Status = OutcomeFail so routing does not silently fall through
// to an unconditional edge. Populated only by the tool handler.
MissingMarker *MarkerDetail
// MissingRoute records the absence of a _TRACKER_ROUTE= sentinel
// in captured stdout when the node had route_required: true
// (#212). Same flow as MissingMarker: handler sets Status = Fail,
// engine emits EventToolRouteMissing. Sentinel extraction itself
// runs unconditionally; this field is populated only when the
// missing-sentinel + route_required combination triggers a fail.
MissingRoute *RouteDetail
// OverrideActor is the Actor classification of the interviewer that produced
// this outcome. Populated by HumanHandler from its bound interviewer's
// Actor() method (via actorOf helper). The engine reads this at edge-selection
// time when an override edge is traversed, to populate OverrideDetail.Actor.
// Empty for non-wait.human handlers (they cannot originate override edges).
OverrideActor Actor
// ChildOverride carries OverrideDetail entries propagated up from a child
// run (subgraph, manager_loop, or parallel branch with a subgraph child).
// The engine's applyOutcome path appends these to the parent's
// runState.validationOverrides after the handler returns.
ChildOverride []OverrideDetail
}
Outcome represents the result of executing a handler on a pipeline node.
type OverrideDetail ¶
type OverrideDetail struct {
// GateNodeID is the source node of the override edge (the wait.human gate).
GateNodeID string `json:"gate_node_id"`
// Label is the edge label of the override edge ("accept", "mark done", etc.).
// Empty when the override edge has no label.
Label string `json:"label,omitempty"`
// Actor identifies who took the override edge.
Actor Actor `json:"actor"`
// SubgraphPath is populated when this override was propagated from a child
// run via Outcome.ChildOverride. Outermost-to-innermost subgraph node IDs;
// the leaf gate node ID lives in GateNodeID, not in SubgraphPath. Empty for
// overrides taken in the run's own graph.
SubgraphPath []string `json:"subgraph_path,omitempty"`
// Timestamp is the moment the override edge was traversed. In the JSONL wire
// format, the enclosing event line carries its own timestamp; this field is
// primarily for Checkpoint persistence where there is no enclosing timestamp.
Timestamp time.Time `json:"timestamp"`
}
OverrideDetail describes a single validation-override event: the gate that produced it, the label that selected the override edge, who acted, and the subgraph path (if propagated from a child run). Persisted on Checkpoint.ValidationOverrides and EngineResult.ValidationOverrides; emitted on PipelineEvent.Override when an override edge is traversed.
func PrependSubgraphPath ¶
func PrependSubgraphPath(in []OverrideDetail, parentNodeID string) []OverrideDetail
PrependSubgraphPath returns a copy of in with parentNodeID prepended to each entry's SubgraphPath. Used by subgraph and manager_loop handlers to lift child ValidationOverrides into parent-visible OverrideDetails with outermost- to-innermost ordering: at depth N each level prepends its own subgraph node ID, so by the time control returns to the outermost run the path enumerates the nesting chain top-down (leaf gate node ID stays on GateNodeID, never in SubgraphPath).
The input is not mutated. Each output entry has a fresh SubgraphPath slice so callers may safely retain or mutate either side independently. Returns nil for nil/empty input so callers can rely on `if len(out) > 0` checks downstream.
type ParallelNodeConfig ¶
type ParallelNodeConfig struct {
ParallelTargets string
FanInSources string
JoinID string
MaxConcurrency int
BranchTimeout time.Duration
}
ParallelNodeConfig is a typed view over a parallel node's attributes. ParallelTargets is the comma-separated list of branch target node IDs; the handler still splits and trims. FanInSources mirrors the same for a fan-in node that collects results from multiple upstream branches. JoinID is the fan-in node that branches should reconverge on. MaxConcurrency and BranchTimeout cap concurrent branches and per-branch wall time; zero means unlimited / no timeout.
type PermissionMode ¶
type PermissionMode string
PermissionMode controls Claude Code's tool approval behavior.
const ( PermissionPlan PermissionMode = "plan" PermissionAcceptEdits PermissionMode = "acceptEdits" PermissionBypassPermissions PermissionMode = "bypassPermissions" PermissionDefault PermissionMode = "default" PermissionDontAsk PermissionMode = "dontAsk" PermissionAuto PermissionMode = "auto" )
func (PermissionMode) Valid ¶
func (m PermissionMode) Valid() bool
Valid returns true if the permission mode is a recognized Claude Code value. Empty string is not valid — callers should default to PermissionBypassPermissions before validation (see buildClaudeCodeConfig).
type PipelineContext ¶
type PipelineContext struct {
// contains filtered or unexported fields
}
PipelineContext is a thread-safe key-value store shared across all pipeline nodes during execution. It has two namespaces: user-visible values and internal engine bookkeeping (retry counters, loop state).
Per-node scoping: every key written via Set or Merge is recorded in a dirty set. After a node's handler completes, the engine calls ScopeToNode(nodeID) to copy those dirty keys into "node.<nodeID>.<key>" entries. The dirty set is then cleared, ready for the next node. Bare keys continue to be overwritten globally (last-writer-wins), preserving full backward compatibility.
func NewPipelineContext ¶
func NewPipelineContext() *PipelineContext
NewPipelineContext creates an empty pipeline context.
func NewPipelineContextFrom ¶
func NewPipelineContextFrom(values map[string]string) *PipelineContext
NewPipelineContextFrom creates a PipelineContext pre-populated with the given values. Used by the parallel handler to give each branch an isolated snapshot of the shared context.
Preloaded values are written directly without marking them dirty, so the first ScopeToNode call after construction only scopes keys that were written after construction — not the entire baseline snapshot.
func (*PipelineContext) ClearDirty ¶
func (c *PipelineContext) ClearDirty()
ClearDirty resets the dirty set without scoping any keys. Call this after all bootstrap writes (graph attrs, initial context, checkpoint restore) are done and before the main engine loop starts, so that baseline values are not copied into the first node's scoped namespace.
func (*PipelineContext) DiffFrom ¶
func (c *PipelineContext) DiffFrom(baseline map[string]string) map[string]string
DiffFrom returns all keys in the current context whose values differ from the given baseline snapshot, including keys that exist in the context but not in baseline. Keys present in baseline but absent here are not reported (PipelineContext has no delete operation).
func (*PipelineContext) Get ¶
func (c *PipelineContext) Get(key string) (string, bool)
Get retrieves a value from the user-visible context.
func (*PipelineContext) GetInternal ¶
func (c *PipelineContext) GetInternal(key string) (string, bool)
GetInternal retrieves a value from the internal engine namespace.
func (*PipelineContext) GetScoped ¶
func (c *PipelineContext) GetScoped(nodeID, key string) (string, bool)
GetScoped retrieves the value of key from the per-node namespace for nodeID. It is equivalent to Get("node.<nodeID>.<key>") but more readable at call sites. Returns ("", false) if the scoped key has not been written.
func (*PipelineContext) Merge ¶
func (c *PipelineContext) Merge(updates map[string]string)
Merge applies all key-value pairs from updates into the user-visible context and marks each key as dirty so it will be included in the next ScopeToNode call.
func (*PipelineContext) MergeWithoutDirty ¶
func (c *PipelineContext) MergeWithoutDirty(updates map[string]string)
MergeWithoutDirty applies updates to the user-visible context without marking keys as dirty. Used for externally-injected values (e.g. steering channel updates from a supervisor) that should be globally visible but not attributed to any node's scoped namespace.
func (*PipelineContext) ScopeToNode ¶
func (c *PipelineContext) ScopeToNode(nodeID string)
ScopeToNode copies all dirty (recently-written) keys into the per-node namespace "node.<nodeID>.<key>" and then clears the dirty set. This lets downstream nodes read a specific upstream node's output — for example "node.MyAgent.last_response" — without being affected by later writes to the bare "last_response" key. The bare keys are NOT removed; they retain their last-writer-wins global semantics for backward compatibility.
Keys that already start with ContextKeyNodePrefix (e.g. "node.X.foo") are skipped — scoping them would create confusing doubly-nested keys like "node.<id>.node.X.foo". The engine passes the node's graph ID directly.
func (*PipelineContext) Set ¶
func (c *PipelineContext) Set(key, value string)
Set stores a value in the user-visible context and marks the key as dirty so it will be included in the next ScopeToNode call.
func (*PipelineContext) SetInternal ¶
func (c *PipelineContext) SetInternal(key, value string)
SetInternal stores a value in the internal engine namespace.
func (*PipelineContext) Snapshot ¶
func (c *PipelineContext) Snapshot() map[string]string
Snapshot returns a shallow copy of the user-visible context values.
type PipelineEvent ¶
type PipelineEvent struct {
Type PipelineEventType
Timestamp time.Time
RunID string
NodeID string
Message string
Err error
Decision *DecisionDetail // non-nil for decision audit trail events
Cost *CostSnapshot // non-nil for EventCostUpdated and EventBudgetExceeded events
Truncation *TruncationDetail // non-nil for EventToolOutputTruncated
Marker *MarkerDetail // non-nil for EventToolMarkerMissing
Route *RouteDetail // non-nil for EventToolRouteMissing
// Override is non-nil on EventValidationOverridden events. Carries the
// gate, label, actor, and subgraph_path of the traversed override edge.
Override *OverrideDetail
// BundleIdentity is the content-addressed identity of the .dipx bundle
// the run was started against ("sha256:<hex>"). Empty for runs from a
// plain .dip file. The engine stamps this on every emitted event so
// activity.jsonl carries provenance on every line.
BundleIdentity string
}
PipelineEvent carries data about a single pipeline lifecycle occurrence.
type PipelineEventHandler ¶
type PipelineEventHandler interface {
HandlePipelineEvent(evt PipelineEvent)
}
PipelineEventHandler receives pipeline events for observability purposes.
var PipelineNoopHandler PipelineEventHandler = pipelineNoopHandler{}
PipelineNoopHandler is a handler that does nothing, useful as a default.
func NodeScopedPipelineHandler ¶
func NodeScopedPipelineHandler(parentNodeID string, inner PipelineEventHandler) PipelineEventHandler
NodeScopedPipelineHandler wraps a PipelineEventHandler and prefixes every event's NodeID with parentNodeID + "/". Child pipeline lifecycle events (started/completed/failed) are filtered out because the parent engine already tracks the subgraph node's lifecycle.
func PipelineMultiHandler ¶
func PipelineMultiHandler(handlers ...PipelineEventHandler) PipelineEventHandler
PipelineMultiHandler fans out each event to every provided handler. Nil handlers in the list are safely skipped.
type PipelineEventHandlerFunc ¶
type PipelineEventHandlerFunc func(evt PipelineEvent)
PipelineEventHandlerFunc is an adapter that lets ordinary functions serve as PipelineEventHandler.
func (PipelineEventHandlerFunc) HandlePipelineEvent ¶
func (f PipelineEventHandlerFunc) HandlePipelineEvent(evt PipelineEvent)
type PipelineEventType ¶
type PipelineEventType string
PipelineEventType identifies the kind of lifecycle event emitted during pipeline execution.
const ( EventPipelineStarted PipelineEventType = "pipeline_started" EventPipelineCompleted PipelineEventType = "pipeline_completed" EventPipelineFailed PipelineEventType = "pipeline_failed" EventStageStarted PipelineEventType = "stage_started" EventStageCompleted PipelineEventType = "stage_completed" EventStageFailed PipelineEventType = "stage_failed" EventStageRetrying PipelineEventType = "stage_retrying" EventCheckpointSaved PipelineEventType = "checkpoint_saved" EventCheckpointFailed PipelineEventType = "checkpoint_failed" EventParallelStarted PipelineEventType = "parallel_started" EventParallelCompleted PipelineEventType = "parallel_completed" EventManagerCycleTick PipelineEventType = "manager_cycle_tick" EventLoopRestart PipelineEventType = "loop_restart" EventWarning PipelineEventType = "warning" EventEdgeTiebreaker PipelineEventType = "edge_tiebreaker" // Decision audit trail events — capture decision points for post-run reconstruction. EventDecisionEdge PipelineEventType = "decision_edge" EventDecisionCondition PipelineEventType = "decision_condition" EventDecisionOutcome PipelineEventType = "decision_outcome" EventDecisionRestart PipelineEventType = "decision_restart" // Cost governance events — emitted after each completed node and when a budget is exceeded. EventCostUpdated PipelineEventType = "cost_updated" EventBudgetExceeded PipelineEventType = "budget_exceeded" // EventToolOutputTruncated fires after a tool node when one or both of // its output streams exceeded the per-stream cap and head bytes were // elided. Carries TruncationDetail. Surfaced by `tracker diagnose` so // operators can correlate routing mismatches with truncation (issue // #208). EventToolOutputTruncated PipelineEventType = "tool_output_truncated" // EventConditionalFallthrough fires when at least one conditional // outgoing edge from a node was evaluated, none matched, and routing // fell through to an unconditional fallback. The runtime already // emits per-condition results and a final decision_edge event, but // this aggregating signal is what `tracker diagnose` correlates with // EventToolOutputTruncated to surface "your routing marker may have // been dropped" (issue #208). It does NOT fire on intentional // unconditional-only routing. EventConditionalFallthrough PipelineEventType = "conditional_fallthrough" // EventBundleMismatchForced is emitted to activity.jsonl when resume // proceeds despite a bundle-identity mismatch because --force-bundle-mismatch // was set. Records both the original (checkpoint) and current identities // in the entry's Message field for post-hoc audit. Emitted once per run by // JSONLEventHandler.WriteBundleMismatchForced before any engine work begins. EventBundleMismatchForced PipelineEventType = "bundle_mismatch_forced" // EventToolMarkerMissing fires when a tool node declared marker_grep // but the regex matched nothing in captured stdout. The node fails // with OutcomeFail rather than silently falling through to an // unconditional edge — the whole point of marker_grep is to remove // the silent-fallback foot-gun the #208 root cause exploited. // Carries MarkerDetail with the configured regex pattern (#210). EventToolMarkerMissing PipelineEventType = "tool_marker_missing" // EventToolRouteMissing fires when a tool node declared // route_required: true but no _TRACKER_ROUTE= sentinel line was // present in captured stdout (#212). Same shape as // EventToolMarkerMissing, different mechanism: route_required is // the strictness flag for the convention-based sentinel channel; // marker_grep is the strictness flag for the regex-attribute // channel. Carries RouteDetail with the captured stdout tail. EventToolRouteMissing PipelineEventType = "tool_route_missing" // EventValidationOverridden fires when the engine traverses an // Edge.Override-marked edge at advanceToNextNode. Carries an // OverrideDetail payload via PipelineEvent.Override. Stage-level // event (NodeID = gate node), same shape as // EventConditionalFallthrough. Rides alongside (not instead of) the // EventDecisionEdge event emitted for the same selection — the // DecisionEdge carries EdgePriorityOverride on its EdgePriority // field so external consumers that only watch DecisionEdge can still // identify the traversal as an override. EventValidationOverridden PipelineEventType = "validation_overridden" )
type PreflightConfig ¶
type PreflightConfig struct {
WorkDir string // absolute path; required
Requires []string // from graph.Attrs["requires"]
Policy GitPreflight // resolved from CLI > library > default ""
AllowInit bool // required when Policy == GitPreflightInit and !InteractiveTTY
InteractiveTTY bool // when true, --git=init may prompt instead of needing --allow-init
Warner func(format string, args ...any) // optional; defaults to a no-op
// PromptYN is used by --git=init in interactive mode. Tests inject a stub.
// When nil, the default reads from stdin.
PromptYN func(prompt string) bool
}
PreflightConfig captures everything Preflight needs to make a decision. All fields are inputs only; no I/O happens until Preflight runs.
type ProviderUsage ¶
type ProviderUsage struct {
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
TotalTokens int `json:"total_tokens"`
CostUSD float64 `json:"cost_usd"`
ReasoningTokens int `json:"reasoning_tokens"`
CacheReadTokens int `json:"cache_read_tokens"`
CacheWriteTokens int `json:"cache_write_tokens"`
SessionCount int `json:"session_count"`
Estimated bool `json:"estimated,omitempty"`
}
ProviderUsage is the per-provider rollup embedded in UsageSummary. Estimated is true when at least one contributing session reported heuristic-derived usage (e.g. the ACP rune-count estimator). A mixed bucket — some sessions metered, some estimated — still flags Estimated so the operator knows the total is not fully trustworthy.
type RegistryFactory ¶
type RegistryFactory func(graph *Graph, parentNodeID string) *HandlerRegistry
RegistryFactory creates a child HandlerRegistry with event handlers scoped to the given parentNodeID. This allows subgraph handlers to create child registries where agent events are prefixed with the parent node's ID, without the pipeline package needing to import the agent package.
type RetryConfig ¶
type RetryConfig struct {
PolicyName string // "" means "use graph default or 'standard'"
MaxRetries int // 0 and MaxRetriesSet=false means "unset"
MaxRetriesSet bool
BaseDelay time.Duration
BaseDelaySet bool
}
RetryConfig is a typed view over the retry-related attributes shared across handlers and the engine's retry logic. It is a lightweight companion to RetryPolicy: RetryConfig carries the *raw configured values* (which may be absent), while ResolveRetryPolicy folds them into a concrete *RetryPolicy with defaults. Use RetryConfig when you need to distinguish "unset" from "set to zero"; use ResolveRetryPolicy when you need the effective policy to run with.
type RetryPolicy ¶
type RetryPolicy struct {
Name string
MaxRetries int
BaseDelay time.Duration
BackoffFn func(attempt int, base time.Duration) time.Duration
}
RetryPolicy defines a named retry strategy with configurable attempt count and backoff.
func ParseRetryPolicy ¶
func ParseRetryPolicy(name string) (*RetryPolicy, bool)
ParseRetryPolicy returns the named policy and true, or nil and false for unknown names.
func ResolveRetryPolicy ¶
func ResolveRetryPolicy(node *Node, graphAttrs map[string]string) *RetryPolicy
ResolveRetryPolicy determines the retry policy for a node by checking: 1. Node attr "retry_policy" 2. Graph attr "default_retry_policy" 3. Falls back to "standard" The resolved policy's MaxRetries is then overridden by node attr "max_retries" or graph attr "default_max_retry" if either is set.
type RouteDetail ¶
type RouteDetail struct {
CapturedTail string `json:"captured_tail,omitempty"`
}
RouteDetail is the payload for EventToolRouteMissing (#212). The matcher itself is built-in (^\s*_TRACKER_ROUTE=(.+?)\s*$) so there is no Pattern field — just the captured stdout tail for diagnosis.
type SessionStats ¶
type SessionStats struct {
Turns int `json:"turns"`
ToolCalls map[string]int `json:"tool_calls,omitempty"`
TotalToolCalls int `json:"total_tool_calls"`
FilesModified []string `json:"files_modified,omitempty"`
FilesCreated []string `json:"files_created,omitempty"`
Compactions int `json:"compactions"`
LongestTurn time.Duration `json:"longest_turn"`
CacheHits int `json:"cache_hits"`
CacheMisses int `json:"cache_misses"`
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
TotalTokens int `json:"total_tokens"`
CostUSD float64 `json:"cost_usd"`
ReasoningTokens int `json:"reasoning_tokens"`
CacheReadTokens int `json:"cache_read_tokens"`
CacheWriteTokens int `json:"cache_write_tokens"`
Provider string `json:"provider,omitempty"`
// Estimated is true when the token/cost numbers come from a heuristic
// rather than metered usage (e.g. the ACP backend's rune-count
// estimator). Downstream consumers — CLI summary, TUI header, tracker
// diagnose — use this to mark provider rollups with an "(estimated)"
// suffix so operators don't confuse approximate spend with metered
// spend. EstimateSource names the heuristic (e.g. "acp-chars-heuristic")
// and is reserved for future per-source reporting. Derived in
// buildSessionStats from llm.Usage.Raw.
Estimated bool `json:"estimated,omitempty"`
EstimateSource string `json:"estimate_source,omitempty"`
}
SessionStats captures agent session metrics for a pipeline node. Only populated for codergen (LLM agent) nodes.
type Stylesheet ¶
type Stylesheet struct {
Rules []StyleRule
}
Stylesheet holds an ordered list of style rules parsed from a CSS-like input.
func ParseStylesheet ¶
func ParseStylesheet(input string) (*Stylesheet, error)
ParseStylesheet parses a CSS-like stylesheet string into a Stylesheet. Each rule has the form: selector { key: value; key: value; }
func (*Stylesheet) Resolve ¶
func (ss *Stylesheet) Resolve(node *Node) map[string]string
Resolve applies the stylesheet to a node and returns the final resolved property map. Rules are applied in specificity order (low to high), so higher-specificity selectors override lower ones. Explicit node attributes override all stylesheet rules.
type SubgraphHandler ¶
type SubgraphHandler struct {
// contains filtered or unexported fields
}
SubgraphHandler executes a named sub-pipeline inline as a single handler step. It looks up the referenced graph by the node's "subgraph_ref" attribute and runs it with the parent's context values as initial context.
func NewSubgraphHandler ¶
func NewSubgraphHandler( graphs map[string]*Graph, registry *HandlerRegistry, pipelineEvents PipelineEventHandler, factory RegistryFactory, ) *SubgraphHandler
NewSubgraphHandler creates a handler that can execute any of the provided named graphs. The pipelineEvents handler receives scoped events from child engine execution. The registryFactory creates child registries with scoped agent event handlers.
func (*SubgraphHandler) Execute ¶
func (h *SubgraphHandler) Execute(ctx context.Context, node *Node, pctx *PipelineContext) (Outcome, error)
Execute runs the referenced sub-pipeline and maps its result to an Outcome. If the subgraph node has params, they are injected into the child graph before execution. Pipeline and agent events from the child engine are scoped with the parent node ID so the TUI can distinguish subgraph nodes from parent nodes.
func (*SubgraphHandler) Name ¶
func (h *SubgraphHandler) Name() string
Name returns the handler name used for registry lookup.
type TerminalStatus ¶
type TerminalStatus string
TerminalStatus is the run-level terminal status carried on EngineResult.Status, tracker.Result.Status, tracker.AuditReport.Status, and tracker.RunSummary.Status.
The known values are:
- OutcomeSuccess "success"
- OutcomeFail "fail"
- OutcomeBudgetExceeded "budget_exceeded"
- OutcomeValidationOverridden "validation_overridden"
The enum is open — future minor releases may add new values. Consumers should use IsSuccess() to classify rather than switching on the raw string.
const ( OutcomeSuccess TerminalStatus = "success" OutcomeRetry TerminalStatus = "retry" OutcomeFail TerminalStatus = "fail" )
const OutcomeBudgetExceeded TerminalStatus = "budget_exceeded"
OutcomeBudgetExceeded signals that a BudgetGuard halted the run.
const OutcomeValidationOverridden TerminalStatus = "validation_overridden"
OutcomeValidationOverridden signals that the run reached the success exit after traversing at least one Edge.Override == true edge. Engine-terminal-only: handlers never return this value; the engine writes it post-loop based on the runState.validationOverrides slice. See docs/superpowers/specs/2026-05-29-validation-overridden-design.md.
func (TerminalStatus) IsSuccess ¶
func (s TerminalStatus) IsSuccess() bool
IsSuccess reports whether the terminal status represents a run that completed without failure. Currently true for {success, validation_overridden}. Any unrecognized value returns false (fail-closed).
type ToolNodeConfig ¶
type ToolNodeConfig struct {
Command string
OutputLimit int // bytes; 0 means use default
WorkingDir string
PassEnv string // comma-separated env var names to pass through
Timeout time.Duration // raw parsed timeout from node attrs; zero means the attr was absent, unparseable, or parsed to 0. ToolHandler.parseTimeout rejects non-positive values at execution time.
MarkerGrep string // regex applied to captured stdout to extract a routing marker into ctx.tool_marker (issue #210). Empty disables. If non-empty and no match, the node fails with OutcomeFail and an EventToolMarkerMissing audit event is emitted.
// RouteRequired is true when the node MUST receive a _TRACKER_ROUTE=
// sentinel line in its captured stdout (issue #212). Sentinel
// extraction itself runs unconditionally; this flag controls whether
// the absence of a match fails the node. When true, no match →
// OutcomeFail + EventToolRouteMissing. Symmetric to marker_grep's
// failure path, but the matcher is built-in (no per-node regex).
RouteRequired bool
}
ToolNodeConfig is a typed view over a tool node's attributes. Tool nodes execute shell commands and surface stdout/stderr to the pipeline context.
type Trace ¶
type Trace struct {
RunID string `json:"run_id"`
Entries []TraceEntry `json:"entries"`
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time"`
}
Trace captures the full execution history of a pipeline run.
func (*Trace) AddEntry ¶
func (tr *Trace) AddEntry(entry TraceEntry)
AddEntry appends a trace entry to the trace log.
func (*Trace) AggregateToolCalls ¶
AggregateToolCalls sums tool call counts from all trace entries with session stats.
func (*Trace) AggregateUsage ¶
func (tr *Trace) AggregateUsage() *UsageSummary
AggregateUsage sums token usage and cost from all trace entries with session stats and any child-run rollups. Child usage is folded in so that subgraph/manager_loop spend is visible in the parent's budget snapshots and CLI summaries rather than disappearing into the child engine's own trace. Without this fold, BudgetGuard evaluating on the parent's trace could never see child spend and --max-tokens / --max-cost would be silently non-binding for any node that runs a child pipeline.
type TraceEntry ¶
type TraceEntry struct {
Timestamp time.Time `json:"timestamp"`
NodeID string `json:"node_id"`
HandlerName string `json:"handler_name"`
Status string `json:"status"`
Duration time.Duration `json:"duration"`
EdgeTo string `json:"edge_to,omitempty"`
Error string `json:"error,omitempty"`
Stats *SessionStats `json:"stats,omitempty"`
// ChildUsage is the aggregated usage of a child run that executed under
// this node (subgraph, manager_loop). Populated when the handler's
// Outcome carries child-run totals so AggregateUsage can include them
// in the parent's rollup. Omitted from JSON when nil.
ChildUsage *UsageSummary `json:"child_usage,omitempty"`
}
TraceEntry records the execution of a single pipeline node.
type TruncationDetail ¶
type TruncationDetail struct {
Stream string `json:"stream"` // "stdout" or "stderr"
Limit int `json:"limit"` // per-stream cap in effect
CapturedBytes int `json:"captured_bytes"` // bytes preserved in the captured tail
DroppedBytes int `json:"dropped_bytes"` // bytes elided from the head
TotalBytes int `json:"total_bytes"` // CapturedBytes + DroppedBytes for convenience
}
TruncationDetail carries structured data about a tool-output truncation event. Attached to PipelineEvent via the Truncation field when a tool stream's tail-window cap was hit. Issue #208.
type UsageSummary ¶
type UsageSummary struct {
TotalInputTokens int `json:"total_input_tokens"`
TotalOutputTokens int `json:"total_output_tokens"`
TotalTokens int `json:"total_tokens"`
TotalCostUSD float64 `json:"total_cost_usd"`
TotalReasoningTokens int `json:"total_reasoning_tokens"`
TotalCacheReadTokens int `json:"total_cache_read_tokens"`
TotalCacheWriteTokens int `json:"total_cache_write_tokens"`
SessionCount int `json:"session_count"`
ProviderTotals map[string]ProviderUsage `json:"provider_totals,omitempty"`
Estimated bool `json:"estimated,omitempty"`
}
UsageSummary aggregates token usage and cost across all pipeline nodes. Estimated is true when any contributing session was heuristic-derived. CLI / TUI / diagnose surfaces render the run total with an "~" or "(estimated)" marker when this is set, so operators don't interpret mixed metered+estimated totals as fully metered.
type ValidationError ¶
ValidationError collects multiple validation failures and warnings into one error.
func ValidateAll ¶
func ValidateAll(g *Graph) *ValidationError
ValidateAll checks a parsed Graph and returns a ValidationError containing both errors and warnings. Returns nil only if neither exists.
func ValidateAllWithLint ¶
func ValidateAllWithLint(g *Graph, registry *HandlerRegistry) *ValidationError
ValidateAllWithLint checks a parsed Graph for structural and semantic issues, including Dippin lint warnings. Returns a ValidationError with both errors and warnings.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string
Source Files
¶
- artifacts.go
- audit_path.go
- backend.go
- budget.go
- checkpoint.go
- condition.go
- context.go
- context_writes.go
- dippin_adapter.go
- dippin_adapter_edges.go
- dippin_load.go
- dipx_load.go
- doc.go
- engine.go
- engine_checkpoint.go
- engine_edges.go
- engine_run.go
- events.go
- events_jsonl.go
- events_logger.go
- expand.go
- fidelity.go
- git_artifacts.go
- git_preflight.go
- graph.go
- handler.go
- lint_tracker.go
- node_config.go
- override.go
- params.go
- parser.go
- retry_policy.go
- snapshot_unix.go
- stylesheet.go
- subgraph.go
- terminal_status.go
- trace.go
- transforms.go
- validate.go
- validate_semantic.go