tracker

package module
v0.49.0 Latest Latest
Warning

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

Go to latest
Published: Jul 30, 2026 License: MIT Imports: 35 Imported by: 0

README

Tracker

Pipeline orchestration engine for multi-agent LLM workflows. Define pipelines in .dip files (Dippin language), execute them with parallel agents, and watch progress in a TUI dashboard.

Built by 2389.ai. Docs, walkthroughs & the TUI in action →

Quick Start

Install — Homebrew (macOS/Linux) or go install:

brew install 2389-research/tap/tracker
# or:  go install github.com/2389-research/tracker/cmd/tracker@latest

Three steps to your first run — no .dip file needed:

tracker doctor            # 1. check setup: API keys, dippin binary, working dir
tracker ask_and_execute   # 2. run a built-in workflow: describe what you want, answer one gate, watch it build
tracker diagnose          # 3. inspect what happened (also: tracker audit / tracker list)

From there:

  • Bring a spectracker init build_product scaffolds build_product.dip + a starter SPEC.md; edit the spec, then tracker build_product.
  • Hands-off — add --autopilot mid to replace human gates with an LLM judge (or --auto-approve for deterministic approval).
  • Drive it elsewhere — from Slack (trackerbot) or your terminal as a REPL (trackerchat).
  • Exploretracker workflows (list built-ins), tracker validate <wf>, tracker simulate <wf>, tracker estimate <wf>, tracker setup, tracker --help.

Release history has moved out of this README — see CHANGELOG.md for what's new in each version, and the Roadmap for what's next.

Pipeline Examples

Four pipelines are embedded in the binary and available via tracker workflows:

ask_and_execute

Competitive implementation: ask the user what to build, fan out to 3 agents (Claude/Codex/Gemini) in isolated git worktrees, cross-critique the implementations, select the best one, apply it, clean up the rest.

build_product

Sequential milestone builder: read a SPEC.md, decompose into milestones, implement each with verification loops (opus-powered fix agent with 50 turns), cross-review the complete result, verify full spec compliance. Context-specific escalation gates let you override flaky tests or skip milestones without aborting the build.

graph LR
    ReadSpec --> Decompose --> ApprovePlan
    ApprovePlan -->|approve| PickNext
    PickNext -->|milestone N| Implement --> Test
    Test -->|pass| Verify --> MarkDone --> PickNext
    Test -->|fail| Fix --> Test
    Test -->|escalate| EscalateMilestone
    EscalateMilestone -->|mark done| MarkDone
    EscalateMilestone -->|retry| Implement
    PickNext -->|all done| CrossReview --> FinalBuild --> FinalSpec --> Cleanup --> Done
build_product_with_superspec

Parallel stream execution for large structured specs: reads the spec's work streams and dependency graph, executes independent streams in parallel (with git worktree isolation), enforces quality gates between phases, cross-reviews with 3 specialized reviewers (architect/QA/product), and audits traceability.

deep_review

Interview-driven codebase review: describe what you want reviewed, answer structured interview questions to scope the analysis, then three parallel agents analyze correctness, security, and design. A second interview presents findings for your context (is this intentional? known issue?), a third prioritizes remediation, and the pipeline produces an actionable remediation plan.

graph LR
    DescribeGoal --> Explore --> ScopeInterview
    ScopeInterview --> AnalyzeParallel
    AnalyzeParallel --> Correctness & Security & Design
    Correctness & Security & Design --> Join
    Join --> Synthesize --> FindingsInterview
    FindingsInterview --> PriorityInterview
    PriorityInterview --> RemediationPlan --> ReviewPlan
    ReviewPlan -->|approve| Finalize --> Done
    ReviewPlan -->|revise| RemediationPlan

Built-in Workflows

Pipelines are embedded in the binary so brew and go install users can run them without cloning the repo:

tracker workflows              # List all built-in workflows
tracker ask_and_execute        # Run directly by name — no files needed
tracker validate build_product # Validate works too
tracker simulate build_product # Simulate too
tracker init build_product     # Copy .dip + scaffold a starter SPEC.md for editing

build_product builds from a SPEC.md in the repo root; running it without one exits with a pointer to tracker init build_product, which scaffolds both the .dip and a starter SPEC.md so the first run succeeds.

Local .dip files always take precedence over built-ins. After tracker init build_product, running tracker build_product uses your local copy.

Dippin Language

Pipelines are defined in .dip files using the Dippin language:

workflow MyPipeline
  goal: "Build something great"
  start: Begin
  exit: Done

  defaults
    model: claude-sonnet-4-6
    provider: anthropic

  agent Begin
    label: Start

  human AskUser
    label: "What should we build?"
    mode: freeform

  agent Implement
    label: "Build It"
    prompt: |
      The user wants: ${ctx.human_response}
      Implement it following the project's conventions.

  agent Done
    label: Done

  edges
    Begin -> AskUser
    AskUser -> Implement
    Implement -> Done
Declaring Environmental Dependencies — requires: (v0.29.0)

Workflows can declare what they need from the host environment with a requires: line in the header:

workflow BuildProduct
  goal: "..."
  requires: git
  start: Start
  exit: Done

Tracker checks these at startup (when you invoke tracker <workflow>). If the env doesn't satisfy them, the run fails in seconds with a copy-paste remediation instead of burning LLM spend before the first failure. Override per-run with --git=auto|off|warn|require|init (default auto respects requires:; --git=init --allow-init auto-initializes the workdir, with safety refusals for $HOME, /, and nested repos — including bare repos, linked worktrees, and submodules). v0.29.0 implements git; unrecognized entries warn and continue so workflow authors can forward-declare deps that future tracker versions will check.

Node Types
Type Shape Description
agent box LLM agent session (codergen)
human hexagon Human-in-the-loop gate (choice, freeform, or hybrid)
tool parallelogram Shell command execution
parallel component Fan-out to concurrent branches
fan_in tripleoctagon Join parallel branches
subgraph tab Execute a referenced sub-pipeline
manager_loop house Managed iteration loop
conditional diamond Condition-based routing
Variable Interpolation

Three namespaces for ${...} syntax in prompts:

  • ${ctx.outcome} — runtime pipeline context (outcome, last_response, human_response, tool_stdout)
  • ${params.model} — workflow-level vars (optionally overridden by --param key=value at run time, v0.19.0) and subgraph parameters passed from a parent pipeline
  • ${graph.goal} — workflow-level attributes

Declare defaults in a top-level vars block and override them per-run:

workflow MyPipeline
  vars
    model: claude-sonnet-4-6
    retries: 3
tracker --param model=claude-opus-4 --param retries=1 MyPipeline

Unknown --param keys hard-fail at startup. Dippin-lang's lint (run automatically at .dip load) flags undeclared ${params.*} references and other variable-reference mistakes — see dippin doctor for the full lint catalog.

Variables are expanded in a single pass — resolved values are never re-scanned, preventing recursive expansion.

Important: Each agent node runs a fresh LLM session. Data flows between nodes via context keys, not conversation history. Per-node scoping (${ctx.node.<nodeID>.<key>}) lets you reference a specific earlier node's output without relying on the last-writer-wins last_response key. See Pipeline Context Flow for the full model, fidelity levels, and parallel-branch patterns.

Edge Conditions
edges
  Check -> Pass  when ctx.outcome = success
  Check -> Fail  when ctx.outcome = fail
  Check -> Retry when ctx.outcome = retry
  Gate -> Next   when ctx.tool_stdout contains all-done
  Gate -> Loop   when ctx.tool_stdout not contains all-done

Supported operators: =, !=, contains, not contains, startswith, not startswith, endswith, not endswith, in, not in, &&, ||, not.

ctx.tool_stdout and ctx.tool_stderr capture the tail of a tool node's output (default cap 64KB per stream, configurable per-node via output_limit: 256KB; --max-output-limit is a hard global ceiling, default 10MB, that caps how high a per-node output_limit can go). Routing markers emitted at end-of-output via printf survive truncation by construction; tracker diagnose surfaces a tool_output_truncated suggestion when a stream was clipped so you know to raise the limit if the captured tail isn't what you expected.

Conditions support the ctx. namespace prefix (dippin convention) and internal.* references for engine-managed state.

Declarative Structured Output — writes: / reads:

Agent, tool, and mode: interview human nodes can declare the context keys they produce and consume (v0.21.0):

agent Planner
  response_format: json_object
  writes:
    - milestone_id
    - files
  reads:
    - spec_path

The node output must be a valid top-level JSON object; every declared key in writes: must be present or the node hard-fails. Extras are allowed (surfaced as warnings), strings are stored verbatim, non-string values are stored as compact JSON. reads: pins fidelity for upstream keys so downstream nodes see consistent data. See Pipeline Context Flow for the full contract, worked examples, and interview-mode semantics.

Per-Node Working Directory

For git worktree isolation in parallel implementations:

agent ImplementClaude
  working_dir: .ai/worktrees/claude
  model: claude-sonnet-4-6
  prompt: Implement the spec in this isolated worktree.

The working_dir attribute is validated against path traversal and shell metacharacters.

Human Gates

Five gate modes:

  • Choice mode (default): presents outgoing edge labels as a radio list. Arrow keys navigate, Enter selects.
  • Freeform mode (mode: freeform): captures text input. If the response matches an edge label (case-insensitive), it routes to that edge. Otherwise it's stored as ctx.human_response.
  • Hybrid mode (automatic): when a freeform gate has labeled outgoing edges, the TUI presents a radio list of labels plus an "other" option for custom feedback. Selecting a label submits it directly; selecting "other" opens a textarea for specific instructions.
  • Yes/No mode (mode: yes_no): fixed two-option prompt. Yes maps to OutcomeSuccess, No maps to OutcomeFail — route with when ctx.outcome = success / when ctx.outcome = fail edges. Distinct from choice mode, where the outcome is always success and routing uses preferred_label.
  • Interview mode (mode: interview): structured multi-field form driven by upstream agent output. An agent generates markdown questions with inline options; the handler parses them into individual form fields and presents a fullscreen interview form. Answers are stored as JSON and markdown summary.

Long prompts with labels (e.g., escalation gates with agent output) automatically use a fullscreen review hybrid view: glamour-rendered scrollable viewport on top (PgUp/PgDn to scroll), radio label selection in the middle, and an "other" freeform option at the bottom for custom retry instructions. Long prompts without labels use a split-pane review: scrollable viewport on top, textarea on bottom.

human ApproveSpec
  label: "Review the spec. Approve, refine, or reject."
  mode: freeform

edges
  ApproveSpec -> Build  label: "approve"
  ApproveSpec -> Revise label: "refine"  restart: true
  ApproveSpec -> Done   label: "reject"
Interview Mode

Interview gates let an agent generate structured questions that the user answers via a form:

human ScopeInterview
  label: "Help us focus the review."
  mode: interview
  questions_key: interview_questions
  answers_key: scope_answers

The upstream agent writes markdown questions to the questions_key context variable. The parser extracts:

  • Numbered/bulleted questions ending in ? or imperative prompts ("Describe...", "List...")
  • Inline options from trailing parentheticals: Auth model? (API key, OAuth, JWT) becomes a select field
  • Yes/no patterns detected automatically as confirm toggles

The TUI presents a fullscreen form with per-field navigation (arrow keys), pagination (PgUp/PgDn for 10+ questions), elaboration textareas (Tab), and pre-fill from previous answers on retry. Answers are stored as JSON at answers_key and as a markdown summary at human_response. If zero questions are parsed, the gate falls back to freeform. Cancellation returns outcome=fail.

A reusable interview loop pattern is available in examples/subgraphs/interview-loop.dip — embed it via subgraph nodes with topic and focus parameters.

Submit with Ctrl+S. Enter inserts newlines. Esc cancels (empty) or submits (with content). Ctrl+C cancels and unblocks the pipeline (no deadlock).

Providers

Tracker supports four LLM providers: anthropic, openai, gemini, and openai-compat (for any OpenAI-compatible API). Set up with:

# Interactive setup wizard
tracker setup

# Verify your configuration
tracker doctor

Keys are stored in ~/.config/2389/tracker/.env. You can also export them directly:

export ANTHROPIC_API_KEY=sk-ant-...
export OPENAI_API_KEY=sk-...
export GEMINI_API_KEY=...

Important: Use gemini (not google) as the provider name in .dip files.

Non-retryable provider errors (quota exceeded, auth failure, model not found) immediately fail the pipeline with a clear message instead of silently retrying.

Cloudflare AI Gateway

