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 →
Install — Homebrew (macOS/Linux) or go install:
brew install 2389-research/tap/tracker
# or: go install github.com/2389-research/tracker/cmd/tracker@latestThree 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 spec —
tracker init build_productscaffoldsbuild_product.dip+ a starterSPEC.md; edit the spec, thentracker build_product. - Hands-off — add
--autopilot midto replace human gates with an LLM judge (or--auto-approvefor deterministic approval). - Drive it elsewhere — from Slack (
trackerbot) or your terminal as a REPL (trackerchat). - Explore —
tracker 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.
Four pipelines are embedded in the binary and available via tracker workflows:
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.
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
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.
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
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 editingbuild_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.
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
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.
| 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 |
Three namespaces for ${...} syntax in prompts:
${ctx.outcome}— runtime pipeline context (outcome, last_response, human_response, tool_stdout)${params.model}— workflow-levelvars(optionally overridden by--param key=valueat 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 MyPipelineUnknown --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.
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.
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.
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.
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 asctx.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 toOutcomeSuccess, No maps toOutcomeFail— route withwhen ctx.outcome = success/when ctx.outcome = failedges. Distinct from choice mode, where the outcome is always success and routing usespreferred_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 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).
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 doctorKeys 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.
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_productOr as a CLI flag:
tracker --gateway-url https://gateway.ai.cloudflare.com/v1/acc/gw build_productTracker 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:
429from 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.
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.Config →
Engine, 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/.
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
| 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 |
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.
| 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 |
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.
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
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.toolEnable 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.
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 --onelineThe 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.
When a pipeline run doesn't go as expected, tracker gives you tools to understand what happened:
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 7813bThe 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).
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>| 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 |
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.dipA 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.
--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.
- A human gate fires → tracker POSTs a
WebhookGatePayloadto--webhook-url. - Your service receives the payload, routes it to a human (Slack message, email, etc.).
- The human responds → your service POSTs a
WebhookGateResponseto thecallback_urlfield. - Tracker resumes the pipeline with the human's answer.
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| 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.
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).
⚠️ 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. CheckCHANGELOG.mdbefore upgrading.
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_...",
},
})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(),
})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) ordot(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, oracp--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_URLenv 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-urlis set (default:10m)--gate-timeout-action— what to do on gate timeout:fail(default) orsuccess--webhook-auth—Authorizationheader 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)
# 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.dipSee LICENSE.