Tracker can route every provider through Cloudflare AI Gateway so you stop hitting rate limits (Anthropic, OpenAI, etc. cap per-account request rates; Cloudflare's gateway capacity is much higher), gain central analytics and caching, and enable model routing on the gateway side.

Set one env var or flag instead of four:

# The root URL of your Cloudflare AI Gateway:
#   https://gateway.ai.cloudflare.com/v1/<account_id>/<gateway_slug>
export TRACKER_GATEWAY_URL="https://gateway.ai.cloudflare.com/v1/acc/gw"

# API keys still go to the provider — Cloudflare just proxies.
export ANTHROPIC_API_KEY=sk-ant-...
export OPENAI_API_KEY=sk-...
export GEMINI_API_KEY=...

tracker build_product

Or as a CLI flag:

tracker --gateway-url https://gateway.ai.cloudflare.com/v1/acc/gw build_product

Tracker automatically appends the per-provider suffix:

Provider Resolved URL
anthropic <gateway>/anthropic
openai <gateway>/openai
gemini <gateway>/google-ai-studio
openai-compat <gateway>/compat

Per-provider overrides still win. If you set ANTHROPIC_BASE_URL directly, Anthropic traffic goes there, and the gateway only proxies the providers you haven't explicitly overridden. This means you can point Anthropic at a self-hosted proxy while keeping OpenAI on Cloudflare with one command.

Troubleshooting:

  • 429 from Cloudflare: something bigger is wrong (account-level limits, bad gateway slug). 429s from direct provider calls are what the gateway is meant to prevent.
  • 401: check your provider API key, not the gateway — Cloudflare passes auth through.
  • Empty responses: verify the gateway slug is correct and the provider is enabled in the Cloudflare dashboard.

Architecture

Tracker is a three-layer stack: an LLM client (provider adapters and token tracking), an agent session (turn loop, tool execution, context compaction), and a pipeline engine (graph execution, edge routing, checkpoints, decision audit, TUI). The dippin adapter converts parsed .dip IR into tracker's Graph model, and handlers implement per-node behavior.

graph TB
    subgraph "Layer 3: Pipeline Engine"
        Engine["Graph Execution<br/>Edge Routing<br/>Checkpoints<br/>Decision Audit"]
        Handlers["Handlers: start, exit, codergen, tool,<br/>wait.human, parallel, parallel.fan_in,<br/>conditional, subgraph, stack.manager_loop"]
        Adapter["Dippin Adapter<br/>IR → Graph"]
        TUI["TUI: node list,<br/>activity log, modals"]
    end
    subgraph "Layer 2: Agent Session"
        Session["Tool Execution<br/>Context Compaction<br/>Event Streaming"]
    end
    subgraph "Layer 1: LLM Client"
        Anthropic & OpenAI & Gemini
    end
    Engine --> Handlers
    Engine --> Adapter
    Engine --> TUI
    Handlers --> Session
    Session --> Anthropic & OpenAI & Gemini

The core engine is UI-agnostic: the TUI, the Slack bot (trackerbot), the terminal REPL (trackerchat), and any future web/mobile front-end are peers on one library boundary — tracker.ConfigEngine, an Interviewer seam for human gates, the pipeline/agent event streams, and RunManager for many concurrent runs. The Slack bot and the REPL share one transport-neutral core (transport/chatops); each adds only its own I/O. See docs/architecture/transport-boundary.md.

For subsystem-level architecture docs, see ARCHITECTURE.md and docs/architecture/.

TUI

The terminal UI shows:

  • Pipeline panel: node list in topological execution order (Kahn's algorithm) with status lamps, thinking spinners, and tool execution indicators
  • Activity log: per-node streaming with line-level formatting (headers, code blocks, bullets), node change separators, multi-node activity indicators for parallel execution, and inline FAILED:/RETRYING: messages when nodes fail or retry
  • Subgraph nodes: dynamically inserted and indented under their parent
Status Icons
Icon Meaning
Pending — not yet reached
🟡 (spinner) Running — LLM thinking
Running — tool executing
● (green) Completed successfully
✗ (red) Failed
↻ (amber) Retrying
⊘ (dim) Skipped — pipeline took a different path
Run terminal status

tracker.Result.Status is one of:

Value Meaning IsSuccess()
success Run reached the success exit; all validations passed. true
validation_overridden Run reached the success exit, but a human, autopilot, or webhook accepted a failed validation along the way. See Result.ValidationOverrides. true
budget_exceeded A BudgetGuard halted the run. false
fail Run halted via failure. false

The enum is open — future minor releases may add new values. Use IsSuccess() (or status_class in JSON output) instead of switching on the raw string.

Keyboard
Key Action
v Cycle log verbosity (all / tools / errors / reasoning)
z Toggle zen mode (full-width log, sidebar hidden)
/ Search the activity log (n/N next/prev, Esc exits)
? Help overlay with all shortcuts
Enter Drill down into the selected node (Esc exits)
y Copy the visible log to the clipboard
Ctrl+O Toggle expand/collapse tool output
Ctrl+S Submit human gate input
Esc Cancel (empty) or submit (with content)
PgUp/PgDn Scroll review viewport (plan approval)
q Quit

Drive it from Slack

cmd/trackerbot is a Slack front-end built on the transport boundary. Mention it and it starts a pipeline in a thread, streams notifications and clarifying gate questions there, and delivers the result — arbitrarily many runs at once, one per thread:

@trackerbot make me a CLI that greets people
@trackerbot run build_product
@trackerbot status   @trackerbot cancel   @trackerbot runs

Natural-language requests are routed to a workflow by an LLM; all four gate modes (choice / yes-no / freeform / interview) work in-thread; interrupted runs resume after a restart. It connects via Socket Mode (no public endpoint). Setup and configuration: cmd/trackerbot/README.md.

Drive it from your terminal

cmd/trackerchat is the same experience as a REPL: type a request, answer gates inline, watch the run — no Slack required. It's the second consumer of the transport boundary, built from the same transport/chatops core, so it inherits every command, gate mode, cost estimate, and steer/bump control the Slack bot has:

$ trackerchat
> run ask_and_execute
❓ Which approach should I take?
   1) Minimal  [default]
   2) Full-featured
> 1
✅ done — success · $0.42 · 1m03s

Decision Audit Trail

Every run produces an activity.jsonl log. Live writes go to the integrity-protected path under $XDG_STATE_HOME/tracker/runs/<id>/activity.jsonl (mode 0o600, default $HOME/.local/state/tracker/runs/<id>/, override via TRACKER_AUDIT_DIR; #213). At run-end a sentinel-stripped snapshot is mirrored back to .tracker/runs/<id>/activity.jsonl for bundle export and post-run grep/jq workflows. Captured content:

  • Pipeline events: node start/complete/fail, checkpoint saves
  • Agent events: LLM turns, tool calls, text output
  • Decision events: edge selection (with priority level and context snapshot), condition evaluations (with match results), node outcomes (with token counts), restart detections

Reconstruct any routing decision after the fact (post-run snapshot path):

# See all edge decisions
grep 'decision_edge' .tracker/runs/<id>/activity.jsonl | python3 -m json.tool

# See condition evaluations
grep 'decision_condition' .tracker/runs/<id>/activity.jsonl | python3 -m json.tool

# See node outcomes with token counts
grep 'decision_outcome' .tracker/runs/<id>/activity.jsonl | python3 -m json.tool

Git Integration

Enable git artifacts from the library via the WithGitArtifacts(true) option on the engine builder; the artifact run directory becomes a git repository and each terminal node outcome creates a commit. (There is no CLI flag for this today — use the ExportBundle helper or the --export-bundle CLI flag to produce a portable bundle from any run directory.)

node(start): start outcome=success
node(middle): codergen outcome=success
node(end): exit outcome=success

Checkpoint tags (checkpoint/<runID>/<nodeID>) mark each save point.

Exporting a run as a portable bundle

ExportBundle packages the entire git history — commits and tags — into a single file you can copy anywhere:

// Library usage
result, _ := engine.Run(ctx)
if err := tracker.ExportBundle(result.ArtifactRunDir, "/tmp/run.bundle"); err != nil {
    log.Printf("bundle export failed: %v", err)
}
# CLI usage: export bundle after the pipeline completes
tracker --export-bundle /tmp/run.bundle examples/ask_and_execute.dip

# Restore and inspect on any machine with git
git clone /tmp/run.bundle /tmp/run
cd /tmp/run && git log --oneline

The bundle is self-contained — no network access needed. Clone it on another machine, inspect the exact sequence of node outcomes, and replay from any checkpoint tag.

Troubleshooting

When a pipeline run doesn't go as expected, tracker gives you tools to understand what happened:

tracker diagnose

Analyzes a run's failures and surfaces the information you need — tool stdout/stderr, error messages, timing anomalies — without manually grepping through JSONL files.

# Diagnose the most recent run
tracker diagnose

# Diagnose a specific run (prefix matching works)
tracker diagnose 7813b

The output shows each failed node with its output, stderr, errors, and actionable suggestions. For example, it will tell you if a tool node failed because of a stale counter file, or if a node completed suspiciously fast (suggesting a configuration issue).

tracker audit

For a broader view of a run's timeline, retries, and recommendations:

# List all runs
tracker list

# Full audit report for a specific run
tracker audit <run-id>
Common issues
Symptom Cause Fix
"no LLM providers configured" Missing API keys tracker setup or export env vars
TestMilestone instantly escalates Stale fix_attempts counter rm .ai/milestones/fix_attempts
Node fails with no visible error Tool stderr not surfaced tracker diagnose shows full output
Pipeline loops forever Unconditional fallback to loop target Ensure fallbacks go to an exit node (Done, escalation gate), not back into the loop
Tool retries same error 5 times Deterministic command bug tracker diagnose flags identical retries — fix the command in the .dip file
Every milestone needs fixing known_failures has comments or bad format Ensure bare test names only, no comments — v0.11.2 strips them automatically
Build loop skips all milestones Milestone headers don't match expected format Use ## Milestone N: Title format — v0.11.2 is flexible + fails loudly

Cost Governance

Tracker exposes per-provider token and dollar cost from every run, and can halt pipelines that exceed configured ceilings.

Library consumers read cost via Result.Cost:

result, _ := tracker.Run(ctx, source, tracker.Config{
    Budget: pipeline.BudgetLimits{
        MaxTotalTokens: 100_000,
        MaxCostCents:   500,           // $5.00
        MaxWallTime:    30 * time.Minute,
    },
})
// IsSuccess() returns true for {success, validation_overridden}; classify by
// status_class for stable bucketing across future enum extensions.
if !result.Status.IsSuccess() {
    log.Printf("run did not complete cleanly: status=%s, spent $%.4f",
        result.Status, result.Cost.TotalUSD)
}
// To branch on overrides specifically:
if len(result.ValidationOverrides) > 0 {
    log.Printf("run involved %d override(s)", len(result.ValidationOverrides))
}
for provider, pc := range result.Cost.ByProvider {
    log.Printf("%s: %d tokens, $%.4f", provider, pc.Usage.InputTokens+pc.Usage.OutputTokens, pc.USD)
}

CLI users pass flags directly to tracker:

tracker --max-tokens 100000 --max-cost 500 --max-wall-time 30m \
    examples/ask_and_execute.dip

A halted run prints a HALTED: budget exceeded section naming the dimension that tripped. Run tracker diagnose to see the per-provider breakdown and remediation guidance.

Streaming consumers subscribe to EventCostUpdated via tracker.Config.EventHandler. Each terminal-node outcome emits a CostSnapshot with aggregate tokens, dollar cost, per-provider totals, and wall-clock elapsed time.

Budget ceilings can also be declared inline in the workflow's defaults: block (v0.19.0) and act as the fallback when neither Config.Budget nor the matching --max-* CLI flags are set:

workflow MyPipeline
  defaults
    model: claude-sonnet-4-6
    max_total_tokens: 100000
    max_cost_cents:   500
    max_wall_time:    30m

Explicit library/CLI values still win over the .dip defaults.

Headless Execution (Webhook Gate)

--webhook-url enables fully headless operation: instead of pausing the pipeline to wait for a human at a terminal, tracker POSTs every human gate as JSON to your URL and waits for a callback.

This is the integration point for Slack bots, email approval flows, mobile push notifications, factory workers, or any custom approval system.

Flow
  1. A human gate fires → tracker POSTs a WebhookGatePayload to --webhook-url.
  2. Your service receives the payload, routes it to a human (Slack message, email, etc.).
  3. The human responds → your service POSTs a WebhookGateResponse to the callback_url field.
  4. Tracker resumes the pipeline with the human's answer.
CLI
tracker --webhook-url https://factory.example.com/api/gate \
        --gate-timeout 30m \
        --gate-timeout-action fail \
        --webhook-auth "Bearer sk_live_..." \
        examples/build_product.dip
Flags
Flag Default Description
--webhook-url (required to enable) URL to POST gate payloads to
--gate-callback-addr :8789 Local addr for the inbound callback server
--gate-timeout 10m How long to wait for a reply per gate
--gate-timeout-action fail What to do on timeout: fail or success
--webhook-auth (empty) Authorization header on outbound POSTs

--webhook-url is mutually exclusive with --autopilot and --auto-approve.

Payload format

Tracker POSTs JSON with this shape:

{
  "gate_id": "uuid",
  "run_id": "optional-run-id",
  "node_id": "ApproveSpec",
  "prompt": "Review the spec. Approve, refine, or reject.",
  "choices": [{"label": "approve", "value": "approve"}, ...],
  "callback_url": "http://localhost:8789/gate/f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "timeout_seconds": 1800,
  "gate_token": "per-gate-secret"
}

Your service POSTs back to callback_url with:

{
  "choice": "approve",
  "freeform": "optional free-text response",
  "reasoning": "optional explanation"
}

Include the gate_token value in the X-Tracker-Gate-Token header — the callback server rejects requests with missing or wrong tokens (HTTP 401).

Library API

⚠️ Stability note (pre-v1.0): tracker's library API is usable now, but breaking changes may still happen between minor releases while the surface is finalized. Check CHANGELOG.md before upgrading. Full policy — the supported surface, the open-enum rule, and the deprecation contract — is in docs/api-stability.md, mechanically guarded by an exported-surface golden snapshot (api_surface_test.go).

Library consumers set tracker.Config.WebhookGate instead of using CLI flags:

result, _ := tracker.Run(ctx, source, tracker.Config{
    WebhookGate: &tracker.WebhookGateConfig{
        WebhookURL:    "https://factory.example.com/api/gate",
        CallbackAddr:  ":8789",
        Timeout:       30 * time.Minute,
        TimeoutAction: "fail",
        AuthHeader:    "Bearer sk_live_...",
    },
})
Analyzing past runs from code
import (
    "context"

    tracker "github.com/2389-research/tracker"
)

ctx := context.Background()
report, err := tracker.DiagnoseMostRecent(ctx, ".")
if err != nil { log.Fatal(err) }

for _, f := range report.Failures {
    fmt.Printf("failed: %s (handler=%s, retries=%d)\n",
        f.NodeID, f.Handler, f.RetryCount)
}
for _, s := range report.Suggestions {
    fmt.Printf("  %s: %s\n", s.Kind, s.Message)
}

tracker.Audit, tracker.DiagnoseMostRecent, tracker.Simulate, and tracker.Doctor all accept context.Context as their first argument and return JSON-serializable reports. tracker.ListRuns and DiagnoseMostRecent/Diagnose accept an optional config (AuditConfig, DiagnoseConfig) with a LogWriter for non-fatal parse warnings; if LogWriter is left unset, warnings are discarded, so embedded callers are silent by default. Set LogWriter to something like os.Stderr (or another writer/logger sink) if you want to receive those warnings. Audit and Simulate currently take just ctx (plus their payload); Doctor takes a required DoctorConfig plus optional functional options (e.g., tracker.WithVersionInfo).

If you currently shell out to tracker diagnose and scrape stdout, migrate to tracker.Diagnose() / tracker.DiagnoseMostRecent() and read DiagnoseReport directly instead of parsing formatted CLI text.

To stream events programmatically in the same NDJSON format as tracker --json, use tracker.NewNDJSONWriter:

w := tracker.NewNDJSONWriter(os.Stdout)
result, _ := tracker.Run(ctx, source, tracker.Config{
    EventHandler: w.PipelineHandler(),
    AgentEvents:  w.AgentHandler(),
})

CLI Reference

tracker [flags] <pipeline>       Run a pipeline (file path or built-in name)
tracker workflows                List built-in workflows
tracker init <workflow>          Copy a built-in to current directory
tracker setup                    Interactive provider configuration
tracker validate <pipeline>      Check pipeline structure
tracker simulate <pipeline>      Dry-run execution plan
tracker doctor                   Preflight health check
tracker diagnose [runID]         Analyze failures in a run
tracker audit <runID>            Full audit report for a run
tracker list                     List recent pipeline runs
tracker update                   Self-update to the latest GitHub release
tracker version                  Show version information

Flags:

  • -w, --workdir — working directory (default: current)
  • -r, --resume — resume a previous run by ID
  • --format — pipeline format override: dip (default) or dot (legacy; emits a deprecation warning)
  • --json — stream events as NDJSON to stdout
  • --no-tui — disable TUI dashboard, use plain console
  • --verbose — show raw provider stream events
  • --backend — agent backend: native (default), claude-code, or acp
  • --autopilot <persona> — replace human gates with an LLM judge (lax / mid / hard / mentor)
  • --auto-approve — deterministically accept every human gate (no LLM)
  • --param key=value — override a declared workflow var at run time (repeatable)
  • --artifact-dir — override the node state directory (default: <workdir>/.tracker/runs)
  • --max-tokens — halt if total tokens across the run exceed this value (0 = no limit)
  • --max-cost — halt if total cost in cents exceeds this value (0 = no limit)
  • --max-wall-time — halt if pipeline wall time exceeds this duration (0 = no limit)
  • --gateway-url — Cloudflare AI Gateway root URL (per-provider *_BASE_URL env vars win)
  • --webhook-url — POST human gate prompts to this URL and wait for callback (headless)
  • --gate-callback-addr — local addr for the webhook callback server (default: :8789)
  • --gate-timeout — per-gate wait timeout when --webhook-url is set (default: 10m)
  • --gate-timeout-action — what to do on gate timeout: fail (default) or success
  • --webhook-authAuthorization header for outbound webhook requests
  • --export-bundle — write a portable git bundle of run artifacts to the given path after completion
  • --bypass-denylist — disable the built-in tool command denylist (prints a stderr warning; sandboxed use only)
  • --tool-allowlist <pattern> — glob pattern a tool command must match to execute (repeatable or comma-separated)
  • --max-output-limit <bytes> — hard ceiling per tool command output stream (default: 10MB)

Development

# Run tests
go test ./... -short

# Validate all example pipelines
for f in examples/*.dip; do tracker validate "$f"; done

# Run dippin simulation tests
for f in examples/*.dip; do dippin test "$f"; done

# Check with dippin-lang tools
dippin doctor examples/build_product.dip
dippin simulate -all-paths examples/build_product.dip

License

See LICENSE.

Documentation

Overview

Package tracker is the public library API for running and inspecting tracker pipelines.

API stability: tracker is pre-v1.0. Public APIs are intended to be production-usable, but breaking changes may still occur between minor releases while the surface is finalized. Read CHANGELOG.md before upgrading.

ABOUTME: Top-level convenience API for running pipelines (.dip preferred, .dot deprecated) with auto-wired dependencies. ABOUTME: Consumers import only this package — LLM clients, registries, and environments are built automatically.

ABOUTME: Shared helpers for resolving run directories and parsing activity.jsonl. ABOUTME: Promoted from cmd/tracker/ so library and CLI use one implementation.

ABOUTME: Decode struct and populate helpers behind ParseActivityLine's structured payloads. ABOUTME: JSON names mirror pipeline's jsonlLogEntry and Go names mirror StreamEvent (E2).

ABOUTME: Library API for auditing a completed pipeline run. ABOUTME: Returns structured timeline, retries, errors, and recommendations.

ABOUTME: Terminal-status classification and recommendation helpers for Audit. ABOUTME: Maps activity + checkpoint into a Status string, a stable StatusClass, and recs.

ABOUTME: Git bundle export for pipeline run artifact repositories. ABOUTME: ExportBundle wraps `git bundle create --all` so a completed run can be ABOUTME: shipped as a single portable file and restored with `git clone <bundle>`.

ABOUTME: LLM client provider-adapter construction and gateway base-URL resolution. ABOUTME: Split from tracker.go to keep the root API file under the size ceiling (#450).

ABOUTME: Public seam for injecting the library's diagnostic sink (#449). ABOUTME: Embedders redirect/suppress/level-filter tracker's internal diagnostics here.

ABOUTME: Library API for diagnosing pipeline run failures. ABOUTME: Reads checkpoint + status.json + activity.jsonl and returns a structured report.

ABOUTME: Diagnose detector for cost asymmetry — one backend with no prompt ABOUTME: caching dominating a run's cost, unnoticed until the bill (#353).

ABOUTME: Activity-log scanning helpers for Diagnose — line loop, per-line ABOUTME: decoding, anomaly recording, and per-node failure enrichment.

ABOUTME: Suggestion-building helpers for Diagnose — turns node failures and ABOUTME: runtime anomalies into actionable Suggestion entries.

ABOUTME: Library API for preflight health checks. ABOUTME: Pure read-only — no network probes unless ProbeProviders: true.

ABOUTME: EstimateRun — a rough pre-run cost/scale ballpark from static analysis. ABOUTME: Prices each agent node's model against a turn heuristic; deliberately a range.

ABOUTME: Public NDJSON event writer for the tracker --json wire format. ABOUTME: Threaded from pipeline/LLM/agent event streams; thread-safe for concurrent writers.

ABOUTME: Populates the structured StreamEvent payload fields from pipeline/agent events. ABOUTME: Field names mirror pipeline's jsonlLogEntry so one decoder serves NDJSON and activity.jsonl.

ABOUTME: Classifies a terminal run error into a human-first cause + remediation ABOUTME: so failure surfaces lead with the problem, not the plumbing (#492).

ABOUTME: Interviewer resolution — selects the human-gate handler from Config. ABOUTME: Split out of tracker.go so the root file stays focused on the API surface (#450).

ABOUTME: Library-facing helpers for resolving pipeline sources and run checkpoints. ABOUTME: Mirrors the CLI's bare-name and run-ID resolution for library consumers.

ABOUTME: RunManager owns multiple concurrent pipeline runs keyed by an external id. ABOUTME: Transport-neutral infra for services (Slack/web) that drive many runs at once (#479).

ABOUTME: Library API for dry-running a pipeline: returns the parsed graph, ABOUTME: BFS execution plan, and list of unreachable nodes.

ABOUTME: Reads a run's agent-authored status-update timeline from the activity ABOUTME: log — a clean, high-level "what got done" history, distinct from the firehose (#494).

ABOUTME: Test-fidelity analysis — flags duplicate/near-duplicate Go test bodies ABOUTME: so a verify gate can't bless byte-for-byte-copied "distinct" tests (#489).

ABOUTME: Built-in workflow catalog — embedded .dip files and name resolution. ABOUTME: Library consumers can list, read, and resolve workflows without shelling to the CLI.

Index

Examples

Constants

View Source
const (
	FormatDip = "dip" // Dippin format (current, default)
	FormatDOT = "dot" // DOT/Graphviz format (deprecated)
)

Pipeline format identifiers.

View Source
const (
	GitPreflightAuto    = pipeline.GitPreflightAuto
	GitPreflightOff     = pipeline.GitPreflightOff
	GitPreflightWarn    = pipeline.GitPreflightWarn
	GitPreflightRequire = pipeline.GitPreflightRequire
	GitPreflightInit    = pipeline.GitPreflightInit
)

GitPreflight values re-exported from pipeline for caller convenience.

View Source
const PinnedDippinVersion = "v0.49.0"

PinnedDippinVersion is the dippin-lang version from go.mod. Kept in sync with go.mod by TestPinnedDippinVersionMatchesGoMod.

Variables

View Source
var (
	// ErrRunKeyActive is returned by Start when a run with the same key is still active.
	ErrRunKeyActive = errors.New("a run with this key is already active")
	// ErrAtCapacity is returned by Start when the concurrency cap is reached. The
	// caller decides whether to queue, reject, or retry later.
	ErrAtCapacity = errors.New("run manager is at capacity")
)
View Source
var ErrGatewayRouteRefused = errors.New("gateway route refused: kind/provider combination unsupported or unknown")

ErrGatewayRouteRefused is returned by the strict resolver functions when a gateway URL is configured but the (kind, provider) pair is unsupported or the kind is unknown. Surfacing this as an error prevents the silent SDK-default fallback (e.g. openai-compat defaulting to openrouter.ai) that contradicts the documented fail-closed semantics of #276.

Functions

func ExportBundle

func ExportBundle(runDir, outPath string) error

ExportBundle writes a git bundle of the run directory's artifact repository to outPath. The bundle captures all commits and tags (including checkpoint tags) produced by WithGitArtifacts during the run. Users can restore the run with `git clone <bundlePath> <dir>` and inspect history with `git log`.

Returns an error if runDir is not a git repository, if git is not in PATH, or if `git bundle create` fails. The error wraps the command's stderr so callers can surface meaningful diagnostics.

The bundle file is portable — it can be copied to another machine and cloned there without network access. This is the recommended way for a remote factory-worker instance to hand a completed run back to the user.

func MostRecentRunID

func MostRecentRunID(workdir string) (string, error)

MostRecentRunID returns the run ID of the most recent run (by checkpoint timestamp) under workdir. Returns an error if no runs with valid checkpoints exist.

func NewLLMClient added in v0.46.0

func NewLLMClient(cfg Config) (*llm.Client, error)

buildClient creates an LLM client from environment variables with base URL support and retry middleware. If provider is non-empty, only that provider is configured (returns error if unknown). gatewayURL is the gateway root URL from Config.GatewayURL; gatewayKind is the matching Config.GatewayKind (empty = cf-aig default). Both are consulted after per-provider *_BASE_URL env vars and before the TRACKER_GATEWAY_URL / TRACKER_GATEWAY_KIND env-var fallbacks (see resolveProviderBaseURLWithGateway). NewLLMClient builds a standalone LLM client from Config for embedders that need model calls outside a pipeline run — e.g. request classification or routing. Only Provider, GatewayURL, and GatewayKind are consulted; Model, LLMClient, and the rest are ignored. It carries the same transport-retry middleware as a run's client. The caller owns Close().

func ResolveActivityLogPath

func ResolveActivityLogPath(runDir string) (path string, secureUsed bool)

ResolveActivityLogPath returns the on-disk location of the activity log for runDir. It prefers the integrity-protected secure path (#213) when present, falling back to <runDir>/activity.jsonl for pre-#213 runs and post-run snapshots. The returned secureUsed flag is true when the path came from the secure location — callers that validate the runtime sentinel should only do so in that case.

runID is derived from runDir's basename, matching the .tracker/runs/<runID> layout enforced by ResolveRunDir.

func ResolveBudgetLimits

func ResolveBudgetLimits(cfg pipeline.BudgetLimits, graph *pipeline.Graph) pipeline.BudgetLimits

ResolveBudgetLimits fills any zero field on cfg from the matching workflow-level default in graph.Attrs. Config values take precedence — the graph attrs are only consulted for fields the caller left unset. Returns the original cfg unchanged if graph is nil or has no attrs.

The graph-level keys consulted are max_total_tokens, max_cost_cents, max_wall_time, and stall_timeout, which the dippin adapter writes from WorkflowDefaults fields in v0.21.0+.

Exported so the tracker CLI can merge its --max-* flag values with workflow defaults without re-implementing the same logic.

func ResolveCheckpoint

func ResolveCheckpoint(workDir, runID string) (string, error)

ResolveCheckpoint finds the checkpoint file path for a given run ID under the working directory's .tracker/runs/<runID>/checkpoint.json layout. The runID argument may be a unique prefix of a real run ID.

Returns the path to checkpoint.json (relative to workDir if workDir is relative), or an error if the run is not found, the prefix is ambiguous, or the checkpoint file is missing.

This is the library equivalent of the CLI's `tracker -r <runID>` flag. Library consumers can set Config.ResumeRunID to have NewEngine resolve the checkpoint automatically.

func ResolveProviderBaseURL

func ResolveProviderBaseURL(provider string) string

ResolveProviderBaseURL returns the base URL a provider's HTTP client should use. Resolution order:

  1. The provider-specific env var (ANTHROPIC_BASE_URL, OPENAI_BASE_URL, GEMINI_BASE_URL, OPENAI_COMPAT_BASE_URL).
  2. TRACKER_GATEWAY_URL with a per-provider suffix appended; the suffix map is selected by TRACKER_GATEWAY_KIND (default cf-aig — Cloudflare AI Gateway conventions).
  3. Empty string, meaning the provider's SDK default.

Per-provider env vars always win over TRACKER_GATEWAY_URL.

**Lax variant.** This function returns the empty string for BOTH "no gateway configured" AND "gateway configured but routing refused." It is preserved for backward compatibility with library callers that existed before #276 added kind dispatch. New code on the adapter construction path MUST use ResolveProviderBaseURLStrict so that refuse-to-route surfaces as an error rather than a silent SDK-default fallback.

func ResolveProviderBaseURLStrict added in v0.36.0

func ResolveProviderBaseURLStrict(provider string) (string, error)

ResolveProviderBaseURLStrict is the fail-closed sibling of ResolveProviderBaseURL. It returns the same URL resolution but distinguishes "no gateway needed" (returns "", nil) from "gateway configured but routing refused" (returns "", ErrGatewayRouteRefused wrapped). Adapter constructors call this so a misconfigured gateway cannot silently leak requests to public SDK default endpoints.

func ResolveRunDir

func ResolveRunDir(workdir, runID string) (string, error)

ResolveRunDir finds the run directory under <workdir>/.tracker/runs matching runID by exact name or unique prefix. Returns an absolute path.

func SetDiagnosticLogger added in v0.48.0

func SetDiagnosticLogger(logger *slog.Logger)

SetDiagnosticLogger installs the sink for tracker's library-internal diagnostics — LLM adapter warnings, pricing warnings, backend subprocess notes, condition-variable warnings, autopilot fallbacks. These are NOT pipeline-lifecycle events (those ride Config.EventHandler); they are the call sites that previously wrote to the process-global logger/stderr.

By default the library discards them (a no-op sink) so an embedded control plane's structured logs stay clean. Pass a *slog.Logger to capture, redirect, or level-filter them; pass nil to reset to the no-op sink.

The sink is process-wide, not per-engine: several diagnostics originate in free functions (e.g. llm.EstimateCost) that cannot carry a per-run logger, so the library funnels all of them through one injectable sink. Genuine per-run, lifecycle observability belongs on Config.EventHandler / Config.AgentEvents instead.

func SortActivityByTime

func SortActivityByTime(entries []ActivityEntry)

SortActivityByTime sorts entries ascending by Timestamp.

Types

type ActivityEntry

type ActivityEntry struct {
	Timestamp time.Time
	// Source is the emitting subsystem: "pipeline" (engine), "agent" (LLM
	// session), "llm" (raw provider events), or "cli" (CLI-level audit).
	Source  string
	Type    string
	RunID   string
	NodeID  string
	Message string
	Error   string
	// Identity of the emitting LLM call / tool, and its payload text
	// (tool output or response preview). Set on agent and llm lines.
	Provider string
	Model    string
	ToolName string
	Content  string
	// BundleIdentity is the content-addressed identity of the .dipx bundle
	// the run executed against ("sha256:<hex>"); empty for a plain .dip run.
	BundleIdentity string

	// Decision fields — populated for decision_edge / decision_condition /
	// decision_outcome / decision_restart / conditional_fallthrough entries.
	EdgeFrom        string
	EdgeTo          string
	EdgeCondition   string
	EdgePriority    string
	ConditionMatch  *bool
	OutcomeStatus   string
	ContextSnapshot map[string]string
	ContextUpdates  map[string]string
	RestartCount    *int
	ClearedNodes    []string
	ConditionsTried []pipeline.ConditionEval
	// TokenInput / TokenOutput are the node's session token counts on a
	// decision entry — never run-cumulative (that is TotalTokens).
	TokenInput  int
	TokenOutput int
	// TokenCacheRead / TokenCacheWrite / TurnCostUSD are the per-turn agent
	// usage extras carried on agent turn_metrics / llm_finish entries (#508);
	// they mirror the same-named StreamEvent fields. Zero on decision entries.
	TokenCacheRead  int
	TokenCacheWrite int
	TurnCostUSD     float64

	// Cost snapshot fields — populated for cost_updated and budget_exceeded
	// entries. Run-cumulative, not per-node. Estimated is true when any
	// contributing session was heuristic-derived.
	TotalTokens    int
	TotalCostUSD   float64
	ProviderTotals map[string]pipeline.ProviderUsage
	WallElapsedMs  int64
	Estimated      bool

	// Truncation fields — populated for tool_output_truncated entries (#208).
	TruncStream   string
	TruncLimit    int
	TruncCaptured int
	TruncDropped  int
	TruncTotal    int

	// Marker fields — populated for tool_marker_missing entries (#210).
	MarkerPattern string
	MarkerTail    string
	MarkerError   string

	// RouteTail is populated for tool_route_missing entries (#212).
	RouteTail string

	// Auto-status fields — populated for auto_status_missing entries (#346).
	AutoStatusTail       string
	AutoStatusFailClosed bool

	// Override fields — populated for "validation_overridden" entries.
	// Mirror the wire-format fields written by the runtime's
	// jsonlLogEntry (see pipeline/events_jsonl.go): the gate that
	// produced the override, the label that selected the override
	// edge, who acted, and the subgraph_path when propagated up from
	// a child run. Empty for non-override entries.
	OverrideGate         string
	OverrideLabel        string
	OverrideActor        pipeline.Actor
	OverrideSubgraphPath []string

	// Gate lifecycle fields — populated for gate_opened / gate_resolved
	// entries (#509). GateID correlates the pair; NodeID identifies the gate
	// node on both. Open-time: GateMode, GateLabel, GatePrompt, GateChoices,
	// GateQuestions. Resolve-time: GateResponse, GateOutcome, GateActor,
	// GateTimedOut (plus Error when the gate failed to collect an answer).
	GateID        string
	GateMode      string
	GateLabel     string
	GatePrompt    string
	GateChoices   []string
	GateQuestions []pipeline.GateQuestion
	GateResponse  string
	GateOutcome   string
	GateActor     pipeline.Actor
	GateTimedOut  bool
}

ActivityEntry is a parsed line from activity.jsonl. Populate via ParseActivityLine — ActivityEntry is not itself a JSON-wire type because tracker has historically used two timestamp formats and time.Time's default unmarshal handles only RFC3339Nano.

Marshal/unmarshal contract: do not json.Marshal/json.Unmarshal ActivityEntry directly. Use ParseActivityLine and LoadActivityLog for decoding and map to your own wire type when encoding.

Field contract: the reader is lossless — every field the runtime writes to activity.jsonl is decoded here, and each field's Go name is the same as the matching StreamEvent field (tracker_events.go), so replaying a finished run from the audit log and following it live over NDJSON read the same names. Timestamp is always set — a line whose ts does not parse is rejected outright. Every other field is optional: a line carries only the fields its event type emits, so a zero value means "this line does not carry it". ConditionMatch and RestartCount are pointers precisely so a consumer can tell false/0 from absent.

func LoadActivityLog

func LoadActivityLog(runDir string) ([]ActivityEntry, error)

LoadActivityLog reads and parses the activity log for runDir, preferring the integrity-protected secure path with fallback to the legacy <runDir>/activity.jsonl. Returns (nil, nil) if neither location has a file. Malformed lines are skipped. Sentinel-stripped lines that don't parse as JSON are dropped silently — callers needing tamper-detection granularity should use ScanActivityLog (or the Diagnose path).

func ParseActivityLine

func ParseActivityLine(line string) (ActivityEntry, bool)

ParseActivityLine decodes a single JSONL line. Returns (zero, false) on any parse error, including an unparseable timestamp. Unknown keys are ignored: a line written by a newer runtime still parses, minus the fields this build does not know. The decode target and the per-group field copies live in tracker_activity_payload.go.

type ActivityError

type ActivityError struct {
	Timestamp time.Time `json:"ts"`
	NodeID    string    `json:"node_id,omitempty"`
	Message   string    `json:"message"`
}

ActivityError is an error entry extracted from the activity log.

type ActivityLogScan

type ActivityLogScan struct {
	Path          string
	SecureUsed    bool
	Entries       []ActivityEntry
	InjectedLines int
	TotalLines    int
	SentinelLines int
}

ActivityLogScan is the structured result of reading an activity log. Path is the on-disk location read; SecureUsed reflects whether the integrity-protected secure log was the source; InjectedLines counts non-sentinel lines observed when reading from the secure path (always 0 when SecureUsed is false — legacy/snapshot files don't carry the runtime sentinel, so absence is not a signal).

func ScanActivityLog

func ScanActivityLog(runDir string) (*ActivityLogScan, error)

ScanActivityLog is LoadActivityLog with tamper-detection counters exposed for callers (e.g. Diagnose) that need to surface injection signals. Lines without the runtime sentinel prefix in the secure file count toward InjectedLines; the line is still parsed best-effort so its content is visible to forensics.

type AuditConfig

type AuditConfig struct {
	// LogWriter receives non-fatal warnings (unreadable activity.jsonl
	// in a run directory, etc.). Nil is treated as io.Discard so
	// embedded library callers do not see warnings on os.Stderr. The
	// tracker CLI sets this to io.Discard for user-facing commands.
	LogWriter io.Writer
}

AuditConfig configures an Audit() or ListRuns() call.

type AuditReport

type AuditReport struct {
	RunID string `json:"run_id"`
	// Status is one of:
	//   - "success"
	//   - "fail"
	//   - "budget_exceeded"
	//   - "validation_overridden"
	//   - "paused_billing"
	// The enum is open — future minor releases may add new values. Consumers
	// should use StatusClass for stable {succeeded|failed|paused} bucketing.
	// See classifyStatus for the resolution algorithm.
	Status string `json:"status"`
	// StatusClass is one of "succeeded", "failed", or "paused" — stable
	// companion to Status for downstream consumers that need bucket
	// categorization that survives future enum extensions. "paused" flags the
	// recoverable, resumable paused_billing terminal (#514); everything else is
	// classified via pipeline.TerminalStatus(Status).IsSuccess().
	StatusClass string `json:"status_class"`
	// TotalDuration is encoded as integer nanoseconds in JSON
	// ("total_duration_ns"), not as a duration string.
	TotalDuration   time.Duration   `json:"total_duration_ns"`
	Timeline        []TimelineEntry `json:"timeline"`
	Retries         []RetryRecord   `json:"retries,omitempty"`
	Errors          []ActivityError `json:"errors,omitempty"`
	Recommendations []string        `json:"recommendations,omitempty"`
	// CompletedNodes is the number of completed nodes recorded in checkpoint.json.
	CompletedNodes int `json:"completed_nodes"`
	// RestartCount is the checkpoint restart counter for the run.
	RestartCount int `json:"restart_count"`
	// CheckpointTimestamp is the last checkpoint write time.
	CheckpointTimestamp time.Time `json:"checkpoint_timestamp"`
	// BundleIdentity is the content-addressed identity ("sha256:<hex>") of
	// the .dipx bundle the run was executed against. Read from the run's
	// checkpoint. Empty for runs from a plain .dip file.
	BundleIdentity string `json:"bundle_identity,omitempty"`
	// ValidationOverrides is populated when one or more override edges were
	// traversed during the run. Sourced from activity-log
	// EventValidationOverridden entries (chronological order); falls back to
	// Checkpoint.ValidationOverrides when the activity log carries no
	// override entries. Empty for runs with no override edges.
	ValidationOverrides []pipeline.OverrideDetail `json:"validation_overrides,omitempty"`
	// OverrideCount is len(ValidationOverrides). Kept as its own field so
	// thin consumers can read it without unmarshaling the slice.
	OverrideCount int `json:"override_count,omitempty"`
}

AuditReport is the structured result of Audit().

func Audit

func Audit(ctx context.Context, runDir string) (*AuditReport, error)

Audit reads checkpoint.json and activity.jsonl under runDir and returns a structured report.

The runDir argument must be a trusted path — Audit reads checkpoint.json and activity.jsonl directly under it. For user-supplied input, resolve the path via ResolveRunDir or use MostRecentRunID first, which enforce the .tracker/runs/<runID> layout.

ctx is checked at entry so a caller that passes an already-cancelled context gets an immediate error instead of silent work. Full cancellation mid-parse would require threading ctx through pipeline.LoadCheckpoint and LoadActivityLog, which is out of scope today (both are fast and bounded). Nil is coalesced to context.Background().

Audit does not accept AuditConfig — it emits no warnings to suppress. Use ListRuns + AuditConfig{LogWriter} for bulk enumeration where the summary builder may skip unreadable activity logs.

type BudgetHalt

type BudgetHalt struct {
	TotalTokens   int     `json:"total_tokens"`
	TotalCostUSD  float64 `json:"total_cost_usd"`
	WallElapsedMs int64   `json:"wall_elapsed_ms"`
	Message       string  `json:"message"`
}

BudgetHalt holds information about a budget halt detected in the activity log.

type CheckDetail

type CheckDetail struct {
	Status  CheckStatus `json:"status"` // "ok" | "warn" | "error" | "hint"
	Message string      `json:"message"`
	Hint    string      `json:"hint,omitempty"`
}

CheckDetail is one sub-line within a CheckResult — used for per-item status lines (per-provider, per-binary, per-subdirectory).

type CheckResult

type CheckResult struct {
	Name    string        `json:"name"`
	Status  CheckStatus   `json:"status"` // "ok" | "warn" | "error" | "skip"
	Message string        `json:"message,omitempty"`
	Hint    string        `json:"hint,omitempty"`
	Details []CheckDetail `json:"details,omitempty"`
}

CheckResult is one section of a DoctorReport.

type CheckStatus

type CheckStatus string

CheckStatus is the status of a CheckResult or CheckDetail. Enum-like typed string so consumers can switch-exhaust. "hint" is only valid on CheckDetail.Status (informational sub-items such as optional providers not configured).

const (
	CheckStatusOK    CheckStatus = "ok"
	CheckStatusWarn  CheckStatus = "warn"
	CheckStatusError CheckStatus = "error"
	CheckStatusSkip  CheckStatus = "skip"
	CheckStatusHint  CheckStatus = "hint"
)

CheckStatus values.

type Config

type Config struct {
	WorkingDir    string // default: os.Getwd()
	CheckpointDir string // checkpoint file path (checkpoint.json); default: empty (engine auto-generates)
	ResumeRunID   string // optional: resume a previous run by ID or unique prefix; resolved via ResolveCheckpoint
	ArtifactDir   string // default: empty (engine auto-generates)
	// GitArtifacts, when true, makes the artifact dir a git repo and commits
	// after every terminal node outcome (the basis for branch-per-run / PR
	// delivery and portable ExportBundle history). Requires git in PATH and is
	// a no-op unless ArtifactDir is set. Off by default.
	GitArtifacts bool
	Format       string                        // "dip" (default), "dot" (deprecated); empty = auto-detect
	Model        string                        // default: env or claude-sonnet-4-6; graph-level attrs take precedence
	Provider     string                        // default: auto-detect from env
	RetryPolicy  string                        // "none" (default), "standard", "aggressive"; graph-level attrs take precedence
	EventHandler pipeline.PipelineEventHandler // optional: live pipeline events
	AgentEvents  agent.EventHandler            // optional: live agent session events
	// LLMTrace attaches a raw-trace observer to the auto-created client or a
	// *llm.Client passed as LLMClient. A custom agent.Completer carries no
	// observable transport, so LLMTrace is a no-op there (as is TokenTracker).
	LLMTrace  llm.TraceObserver // optional: raw LLM trace events
	LLMClient agent.Completer   // optional: override auto-created client
	// TokenTracker optionally injects the per-provider token/cost tracker instead
	// of the engine creating its own. An in-process transport that renders spend
	// (the TUI) shares one tracker between its view model and the engine. The
	// engine attaches it idempotently — re-adding the same tracker to a supplied
	// *llm.Client is skipped, so usage is never double-counted.
	TokenTracker *llm.TokenTracker
	Context      map[string]string // optional: initial pipeline context
	Params       map[string]string // optional: override declared workflow params (keys without "params." prefix)
	// Subgraphs are pre-loaded child graphs keyed by subgraph_ref, for a graph
	// with subgraph nodes; nil/empty for flat pipelines. See NewEngineFromGraph.
	Subgraphs map[string]*pipeline.Graph
	Backend   string // "native" (default), "claude-code", "acp"; selects agent backend
	// ToolSafety overrides the tool-handler security config (denylist/allowlist,
	// output limits, env passthrough); nil uses the registry defaults.
	ToolSafety  *handlers.ToolHandlerConfig
	Autopilot   string                // "" (interactive), "lax", "mid", "hard", "mentor"; LLM-driven gate decisions
	AutoApprove bool                  // auto-approve all human gates with default/first option
	Budget      pipeline.BudgetLimits // configures pipeline-level token, cost, and wall-time ceilings
	// GatewayURL is the root URL of a Cloudflare AI Gateway (or any compatible
	// proxy). When non-empty it is used as the base for all provider URLs, with
	// the per-provider suffix appended (e.g. "<gateway>/anthropic"). A
	// per-provider *_BASE_URL env var always takes precedence over GatewayURL so
	// library callers can still override individual providers. The TRACKER_GATEWAY_URL
	// env var is the fallback when GatewayURL is empty.
	GatewayURL string
	// GatewayKind selects the path convention used with GatewayURL (or its
	// TRACKER_GATEWAY_URL env-var fallback). Empty or GatewayKindCFAIG
	// (default, backcompat) routes via Cloudflare AI Gateway conventions:
	// /anthropic, /openai, /google-ai-studio, /compat. GatewayKindBedrock
	// targets the 2389 bedrock-gateway Worker which uses native SDK paths.
	// The TRACKER_GATEWAY_KIND env var is the fallback when GatewayKind is
	// empty. See ResolveProviderBaseURL.
	GatewayKind GatewayKind
	// Interviewer optionally injects a custom in-process human-gate handler.
	// This is the seam for interactive transports (TUI, Slack, web, mobile):
	// the transport implements handlers.Interviewer and, optionally, the richer
	// handlers.FreeformInterviewer / LabeledFreeformInterviewer /
	// InterviewInterviewer extensions plus the optional Actor() / Cancel() /
	// ContextSetter side-interfaces — the human handler upgrades via type
	// assertion and picks the richest supported mode. When set, it takes
	// precedence over AutoApprove, WebhookGate, and Autopilot. Nil is a no-op.
	Interviewer handlers.Interviewer
	WebhookGate *WebhookGateConfig // optional: post human gates to an HTTP webhook and wait for callback
	// BundleIdentity is the content-addressed identity ("sha256:<hex>") of
	// the .dipx bundle this run was loaded from. Stamped onto every emitted
	// PipelineEvent and persisted to the checkpoint for resume verification.
	// Empty (the default) is a no-op and matches plain .dip behavior.
	//
	// Callers that build their own JSONLEventHandler should also call
	// activityLog.SetBundleIdentity(cfg.BundleIdentity) so agent/llm writes
	// outside the engine event chain carry the same provenance.
	BundleIdentity string
	// Git configures the v0.29.0 git preflight check. Nil = auto, which
	// respects the workflow's `requires:` block. See GitConfig.
	Git *GitConfig
	// SteeringChan optionally injects mid-run context updates from an external
	// supervisor (a chat "steer" command, a web control, a manager loop). Each
	// map sent on it is merged into the pipeline context between node
	// executions, so steered values are visible to the next node's edge
	// selection and prompt expansion. Values are namespaced by the sender
	// (e.g. "steer.guidance"); a workflow references them like any context key.
	// Nil disables steering. The channel is drained non-blockingly — sends never
	// block the engine, and updates surface at the next inter-node boundary.
	SteeringChan <-chan map[string]string
}

Config controls pipeline execution. All fields are optional. Zero-value Config uses environment variables for LLM credentials, the current working directory, and auto-generated run directories.

type CostReport

type CostReport struct {
	TotalUSD   float64
	ByProvider map[string]llm.ProviderCost
	LimitsHit  []string
}

CostReport summarizes spend for a pipeline run. TotalUSD is the sum of ByProvider[*].USD. LimitsHit names the budget dimensions that halted the run (empty when the run completed normally).

type DiagnoseConfig

type DiagnoseConfig struct {
	// LogWriter receives non-fatal parse/read warnings — specifically
	// malformed status.json content (one warning per bad file) and
	// bufio.Scanner errors while reading activity.jsonl (e.g. lines
	// exceeding the 1 MB buffer limit, I/O failures). Nil is treated
	// as io.Discard so library callers do not see stray warnings on
	// os.Stderr. The tracker CLI sets this to io.Discard for user-
	// facing commands.
	LogWriter io.Writer
}

DiagnoseConfig configures a Diagnose() run.

type DiagnoseReport

type DiagnoseReport struct {
	RunID          string      `json:"run_id"`
	CompletedNodes int         `json:"completed_nodes"`
	BudgetHalt     *BudgetHalt `json:"budget_halt,omitempty"`
	// ValidationOverrides is the list of override edges traversed during the run.
	// Sourced from activity log first, with checkpoint fallback for runs whose
	// activity log is missing (legacy / archived). Empty for runs with no
	// override edges. Populated for every terminal status (success, fail,
	// budget_exceeded, validation_overridden) so override forensics survive
	// failure-after-override scenarios. Per spec §9.4 the override section in
	// `tracker diagnose` is informational only — override is NOT a failure and
	// does NOT raise a Suggestion of its own.
	ValidationOverrides []pipeline.OverrideDetail `json:"validation_overrides,omitempty"`
	// OverrideCount is len(ValidationOverrides). Kept as its own field so
	// JSON consumers can branch without iterating the slice.
	OverrideCount int           `json:"override_count,omitempty"`
	Failures      []NodeFailure `json:"failures"`
	Suggestions   []Suggestion  `json:"suggestions"`
}

DiagnoseReport is the structured output of Diagnose / DiagnoseMostRecent.

func Diagnose

func Diagnose(ctx context.Context, runDir string, opts ...DiagnoseConfig) (*DiagnoseReport, error)

Diagnose analyzes a run directory and returns a structured report.

The runDir argument must be a trusted path — Diagnose reads checkpoint.json, activity.jsonl, and every <nodeID>/status.json under it. For user-supplied input, resolve the path via ResolveRunDir or DiagnoseMostRecent first, which enforce the .tracker/runs/<runID> layout.

If ctx is cancelled mid-parse, Diagnose returns ctx.Err() — a partial report is never returned as a success, so callers using deadlines can distinguish complete from truncated analysis. A nil ctx is treated as context.Background() (no cancellation possible).

Example
package main

import (
	"context"
	"fmt"

	tracker "github.com/2389-research/tracker"
)

func main() {
	report, err := tracker.Diagnose(context.Background(), "testdata/runs/failed")
	if err != nil {
		fmt.Println("diagnose failed")
		return
	}
	if len(report.Failures) == 0 {
		fmt.Println("no failures")
		return
	}
	fmt.Println("failed node:", report.Failures[0].NodeID)
	fmt.Println("retry count:", report.Failures[0].RetryCount)
}
Output:
failed node: Build
retry count: 2

func DiagnoseMostRecent

func DiagnoseMostRecent(ctx context.Context, workdir string, opts ...DiagnoseConfig) (*DiagnoseReport, error)

DiagnoseMostRecent finds the most recent run under workdir and diagnoses it.

type DoctorConfig

type DoctorConfig struct {
	// WorkDir is the working directory to check. If empty, os.Getwd() is used.
	WorkDir string
	// Backend is the agent backend ("", "native", "claude-code"). When
	// "claude-code", a missing claude binary is a hard error.
	Backend string
	// ProbeProviders, when true, makes a minimal network call to each
	// configured provider to verify auth. Default false — key presence only.
	ProbeProviders bool
	// PipelineFile, when non-empty, adds a "Pipeline File" check that parses
	// and validates the given .dip / .dot file.
	PipelineFile string
	// contains filtered or unexported fields
}

DoctorConfig configures a Doctor() run.

type DoctorOption

type DoctorOption func(*DoctorConfig)

DoctorOption configures a Doctor run via a functional option.

func WithGitConfig

func WithGitConfig(policy GitPreflight, allowInit bool) DoctorOption

WithGitConfig sets the git preflight policy considered by the Git Requires check. Library callers that don't set this get GitPreflightAuto behavior (respect workflow `requires:`). CLI doctor mode passes --git/--allow-init through this option.

func WithVersionInfo

func WithVersionInfo(version, commit string) DoctorOption

WithVersionInfo attaches a tracker version and commit hash for display in the "Version Compatibility" check. CLI callers populate these from build-time ldflags; library callers typically do not need this.

type DoctorReport

type DoctorReport struct {
	Checks   []CheckResult `json:"checks"`
	OK       bool          `json:"ok"`
	Warnings int           `json:"warnings"`
	Errors   int           `json:"errors"`
}

DoctorReport is the structured result of a Doctor() call.

func Doctor

func Doctor(ctx context.Context, cfg DoctorConfig, opts ...DoctorOption) (*DoctorReport, error)

Doctor runs a suite of preflight checks and returns a structured report.

By default Doctor makes no network calls: provider configuration is detected via env-var presence and basic format validation. Set cfg.ProbeProviders = true to additionally make a 1-token API call per provider to verify auth. The CLI's "tracker doctor" command sets that flag; library callers should leave it false unless they specifically want live credential verification.

Provider probes and binary version lookups honor ctx: cancelling the context aborts in-flight checks. A nil context is treated as context.Background().

Write side effects (gitignore fix-up, workdir creation prompts) are NOT performed by Doctor — callers inspect the report and apply any fixes themselves.

Example
package main

import (
	"context"
	"fmt"
	"os"

	tracker "github.com/2389-research/tracker"
)

func main() {
	workDir, err := os.MkdirTemp("", "tracker-example-doctor-*")
	if err != nil {
		fmt.Println("doctor failed")
		return
	}
	defer os.RemoveAll(workDir)

	report, err := tracker.Doctor(context.Background(), tracker.DoctorConfig{
		WorkDir: workDir,
	})
	if err != nil {
		fmt.Println("doctor failed")
		return
	}
	fmt.Println("checks:", len(report.Checks) > 0)
}
Output:
checks: true

type DuplicateTestGroup added in v0.47.0

type DuplicateTestGroup struct {
	Kind  string         `json:"kind"` // "identical" | "near-identical"
	Tests []TestLocation `json:"tests"`
}

DuplicateTestGroup is a set of test functions sharing a body.

type Engine

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

Engine wraps pipeline.Engine with auto-wired internals.

func NewEngine

func NewEngine(source string, cfg Config) (*Engine, error)

NewEngine parses a pipeline source (.dip preferred, DOT deprecated), auto-wires all internals, and returns an Engine. Format is auto-detected from content if Config.Format is empty: sources starting with "digraph" or "strict digraph" are treated as DOT, everything else as .dip. The caller must call Close() when done to release resources.

This wrapper exists for backward compatibility: it uses context.Background() for the v0.29.0 git preflight. New callers that want to support cancellation of the preflight (especially with `--git=init` which has a `git init` side effect) should use NewEngineWithContext instead.

func NewEngineFromGraph added in v0.46.0

func NewEngineFromGraph(ctx context.Context, graph *pipeline.Graph, cfg Config) (*Engine, error)

NewEngineFromGraph assembles an Engine from an already-parsed graph, skipping source parsing. Use it when the caller has loaded the graph itself and, for a pipeline with subgraph nodes, resolved the subgraph_ref files into Config.Subgraphs — as the CLI does. Otherwise identical to NewEngineWithContext (validate → git preflight → resume → client → assemble).

func NewEngineWithContext

func NewEngineWithContext(ctx context.Context, source string, cfg Config) (*Engine, error)

NewEngineWithContext is the context-aware form of NewEngine. The supplied ctx threads into the v0.29.0 git preflight check; a canceled context aborts preflight (including the `--git=init` `git init` side effect) rather than letting it complete before Engine.Run(ctx) observes the cancellation. tracker.Run(ctx, ...) calls this form so library callers who pass a real ctx get end-to-end cancellation coverage.

func (*Engine) Close

func (e *Engine) Close() error

Close releases resources. Must be called if the engine was created with NewEngine. Safe for concurrent use; idempotent.

func (*Engine) Run

func (e *Engine) Run(ctx context.Context) (*Result, error)

Run executes the pipeline to completion.

func (*Engine) TokenTracker added in v0.46.0

func (e *Engine) TokenTracker() *llm.TokenTracker

TokenTracker returns the per-provider token/cost tracker attached to this engine's run. A transport that renders spend in-process (e.g. the TUI status bar and per-node cost) shares this rather than reconstructing usage from the event stream. Always non-nil for a successfully constructed engine (it reports zeros when no LLM client is attached, e.g. --backend claude-code without keys).

type FailureCause added in v0.47.0

type FailureCause struct {
	Kind      string // "billing" | "auth" | "rate_limit" | "context_length" | "network" | "timeout" | "config" | "content_filter" | "generic"
	Icon      string
	Title     string // the headline — the most important, actionable thing
	Detail    string // the underlying cause (e.g. the provider message), may be ""
	NextSteps string // what the user should do, may be ""
}

FailureCause is a human-first explanation of why a run failed: an icon + title to lead with, the underlying cause, and the concrete next step. Renderers show Title first and keep node/stage context secondary — the opposite of the raw "handler error at node X" wrapper.

func ClassifyFailure added in v0.47.0

func ClassifyFailure(err error) FailureCause

ClassifyFailure maps a terminal run error to a FailureCause. It unwraps the node/handler wrappers to find the real cause (a typed provider error, a billing signal, etc.) and returns a generic "Run failed" carrying the raw message when nothing more specific matches. err==nil yields the zero value.

type GatewayKind added in v0.36.0

type GatewayKind string

GatewayKind selects the path convention used when TRACKER_GATEWAY_URL is set. The default (cf-aig) matches Cloudflare AI Gateway's per-provider subpath convention; bedrock targets the 2389 bedrock-gateway Worker which uses native SDK URL paths.

See docs/superpowers/specs/2026-06-01-issue-274-bedrock-gateway-integration-design.md.

const (
	// GatewayKindCFAIG routes via Cloudflare AI Gateway path conventions:
	// /anthropic, /openai, /google-ai-studio, /compat. Default.
	GatewayKindCFAIG GatewayKind = "cf-aig"

	// GatewayKindBedrock routes via the 2389 bedrock-gateway Worker which
	// translates SDK requests to AWS Bedrock Converse. Uses native SDK
	// URL conventions: empty suffix for Anthropic, /v1 for OpenAI and
	// Gemini. openai-compat is not supported on this gateway.
	GatewayKindBedrock GatewayKind = "bedrock"
)

type GitConfig

type GitConfig struct {
	Preflight GitPreflight
	AllowInit bool
}

GitConfig configures the git preflight check that runs before any node executes. Zero value (or nil *GitConfig on Config.Git) resolves to GitPreflightAuto, which respects the workflow's `requires:` block.

AllowInit is required when Preflight == GitPreflightInit and stdin is not a TTY — it is the second safety latch on automatic `git init`.

type GitPreflight

type GitPreflight = pipeline.GitPreflight

GitPreflight is the resolved preflight policy that controls the v0.29.0 git environment check. Type alias to pipeline.GitPreflight so callers don't have to import the pipeline package for this single value.

func ResolveGitConfig

func ResolveGitConfig(cfg Config) (GitPreflight, bool)

ResolveGitConfig returns the (policy, allowInit) pair to apply for this run, considering Config.Git. The zero value resolves to (auto, false).

type ManagedRun added in v0.46.0

type ManagedRun struct {
	Key     string // caller-chosen external id (e.g. a Slack thread_ts)
	WorkDir string // isolated working directory for this run
	// contains filtered or unexported fields
}

ManagedRun is a single run owned by a RunManager. Safe for concurrent reads.

func (*ManagedRun) Done added in v0.46.0

func (m *ManagedRun) Done() <-chan struct{}

Done is closed when the run reaches a terminal state.

func (*ManagedRun) Result added in v0.46.0

func (m *ManagedRun) Result() (*Result, error)

Result returns the run's result and error once it has finished. Before the run is Done, it returns (nil, nil). After Done it mirrors tracker.Run: a run that produced a terminal result has a non-nil *Result (with RunID and a terminal Status) — accompanied by a non-nil error for handler-error, strict-failure, paused (see RunPaused), or cancelled exits. A non-nil error therefore does not by itself mean the run failed; read State(). Only an init/invariant failure before any terminal result yields (nil, err).

func (*ManagedRun) ResumeRunID added in v0.47.0

func (m *ManagedRun) ResumeRunID() string

ResumeRunID returns the tracker run id to resume from when the run stopped in a recoverable paused state (State() == RunPaused), else "". Pass it as Config.ResumeRunID (with the same Config.CheckpointDir and this run's WorkDir) to continue from the paused node. Capture it before Forget() drops the run.

func (*ManagedRun) RunID added in v0.46.0

func (m *ManagedRun) RunID() string

RunID returns the tracker run id once the run has finished, else "".

func (*ManagedRun) State added in v0.46.0

func (m *ManagedRun) State() RunState

State returns the run's current lifecycle state.

type NDJSONWriter

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

NDJSONWriter is a thread-safe writer that serializes StreamEvents line by line onto an io.Writer. Library consumers use it to produce the same stream as the tracker CLI's --json mode.

Backpressure note: Write holds an internal mutex for the duration of the underlying io.Writer.Write call. When three handler sources (pipeline, agent, LLM trace) share one writer, a slow backing writer serializes handler callbacks across those sources. If the backing writer can block (network socket, pipe), wrap it in a bufio.Writer or a channel-backed forwarder to decouple producers from the slow sink.

func NewNDJSONWriter

func NewNDJSONWriter(w io.Writer) *NDJSONWriter

NewNDJSONWriter returns a new writer backed by w.

Example
package main

import (
	"bytes"
	"fmt"

	tracker "github.com/2389-research/tracker"
)

func main() {
	var buf bytes.Buffer
	w := tracker.NewNDJSONWriter(&buf)
	_ = w.Write(tracker.StreamEvent{
		Timestamp: "2026-04-17T10:00:00.000Z",
		Source:    "pipeline",
		Type:      "pipeline_started",
		RunID:     "run-123",
	})
	fmt.Println(buf.Len() > 0)
}
Output:
true

func (*NDJSONWriter) AgentHandler

func (s *NDJSONWriter) AgentHandler() agent.EventHandler

AgentHandler returns an agent.EventHandler that writes agent events to this stream. Panics in the underlying writer are recovered (see PipelineHandler).

func (*NDJSONWriter) PipelineHandler

func (s *NDJSONWriter) PipelineHandler() pipeline.PipelineEventHandler

PipelineHandler returns a pipeline.PipelineEventHandler that writes events to this stream. Panics in the underlying writer are recovered and logged to os.Stderr once (per writer instance) so a misbehaving sink cannot crash the pipeline goroutine.

func (*NDJSONWriter) TraceObserver

func (s *NDJSONWriter) TraceObserver() llm.TraceObserver

TraceObserver returns an llm.TraceObserver that writes trace events to this stream. Panics in the underlying writer are recovered (see PipelineHandler).

func (*NDJSONWriter) Write

func (s *NDJSONWriter) Write(evt StreamEvent) error

Write serializes evt as a JSON line. Safe to call from multiple goroutines. Returns a non-nil error if marshalling or writing to the underlying io.Writer fails, including short writes (io.Writer.Write may legally return n < len(data) with a nil error). The first write error is also logged to os.Stderr once so long-running callers that ignore the return value still surface it.

type NodeFailure

type NodeFailure struct {
	NodeID  string `json:"node_id"`
	Outcome string `json:"outcome"`
	Handler string `json:"handler,omitempty"`
	// Duration is the elapsed time for the most recent attempt of the node.
	// It is encoded as integer nanoseconds in JSON ("duration_ns"), not
	// as a duration string.
	Duration time.Duration `json:"duration_ns,omitempty"`
	// RetryCount is the number of stage_failed events observed for this node
	// — i.e., the total failure count, not "retries beyond the first attempt."
	// A node that failed once (no retry) has RetryCount == 1.
	RetryCount int `json:"retry_count,omitempty"`
	// IdenticalRetries is true when every stage_failed event had the same
	// error/tool_error signature — a deterministic bug, not a flaky one.
	IdenticalRetries bool     `json:"identical_retries,omitempty"`
	Stdout           string   `json:"stdout,omitempty"`
	Stderr           string   `json:"stderr,omitempty"`
	Errors           []string `json:"errors,omitempty"`
}

NodeFailure captures everything known about a failed node.

type PlanStep

type PlanStep struct {
	Step   int       `json:"step"`
	NodeID string    `json:"node_id"`
	Edges  []SimEdge `json:"edges,omitempty"`
}

PlanStep is one step in an execution plan.

type Result

type Result struct {
	RunID string
	// Status carries the run's terminal status. Known values today:
	//   - "success"
	//   - "fail"
	//   - "budget_exceeded"
	//   - "validation_overridden"
	//   - "paused_billing" — recoverable provider credit/quota exhaustion: the
	//     checkpoint is saved and in-flight work preserved, so the run resumes
	//     from the paused node (Config.ResumeRunID / `tracker -r`) once credits
	//     are topped up. Not a failure; offer resume, not a from-scratch redo.
	// The enum is OPEN — future minor releases may add new values, so never
	// switch exhaustively on the raw string. Use
	// pipeline.TerminalStatus(r.Status).IsSuccess() to classify (fail-closed).
	Status           string
	CompletedNodes   []string
	Context          map[string]string
	EngineResult     *pipeline.EngineResult
	Trace            *pipeline.Trace      // full execution trace (nodes, timing, stats)
	TokensByProvider map[string]llm.Usage // per-provider token totals
	ToolCallsByName  map[string]int       // tool call counts by name
	Cost             *CostReport          // per-provider cost rollup; nil when no usage recorded
	// ArtifactRunDir is the run-specific artifact directory (e.g.
	// "<artifactDir>/<runID>"). Populated when WithArtifactDir is set via
	// Config.ArtifactDir. Pass this to ExportBundle to create a portable
	// git bundle of the run's history.
	ArtifactRunDir string
	// BundlePath is the path of the exported git bundle. Populated only when
	// ExportBundle is invoked by the caller after Run completes.
	BundlePath string
	// BundleIdentity is the content-addressed identity ("sha256:<hex>") of
	// the .dipx bundle the run was loaded from, mirrored from Config.BundleIdentity
	// for the caller's convenience. Empty for plain .dip runs.
	BundleIdentity string
	// ValidationOverrides is the list of override edges traversed during the run.
	// Empty for runs with no override edges. Populated for every terminal status
	// (including fail and budget_exceeded) so forensics see overrides even when
	// failure dominates.
	ValidationOverrides []pipeline.OverrideDetail
}

Result contains the outcome of a pipeline execution.

func Run

func Run(ctx context.Context, source string, cfg Config) (*Result, error)

Run parses a pipeline source, auto-wires all internals, executes, and returns the result. This is the one-call convenience function. It handles Close() automatically.

type RetryRecord

type RetryRecord struct {
	NodeID   string `json:"node_id"`
	Attempts int    `json:"attempts"`
}

RetryRecord records how many times a node was retried.

type RunEstimate added in v0.46.0

type RunEstimate struct {
	Steps       int      `json:"steps"`        // execution-plan length
	AgentNodes  int      `json:"agent_nodes"`  // LLM agent nodes
	Models      []string `json:"models"`       // distinct models, sorted
	LowUSD      float64  `json:"low_usd"`      // ~1 turn per agent node
	ExpectedUSD float64  `json:"expected_usd"` // a typical fraction of max turns
	HighUSD     float64  `json:"high_usd"`     // max turns per agent node
}

RunEstimate is a rough pre-run cost/scale ballpark. It is an ESTIMATE: actual cost depends on how many turns each agent uses and how many times loops run, which can't be known statically — hence the wide Low..High spread. Use it to set expectations and a budget ceiling, not as a precise quote.

func EstimateRun added in v0.46.0

func EstimateRun(ctx context.Context, source string) (*RunEstimate, error)

EstimateRun parses source and returns a rough cost/scale estimate. It runs the same static simulation as Simulate, then prices each agent node's model.

type RunManager added in v0.46.0

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

RunManager owns multiple concurrent pipeline runs keyed by an external id. It provides the mechanism (isolation, lifecycle, an optional capacity cap) and leaves admission policy (queue vs reject at capacity) to the caller. Safe for concurrent use.

func NewRunManager added in v0.46.0

func NewRunManager(opts ...RunManagerOption) *RunManager

NewRunManager creates a RunManager.

func (*RunManager) Cancel added in v0.46.0

func (rm *RunManager) Cancel(key string) bool

Cancel stops the run for key by cancelling its context. Returns false if no such run is tracked. Cancelling an already-finished run is a no-op.

func (*RunManager) Forget added in v0.46.0

func (rm *RunManager) Forget(key string) bool

Forget drops a finished run from the manager's tracking. Returns false if the run is unknown or still active (active runs cannot be forgotten; Cancel first).

func (*RunManager) Get added in v0.46.0

func (rm *RunManager) Get(key string) (*ManagedRun, bool)

Get returns the managed run for key, if present.

func (*RunManager) List added in v0.46.0

func (rm *RunManager) List() []*ManagedRun

List returns all runs the manager currently tracks, ordered by key.

func (*RunManager) Start added in v0.46.0

func (rm *RunManager) Start(ctx context.Context, key, source string, cfg Config) (*ManagedRun, error)

Start launches source under cfg as a new run keyed by key. The run executes in its own goroutine; ctx bounds its lifetime, so pass a long-lived context (not a short request context) and use Cancel to stop a single run. Returns ErrRunKeyActive if a run with the same key is still active, or ErrAtCapacity if the concurrency cap is reached — the caller decides whether to queue or reject. When cfg.WorkingDir is empty and a workdir base is configured, an isolated per-key directory is created.

type RunManagerOption added in v0.46.0

type RunManagerOption func(*RunManager)

RunManagerOption configures a RunManager.

func WithMaxConcurrent added in v0.46.0

func WithMaxConcurrent(n int) RunManagerOption

WithMaxConcurrent caps the number of simultaneously-active runs. When the cap is reached, Start returns ErrAtCapacity. A value <= 0 means unbounded.

func WithWorkDirBase added in v0.46.0

func WithWorkDirBase(dir string) RunManagerOption

WithWorkDirBase sets a base directory under which each run without an explicit Config.WorkingDir gets its own isolated subdirectory (base/<sanitized-key>). Isolated workdirs keep concurrent runs from colliding on run-state files.

type RunState added in v0.46.0

type RunState string

RunState is the lifecycle state of a managed run.

const (
	RunStarting  RunState = "starting"
	RunRunning   RunState = "running"
	RunSucceeded RunState = "succeeded"
	RunFailed    RunState = "failed"
	RunCanceled  RunState = "canceled"
	// RunPaused is a run that stopped in a recoverable, resumable terminal —
	// today only the engine's paused_billing halt (#487): the checkpoint is
	// saved, in-flight work preserved, and the run continues from the paused
	// node once credits are topped up. It is NOT a failure: an embedder should
	// surface "add credit and resume", not "this run is dead". Feed
	// ManagedRun.ResumeRunID() back as Config.ResumeRunID to resume.
	RunPaused RunState = "paused"
)

The set of states is an OPEN enum — future minor releases may add more (e.g. a blocked-on-human-gate state). Classify with Terminal() and compare against the states you care about; do not switch exhaustively.

func (RunState) Terminal added in v0.46.0

func (s RunState) Terminal() bool

Terminal reports whether the state is a finished state — the run's goroutine has exited, Done() is closed, and Result() is populated.

RunPaused counts as Terminal even though the *work* is unfinished. It is "finished-but-resumable": this process's run really has ended, so a state where Terminal() were false would contradict a closed Done() channel, keep the key locked by the active-key guard in claim() (stranding the run — the caller could never start the resume under the same key), and make Forget() refuse it forever. Resumability is a separate axis from finishedness, surfaced by State() == RunPaused and ResumeRunID(), not by Terminal().

type RunSummary

type RunSummary struct {
	RunID string `json:"run_id"`
	// Status is one of: "success", "fail", "budget_exceeded",
	// "validation_overridden", "paused_billing". Open enum; prefer StatusClass
	// for stable {succeeded|failed|paused} bucketing. See classifyStatus for the
	// resolution algorithm.
	Status string `json:"status"`
	// StatusClass is one of "succeeded", "failed", or "paused" — stable
	// companion to Status. "paused" flags paused_billing (#514); everything else
	// is classified via pipeline.TerminalStatus(Status).IsSuccess().
	StatusClass string    `json:"status_class"`
	Nodes       int       `json:"nodes"`
	Retries     int       `json:"retries"`
	Restarts    int       `json:"restarts"`
	Timestamp   time.Time `json:"timestamp"`
	// Duration is encoded as integer nanoseconds in JSON ("duration_ns"),
	// not as a duration string.
	Duration time.Duration `json:"duration_ns"`
	// FailedAt is the node ID where the run halted, populated for both
	// "fail" and "budget_exceeded" runs (Gap 5.2: budget-halted runs are
	// no longer classified as "fail", so the gate also fires on
	// "budget_exceeded").
	FailedAt string `json:"failed_at,omitempty"`
	// BundleIdentity is the content-addressed identity ("sha256:<hex>") of
	// the .dipx bundle the run was executed against. Read from the run's
	// checkpoint at summary-build time. Empty for runs from a plain .dip file.
	BundleIdentity string `json:"bundle_identity,omitempty"`
	// OverrideCount is the number of override edges traversed in this run.
	// Sourced from activity log when present, else
	// len(Checkpoint.ValidationOverrides). RunSummary stays thin — for the
	// full OverrideDetail slice see AuditReport.ValidationOverrides.
	OverrideCount int `json:"override_count,omitempty"`
}

RunSummary is a condensed view of a single pipeline run for listing.

func ListRuns

func ListRuns(workdir string, opts ...AuditConfig) ([]RunSummary, error)

ListRuns returns all runs under workdir/.tracker/runs, sorted newest first. If the runs directory does not exist, ListRuns returns (nil, nil).

type SimEdge

type SimEdge struct {
	From      string `json:"from"`
	To        string `json:"to"`
	Label     string `json:"label,omitempty"`
	Condition string `json:"condition,omitempty"`
}

SimEdge is an edge in a SimulateReport.

type SimNode

type SimNode struct {
	ID      string            `json:"id"`
	Handler string            `json:"handler,omitempty"`
	Shape   string            `json:"shape,omitempty"`
	Label   string            `json:"label,omitempty"`
	Attrs   map[string]string `json:"attrs,omitempty"`
}

SimNode is a node in a SimulateReport.

type SimulateReport

type SimulateReport struct {
	Format        string            `json:"format"`
	Name          string            `json:"name,omitempty"`
	StartNode     string            `json:"start_node,omitempty"`
	ExitNode      string            `json:"exit_node,omitempty"`
	GraphAttrs    map[string]string `json:"graph_attrs,omitempty"`
	Nodes         []SimNode         `json:"nodes"`
	Edges         []SimEdge         `json:"edges"`
	ExecutionPlan []PlanStep        `json:"execution_plan"`
	Unreachable   []string          `json:"unreachable,omitempty"`
}

SimulateReport is the structured output of a dry-run over a pipeline source. No LLM calls, no side effects — pure graph introspection.

func Simulate

func Simulate(ctx context.Context, source string) (*SimulateReport, error)

Simulate parses source and returns a SimulateReport. Format is detected from content.

ctx is checked at entry so a caller that passes an already-cancelled context gets an immediate error instead of silent work. Full cancellation mid-parse would require threading ctx through parsePipelineSource → dippin-lang's parser, which is out of scope today (parses are fast and O(n) anyway). Nil is coalesced to context.Background().

func SimulateGraph

func SimulateGraph(ctx context.Context, graph *pipeline.Graph) (*SimulateReport, error)

SimulateGraph returns a SimulateReport for a pre-parsed graph. It is the graph-in variant of Simulate: callers that already parsed (e.g. the CLI's validate + simulate flow, or tooling that built a graph programmatically) avoid a second parse. Unlike Simulate, Format is left empty — there is no source string to inspect; the caller can set it if desired.

ctx is honored at entry; nil is coalesced to context.Background(). The graph is consumed synchronously. For graphs built via the library's parsers or Graph.AddEdge, the BFS plan is O(nodes+edges); graphs that populate Graph.Edges directly without AddEdge leave the adjacency index unbuilt, and OutgoingEdges then scans all edges per lookup — callers that construct graphs by hand should prefer AddEdge to keep that bound.

type StatusEntry added in v0.47.0

type StatusEntry struct {
	Timestamp string `json:"ts"`
	NodeID    string `json:"node_id,omitempty"`
	Text      string `json:"text"`
}

StatusEntry is one agent-authored status update.

func RunStatusTimeline added in v0.47.0

func RunStatusTimeline(runDir string) ([]StatusEntry, error)

RunStatusTimeline returns the agent-authored status updates for a run, in log order — a compact high-level timeline of what the run accomplished, without the per-turn/tool firehose. runDir must be a resolved run directory.

type StreamEvent

type StreamEvent struct {
	Timestamp string `json:"ts"`
	Source    string `json:"source"`              // "pipeline", "llm", "agent"
	Type      string `json:"type"`                // event type within source
	RunID     string `json:"run_id,omitempty"`    // pipeline run ID
	NodeID    string `json:"node_id,omitempty"`   // pipeline node ID
	Message   string `json:"message,omitempty"`   // human-readable message
	Error     string `json:"error,omitempty"`     // error text
	Provider  string `json:"provider,omitempty"`  // LLM provider
	Model     string `json:"model,omitempty"`     // LLM model
	ToolName  string `json:"tool_name,omitempty"` // tool name for agent/LLM tool events
	Content   string `json:"content,omitempty"`   // text content (LLM output, tool output)
	// TerminalStatus is set only on a pipeline run's terminal event
	// (pipeline_completed / pipeline_failed / budget_exceeded): "success",
	// "validation_overridden", "fail", or "budget_exceeded". Empty otherwise.
	TerminalStatus string `json:"terminal_status,omitempty"`
	// BundleIdentity is the content-addressed identity of the .dipx bundle the
	// run executes against ("sha256:<hex>"). Set on pipeline events of a bundle
	// run; empty for a plain .dip run.
	BundleIdentity string `json:"bundle_identity,omitempty"`

	// Decision fields — set on decision_edge / decision_condition /
	// decision_outcome / decision_restart / conditional_fallthrough events, from
	// pipeline.DecisionDetail. ConditionMatch and RestartCount are pointers so a
	// consumer can distinguish "false"/"0" from "not carried".
	EdgeFrom        string                   `json:"edge_from,omitempty"`
	EdgeTo          string                   `json:"edge_to,omitempty"`
	EdgeCondition   string                   `json:"edge_condition,omitempty"`
	EdgePriority    string                   `json:"edge_priority,omitempty"`
	ConditionMatch  *bool                    `json:"condition_match,omitempty"`
	OutcomeStatus   string                   `json:"outcome_status,omitempty"`
	ContextSnapshot map[string]string        `json:"context_snapshot,omitempty"`
	ContextUpdates  map[string]string        `json:"context_updates,omitempty"`
	RestartCount    *int                     `json:"restart_count,omitempty"`
	ClearedNodes    []string                 `json:"cleared_nodes,omitempty"`
	ConditionsTried []pipeline.ConditionEval `json:"conditions_tried,omitempty"`

	// TokenInput / TokenOutput carry the token counts of whatever the event
	// describes: the node's session stats on a pipeline decision event, the
	// turn's usage on an agent turn_metrics / llm_finish event. They are never
	// run-cumulative — that is total_tokens below.
	TokenInput  int `json:"token_input,omitempty"`
	TokenOutput int `json:"token_output,omitempty"`
	// TokenCacheRead / TokenCacheWrite / TurnCostUSD are the per-turn agent
	// extras (turn_metrics, llm_finish). No activity.jsonl counterpart.
	TokenCacheRead  int     `json:"token_cache_read,omitempty"`
	TokenCacheWrite int     `json:"token_cache_write,omitempty"`
	TurnCostUSD     float64 `json:"turn_cost_usd,omitempty"`

	// Cost snapshot fields — set on cost_updated and budget_exceeded events,
	// from pipeline.CostSnapshot. Run-cumulative, not per-node. Estimated is
	// true when any contributing session was heuristic-derived.
	TotalTokens    int                               `json:"total_tokens,omitempty"`
	TotalCostUSD   float64                           `json:"total_cost_usd,omitempty"`
	ProviderTotals map[string]pipeline.ProviderUsage `json:"provider_totals,omitempty"`
	WallElapsedMs  int64                             `json:"wall_elapsed_ms,omitempty"`
	Estimated      bool                              `json:"estimated,omitempty"`

	// Truncation fields — set on tool_output_truncated events (#208).
	TruncStream   string `json:"trunc_stream,omitempty"`
	TruncLimit    int    `json:"trunc_limit,omitempty"`
	TruncCaptured int    `json:"trunc_captured_bytes,omitempty"`
	TruncDropped  int    `json:"trunc_dropped_bytes,omitempty"`
	TruncTotal    int    `json:"trunc_total_bytes,omitempty"`

	// Marker fields — set on tool_marker_missing events (#210).
	MarkerPattern string `json:"marker_pattern,omitempty"`
	MarkerTail    string `json:"marker_tail,omitempty"`
	MarkerError   string `json:"marker_error,omitempty"`

	// RouteTail is set on tool_route_missing events (#212).
	RouteTail string `json:"route_tail,omitempty"`

	// Auto-status fields — set on auto_status_missing events (#346).
	AutoStatusTail       string `json:"auto_status_tail,omitempty"`
	AutoStatusFailClosed bool   `json:"auto_status_fail_closed,omitempty"`

	// Override fields — set on validation_overridden events.
	OverrideGate         string         `json:"override_gate,omitempty"`
	OverrideLabel        string         `json:"override_label,omitempty"`
	OverrideActor        pipeline.Actor `json:"override_actor,omitempty"`
	OverrideSubgraphPath []string       `json:"override_subgraph_path,omitempty"`

	// GateID is set only on the gate lifecycle events (gate_opened /
	// gate_resolved) and correlates the pair (#509). NodeID identifies the gate
	// node on both.
	GateID string `json:"gate_id,omitempty"`
	// Gate payload fields, from pipeline.GateDetail. Open-time: GateMode,
	// GateLabel, GatePrompt, GateChoices, GateQuestions. Resolve-time:
	// GateResponse, GateOutcome, GateActor, GateTimedOut (plus Error above when
	// the gate failed to collect an answer). GateMode is repeated on the
	// resolution so GateResponse can be interpreted without joining.
	GateMode      string                  `json:"gate_mode,omitempty"`
	GateLabel     string                  `json:"gate_label,omitempty"`
	GatePrompt    string                  `json:"gate_prompt,omitempty"`
	GateChoices   []string                `json:"gate_choices,omitempty"`
	GateQuestions []pipeline.GateQuestion `json:"gate_questions,omitempty"`
	GateResponse  string                  `json:"gate_response,omitempty"`
	GateOutcome   string                  `json:"gate_outcome,omitempty"`
	GateActor     pipeline.Actor          `json:"gate_actor,omitempty"`
	GateTimedOut  bool                    `json:"gate_timed_out,omitempty"`

	// Run snapshot fields — set on pipeline_started, from pipeline.RunSnapshot:
	// the top-level node inventory (sorted by ID, not execution order) plus
	// resume state, so a subscriber joining at run start can seed its progress
	// model without separate access to the graph or checkpoint.
	// SnapshotCurrentNode / SnapshotCompletedNodes are populated only on resume.
	// No activity.jsonl counterpart — these names are new.
	SnapshotNodes          []StreamSnapshotNode `json:"snapshot_nodes,omitempty"`
	SnapshotStartNode      string               `json:"snapshot_start_node,omitempty"`
	SnapshotExitNode       string               `json:"snapshot_exit_node,omitempty"`
	SnapshotCurrentNode    string               `json:"snapshot_current_node,omitempty"`
	SnapshotCompletedNodes []string             `json:"snapshot_completed_nodes,omitempty"`
}

StreamEvent is the stable wire format for the tracker --json mode.

Field-stability contract:

  • Existing JSON field names are never renamed, retyped, or removed. New optional fields may be added without a major bump, so a consumer must tolerate unknown keys.
  • Every field except ts/source/type is `omitempty`: a field is present only on the event types that actually carry it. A consumer keys off `type` (within `source`) and reads the fields documented for it; it must not infer meaning from a field's absence beyond "this event does not carry it".
  • Where the same datum is also written to `activity.jsonl`, the JSON field name here is identical, so one decoder serves both streams. The only fields with no `activity.jsonl` counterpart are the run-snapshot group (`snapshot_*`) and the per-turn agent usage extras (`token_cache_read`, `token_cache_write`, `turn_cost_usd`).

Payload size: `context_snapshot` / `context_updates` are unbounded maps of routing-relevant context (a decision event on a context-heavy run can carry tens of kilobytes), `gate_prompt` is capped at pipeline.GateMaxPromptBytes, and the `*_tail` diagnostic fields are capped at 256 bytes by their emitters. `content` is unbounded (a tool_call_end carries full tool output). A consumer on a bounded transport should cap or drop those fields itself.

type StreamSnapshotNode added in v0.47.0

type StreamSnapshotNode struct {
	ID      string `json:"id"`
	Label   string `json:"label,omitempty"`
	Handler string `json:"handler,omitempty"`
}

StreamSnapshotNode is one node of a pipeline_started run snapshot on the NDJSON wire. It mirrors pipeline.SnapshotNode with explicit wire tags.

type Suggestion

type Suggestion struct {
	NodeID  string         `json:"node_id,omitempty"`
	Kind    SuggestionKind `json:"kind"`
	Message string         `json:"message"`
}

Suggestion is an actionable recommendation produced by Diagnose.

type SuggestionKind

type SuggestionKind string

SuggestionKind is the typed string identifying which template produced a Suggestion. The underlying string values are stable; new kinds may be added additively.

const (
	SuggestionRetryPattern     SuggestionKind = "retry_pattern"
	SuggestionEscalateLimit    SuggestionKind = "escalate_limit"
	SuggestionNoOutput         SuggestionKind = "no_output"
	SuggestionShellCommand     SuggestionKind = "shell_command"
	SuggestionGoTest           SuggestionKind = "go_test"
	SuggestionSuspiciousTiming SuggestionKind = "suspicious_timing"
	SuggestionBudget           SuggestionKind = "budget"
	// SuggestionToolOutputTruncated fires when a tool node's output stream
	// exceeded its per-stream cap. Surfaces actionable copy pointing at
	// output_limit and at the canonical authoring pattern. Issue #208.
	SuggestionToolOutputTruncated SuggestionKind = "tool_output_truncated"
	// SuggestionConditionalFallthrough fires when a node's conditional
	// routing edges all evaluated false and routing fell back to an
	// unconditional edge. Issue #208.
	SuggestionConditionalFallthrough SuggestionKind = "conditional_fallthrough"
	// SuggestionToolMarkerMissing fires when a tool node declared
	// marker_grep but the regex matched nothing (or failed to compile).
	// Surfaces the configured pattern, the captured stdout tail, and the
	// recommended fix. Issue #210.
	SuggestionToolMarkerMissing SuggestionKind = "tool_marker_missing"
	// SuggestionToolRouteMissing fires when a tool node had
	// route_required: true but no _TRACKER_ROUTE= sentinel line was
	// emitted to stdout. Surfaces the captured stdout tail and the
	// recommended author pattern. Issue #212.
	SuggestionToolRouteMissing SuggestionKind = "tool_route_missing"
	// SuggestionAutoStatusMissing fires when an auto_status agent node
	// completed normally but its response contained no parseable STATUS
	// line (#346). Goal-gate nodes fail closed; plain auto_status nodes
	// keep the legacy success default — the suggestion copy distinguishes
	// the two so a silently-defaulted verdict is visible post-run.
	SuggestionAutoStatusMissing SuggestionKind = "auto_status_missing"
	// SuggestionAuditLogInjection fires when the integrity-protected
	// activity log has one or more lines missing the runtime sentinel
	// prefix (#213). Detection-only — the suggestion text is explicit
	// that the sentinel is not authentication; a motivated forger who
	// reads tracker's source can emit the bytes. Surfaces the count of
	// suspect lines and the audit-log path so operators can
	// investigate.
	SuggestionAuditLogInjection SuggestionKind = "audit_log_injection"
)

Suggestion kinds (stable; new ones may be added additively).

const SuggestionCostAsymmetry SuggestionKind = "cost_asymmetry"

SuggestionCostAsymmetry fires when one provider dominated a run's cost with no prompt caching — the failure mode where a slow, uncached backend in a fan-out silently drives most of the bill (#353). Actionable: cap it with per-node max_cost_usd, route the lane cheaper, or reshape a fan-out to escalate-on-fail.

type TestFidelityReport added in v0.47.0

type TestFidelityReport struct {
	// DuplicateGroups are sets of ≥2 test functions with the same body. "identical"
	// = byte-for-byte equal bodies; "near-identical" = equal after normalizing
	// literal values (so tests differing only in a string/number collide).
	DuplicateGroups []DuplicateTestGroup `json:"duplicate_groups,omitempty"`
}

TestFidelityReport lists groups of Go test functions that share a body — a fidelity red flag the structural VerifyMilestone checks are blind to (#489).

func AnalyzeTestFidelity added in v0.47.0

func AnalyzeTestFidelity(dir string) (*TestFidelityReport, error)

AnalyzeTestFidelity scans a directory tree for Go test files and reports test functions whose bodies duplicate one another. Unparseable files are skipped (best-effort), not fatal — the analysis is advisory. dir is walked recursively, skipping vendor/.git/node_modules.

type TestLocation added in v0.47.0

type TestLocation struct {
	Name string `json:"name"`
	File string `json:"file"`
	Line int    `json:"line"`
}

TestLocation identifies a test function.

type TimelineEntry

type TimelineEntry struct {
	Timestamp time.Time `json:"ts"`
	Type      string    `json:"type"`
	NodeID    string    `json:"node_id,omitempty"`
	Message   string    `json:"message,omitempty"`
	// Duration is encoded as integer nanoseconds in JSON ("duration_ns"),
	// not as a duration string.
	Duration time.Duration `json:"duration_ns,omitempty"`
}

TimelineEntry is a single entry in the audit timeline.

type ValidateOption

type ValidateOption func(*validateConfig)

ValidateOption configures ValidateSource behavior.

func WithValidateFormat

func WithValidateFormat(format string) ValidateOption

WithValidateFormat sets the pipeline source format ("dip" or "dot").

type ValidationResult

type ValidationResult struct {
	Graph    *pipeline.Graph
	Errors   []string
	Warnings []string
	Hints    []string
}

ValidationResult contains the outcome of pipeline validation.

func ValidateSource

func ValidateSource(source string, opts ...ValidateOption) (*ValidationResult, error)

ValidateSource parses and validates a pipeline source string without executing it. Returns a ValidationResult with structured errors, warnings, and hints. An error is returned when the source cannot be parsed or has structural errors.

type WebhookGateConfig

type WebhookGateConfig struct {
	WebhookURL    string        // required: URL to post gate payloads to
	CallbackAddr  string        // local listen addr for callback server (default: :0, an OS-assigned ephemeral port; the bound address is advertised via each gate's callback_url)
	Timeout       time.Duration // wait timeout per gate (default: 10m)
	TimeoutAction string        // "fail" (default) or "success" on timeout
	AuthHeader    string        // Authorization header for outbound requests
	RunID         string        // optional: run ID embedded in gate payloads
}

WebhookGateConfig controls headless webhook-based human gate handling. When set, human gate prompts are POSTed to WebhookURL and the pipeline waits for a callback POST to the local callback server.

type WorkflowInfo

type WorkflowInfo struct {
	Name        string   // bare name used for lookup, e.g. "build_product"
	File        string   // path within the embedded FS, e.g. "examples/build_product.dip"
	DisplayName string   // workflow declaration name, e.g. "BuildProduct"
	Goal        string   // parsed from the goal: field at the top of the .dip file
	Requires    []string // parsed from the `requires:` field (v0.29.0); nil if not declared
}

WorkflowInfo describes a built-in workflow embedded in the tracker binary.

func LookupWorkflow

func LookupWorkflow(name string) (WorkflowInfo, bool)

LookupWorkflow returns the WorkflowInfo for a built-in workflow by bare name, or (zero, false) if no built-in matches. The returned value shares no mutable state with the cached catalog.

func OpenWorkflow

func OpenWorkflow(name string) ([]byte, WorkflowInfo, error)

OpenWorkflow returns the raw source bytes of a built-in workflow by bare name. This is the same content that `tracker init <name>` would copy to disk. Returns an error if the name is not a known built-in.

func ResolveSource

func ResolveSource(name, workDir string) (source string, info WorkflowInfo, err error)

ResolveSource resolves a pipeline name to the actual workflow source text. The resolution order matches the CLI's tracker.resolvePipelineSource:

  1. If name contains "/" or "\" (forward or back slash) or ends in ".dip"/".dot", treat it as a filesystem path and read it from disk.
  2. If "<name>.dip" exists under workDir, read that.
  3. If "<name>" exists under workDir, read that.
  4. If name matches a built-in workflow, return the embedded source.
  5. Otherwise return an error listing available built-ins.

The returned WorkflowInfo is populated only for built-in workflows; it's the zero value for filesystem sources. workDir may be empty — in that case the current working directory is used for relative lookups.

func Workflows

func Workflows() []WorkflowInfo

Workflows returns the list of workflows embedded in the tracker binary, sorted by name. Library consumers can use this to show users the available built-ins without shelling out to `tracker workflows`. Returned values share no mutable state with the cached catalog.

Directories

Path Synopsis
ABOUTME: Turn-budget checkpoint evaluation for the agent session loop.
ABOUTME: Turn-budget checkpoint evaluation for the agent session loop.
exec
ABOUTME: ExecutionEnvironment interface abstracting where agent tools run.
ABOUTME: ExecutionEnvironment interface abstracting where agent tools run.
tools
ABOUTME: Bash tool executes shell commands in the working directory.
ABOUTME: Bash tool executes shell commands in the working directory.
cmd
tracker command
ABOUTME: Builds pipeline.AgentTurnUsage from an agent.Event for the activity-log writer.
ABOUTME: Builds pipeline.AgentTurnUsage from an agent.Event for the activity-log writer.
tracker-conformance command
ABOUTME: golden-trace conformance fixtures — deterministic engine runs driven ABOUTME: by a stub completer (no API keys) and snapshotted so downstream ports ABOUTME: can diff for event-schema / handler-contract / usage-shape drift.
ABOUTME: golden-trace conformance fixtures — deterministic engine runs driven ABOUTME: by a stub completer (no API keys) and snapshotted so downstream ports ABOUTME: can diff for event-schema / handler-contract / usage-shape drift.
tracker-swebench command
ABOUTME: `tracker-swebench analyze <results-dir>` — bulk-triage a prior run's artifacts.
ABOUTME: `tracker-swebench analyze <results-dir>` — bulk-triage a prior run's artifacts.
tracker-swebench/agent-runner command
ABOUTME: In-container agent binary for SWE-bench evaluation harness.
ABOUTME: In-container agent binary for SWE-bench evaluation harness.
trackerbot command
ABOUTME: Aliases the transport-neutral chatops types into package main so the ABOUTME: Slack transport (slack.go) and wiring (main.go) read cleanly.
ABOUTME: Aliases the transport-neutral chatops types into package main so the ABOUTME: Slack transport (slack.go) and wiring (main.go) read cleanly.
trackerchat command
ABOUTME: Entry point for trackerchat — a terminal REPL front-end for Tracker.
ABOUTME: Entry point for trackerchat — a terminal REPL front-end for Tracker.
internal
bundleid
Package bundleid provides shared formatting helpers for content-addressed .dipx bundle identities.
Package bundleid provides shared formatting helpers for content-addressed .dipx bundle identities.
diag
ABOUTME: Injectable diagnostic sink for tracker's library packages (#449).
ABOUTME: Injectable diagnostic sink for tracker's library packages (#449).
dipxtest
ABOUTME: Test helper that packs real .dipx bundles for use in downstream tests.
ABOUTME: Test helper that packs real .dipx bundles for use in downstream tests.
llm
ABOUTME: Detects provider credit/quota exhaustion and builds an actionable, ABOUTME: account-attributed message — never printing the raw API key (#487 Phase 1).
ABOUTME: Detects provider credit/quota exhaustion and builds an actionable, ABOUTME: account-attributed message — never printing the raw API key (#487 Phase 1).
anthropic
ABOUTME: Anthropic Messages API adapter implementing the ProviderAdapter interface.
ABOUTME: Anthropic Messages API adapter implementing the ProviderAdapter interface.
google
ABOUTME: Google Gemini API adapter implementing the ProviderAdapter interface.
ABOUTME: Google Gemini API adapter implementing the ProviderAdapter interface.
openai
ABOUTME: OpenAI Responses API adapter implementing the ProviderAdapter interface.
ABOUTME: OpenAI Responses API adapter implementing the ProviderAdapter interface.
openaicompat
ABOUTME: OpenAI Chat Completions compatible adapter implementing the ProviderAdapter interface.
ABOUTME: OpenAI Chat Completions compatible adapter implementing the ProviderAdapter interface.
ABOUTME: Resolves the on-disk location of the integrity-protected activity log.
ABOUTME: Resolves the on-disk location of the integrity-protected activity log.
handlers
ABOUTME: LLM-backed autopilot interviewer that replaces human gates with automated decisions.
ABOUTME: LLM-backed autopilot interviewer that replaces human gates with automated decisions.
tools
jailcheck command
ABOUTME: jailcheck flags direct filesystem-mutation / subprocess calls in ABOUTME: agent/tools that bypass the ExecutionEnvironment seam guarding the writable_paths jail (#272/#275/#283).
ABOUTME: jailcheck flags direct filesystem-mutation / subprocess calls in ABOUTME: agent/tools that bypass the ExecutionEnvironment seam guarding the writable_paths jail (#272/#275/#283).
transport
chatops
ABOUTME: Posts a run's outcome to its thread — diagnosis on failure, adaptive on success (D3).
ABOUTME: Posts a run's outcome to its thread — diagnosis on failure, adaptive on success (D3).
cli
ABOUTME: A terminal REPL transport — a second consumer of transport/chatops that ABOUTME: proves the boundary: type a request, answer gates inline, watch it run.
ABOUTME: A terminal REPL transport — a second consumer of transport/chatops that ABOUTME: proves the boundary: type a request, answer gates inline, watch it run.
conformance
ABOUTME: A reusable conformance suite any transport runs to prove its handlers.Interviewer honours the gate contract (all modes + cancellation).
ABOUTME: A reusable conformance suite any transport runs to prove its handlers.Interviewer honours the gate contract (all modes + cancellation).
ABOUTME: Event adapter — converts raw engine events into typed TUI messages.
ABOUTME: Event adapter — converts raw engine events into typed TUI messages.

Jump to

Keyboard shortcuts

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