Changelog
Every feature, fix, and breaking change. All GitHub Releases →
Added
adversarial-reviewreusable subgraph (#623). A read-only, parallel adversarial code review that a workflow embeds withref:— the false-positive control from Adversarial Review (arXiv 2608.18167) shipped as engine structure + deterministic tools, not prompt discipline.examples/subgraphs/adversarial-review.dipruns three independent perspectives (correctness / security / design) in parallel over a frozen diff, merges + cross-perspective dedupes their findings, runs a typed-verdict critic audit, and loops a bounded re-review over contested findings (the graph’s per-targetmax_restartsis the engine-side bound, enforced before the adjudicate round cap can exhaust it). Final verdicts come from the deterministic FP gate — ungroundedDISAGREE_CONCERNfindings are demoted byrank_and_filter.sh, plus the caller’sseverity_threshold— and leave via the declared output contractwrites: review_findings, review_verdict(callers readctx.review_findings/ctx.review_verdictand route onapprove/rework). The subgraph is read-only by construction: every agent only reads the frozen diff and writes findings JSON under.ai/review/, so a hostile diff cannot steer the reviewers into editing code. Failure is fail-closed at the approval boundary: a review that cannot complete degrades torework, neverapprove.examples/adversarial-review-demo.dipis the drop-in caller (subgraph + report node). Scripts live inexamples/subgraphs/scripts/adversarial-review/(moved from the #622 spike location);rank_and_filter_test.shproves the node gate’s threshold/verdict behavior and lockstep parity with the #622 referencerank_filter.shon the shared fixture corpus. Both new pipelines passdippin doctor(A grade) anddippin simulate -all-paths.
Changed
- dippin-lang pinned to v0.72.0 (#637 / dippin #297) —
openai-compat(aliasopenai-compatible) is now a first-class custom-gateway provider in dippin’s catalog: every id under it is known-but-unpriced, so loading an openai-compat pipeline no longer surfaces a spurious DIP108 “model not in catalog” warning per agent. Cost estimation follows suit — the newllm.EstimateCostForProviderprices a catalogued id routed through the gateway from the catalog as before, but treats an opaque gateway id as unpriced by design ($0, no “unknown model” diagnostic) rather than a genuine miss; a first-party provider with an uncatalogued id still warns. The token tracker, agent session, and ACP backend cost paths use it. - dippin-lang pinned to v0.71.0 (#626 / dippin #296) — uptakes the catalog v0.71.0 pricing refresh;
tracker doctor’sPinnedDippinVersionand the Models & Providers table follow. Example pipelines pick up the current stable model ids (#629):claude-sonnet-5/claude-opus-5/gpt-5.5replace the retiredclaude-sonnet-4-6/claude-opus-4-6/gpt-5.2pins, tiering intact.
Fixed
tracker doctorno longer false-fails openai-compat gateway keys (#636). The OpenAIsk-prefix assertion was applied toOPENAI_COMPAT_API_KEYtoo, flagging a valid minted JWT / arbitrary bearer as “invalid format” even though runs worked.openai-compatnow only requires a non-empty key — the upstream defines its own credential shape. When the key is set but neitherOPENAI_COMPAT_BASE_URLnorTRACKER_GATEWAY_URLresolves an endpoint, the OK line carries an advisory hint that requests go to the adapter default (OpenRouter).--backend claude-codecould not start: subprocess no longer outlives cancellation (#635). The claude subprocess was spawned withexec.Command, whosecmd.Waitignores context cancellation — cancelling a claude-code agent left the process (and its children) running and the session turn hung. The spawn now usesexec.CommandContextso a cancelled run kills the subprocess.- Operator rejection at a human gate is no longer reported as
success(#633). The exit node’s passthrough handler always returns success, so a run that reached the exit after the operator chose the gate’s rejection-labeled (abandon/reject) non-override edge terminatedsuccess— the rejection was invisible inresult.Status. The exit success path now checks the durable checkpoint edge selections and terminatesfailwith arun rejected at human gate …message. Accepts (override: trueedges) and unlabeled freeform/interview gate→exit edges are unaffected.
Fixed
build_productmilestone escalation:acceptnow escapes the structural gate (#730). WhenCheckMilestoneOutputsflags a milestone (outputs-missing), theEscalateMilestonegate’sacceptchoice re-enteredCheckMilestoneOutputs— the very check the operator is overriding — so a flagged tree (a false positive on a green build, or a true positive the operator has chosen to ship anyway) deterministically cycled back to the gate until onlyabandon(discard the whole run) exited.acceptnow routes forward toClearStaleReviews, the same ship/review entry theoutputs-presentedge uses: the run still earns the three-way cross-review, theFinalBuildwhole-treego test ./..., and theFinalSpecCheckcompliance subgraph beforeCleanup(#392 intent preserved), whileacceptis always a real forward exit, independent of why the gate flagged.
Changed
- dippin-lang pinned to v0.70.0 — Meta joins the catalog as a new provider and the Muse Spark family is priced, so the Models & Providers table now lists five more ids (117 entries across 11 providers):
muse-spark-1.1/1.2/1.3at $1.25/M in, $0.15/M cached read, $4.25/M out, plusmuse-spark-1.2-contributorandmuse-spark-1.3-contributorat $0.10 / $0.002 / $0.20. All carry a 1M context window;max_outputand capability data are absent by design (Meta publishes no per-version values), so a context-window lookup resolves while capability queries stay unknown — dippin’s absent-not-guessed contract. - The
-contributortier is Meta’s own, and it has a string attached. The ~12× cheaper price is bought by granting Meta permission to train on your prompts and completions. Only the standard ids carry family metadata, so a provider-scopedmuse-spark@latestresolves tomuse-spark-1.3and can never silently land a run on the train-on-my-data tier. - No tracker pricing code changed: the Meta entries carry an absolute cached-input price, so tracker’s cache overlay self-disables for them and the real $0.002/M contributor cache-read rate is billed rather than the 0.1× default’s $0.01/M — a 5× error avoided by construction.
- Muse is priced, not yet reachable. tracker still has no Meta provider routing, so a Muse model can be costed but not run. Meta describes its Model API as OpenAI-SDK compatible, which makes
openai-compatplus a base URL the likely path — unverified against a live endpoint.
Changed
- dippin-lang pinned to v0.69.0 — six newly verified models join the catalog tracker prices and lists against (117 entries):
claude-fable-5-1,gemini-3.7-flash,gemini-3.8-flash,glm-5.3,glm-5.3-flash,MiniMax-M2.5-highspeed. Fable 5.1's unusual 0.025× cache-read rate is honored exactly (tracker's cache overlay self-disables for any model dippin prices), so a cache-heavy run is billed at $0.25/1M rather than the 0.1× default's $1.00. Also picks up dippin's fix to a daily pricing-sync job that reported success while detecting nothing. No tracker code change — the model catalog is derived from dippin by construction.
Added
- Deterministic false-positive gate for Adversarial Review (#622 spike) — encodes the paper's typed-verdict control (
AGREE/DISAGREE_EVIDENCE/DISAGREE_CONCERN) as a tool: a finding survives only when grounded, ungrounded concerns are demoted. 15-case fixture suite. The full reusable subgraph is #623.
Changed
- Fail-safe cost cap on
build_product's un-cached ReviewCodex lane (#353) — the OpenAI reviewer (zero prompt-cache reads) was the $7.35-runaway risk and had no bound; nowmax_cost_usd: 4.00+cost_exceeded_action: failroutes a breach to the human gate (no retry, no lost work). Conservative floor; the cached Anthropic lane stays uncapped.
Changed
- Cheaper
build_productmilestone verification (#490) —VerifyMilestonenow runs atreasoning_effort: medium; an A/B on real runs showed ~20% lower per-invocation cost (verify dropped from above to below Implement cost) with coverage intact (it still reads the whole SPEC.md + runs git archaeology). Reversible.
Added
- VerifyMilestone test-fidelity heuristics (#532) — three additive advisory detectors: structural near-duplicate (token-Jaccard, guarded by shared call targets), unreached-path via coverage attribution (
verify-tests --coverage), and production-logic-re-implemented-in-tests. All advisory (exit 0) with known-good/known-hollow fixtures; newtracker.AnalyzeTestReachability.
Follow-ups from the multi-agent review of the SIFT audit (v0.67–v0.70) — the audit itself held up clean.
Added
- Provider rate-limit metadata populated from headers (#617) — the Anthropic and OpenAI adapters now fill
Response.RateLimitfrom their rate-limit headers on both the direct and traced paths (it was defined but never populated). Gemini/openai-compat have no standard headers.
Fixed
- Per-session tool-cache telemetry no longer reports 0 (#618) — a deferred finalize mutated an unnamed return, so
tool_cache_hits/tool_cache_misseswere always 0. Observability-only; cost/routing were never affected.
Changed
- Skip per-turn progress capture when turn-checkpointing is off (#619), and a field-count guard for the execution-graph clone (#621).
Final batch of the 2026-08-24 SIFT simplification audit (#592) — the audit's public findings are now complete.
Fixed
- An empty provider response after tool calls fails loudly (#601) — emptiness is judged on the current response, not session-total tool calls, so a post-tool empty response is no longer accepted as a clean stop.
- Resumed agents keep their cost / anti-loop budget (#596) — turn snapshots (schema v2) now persist a typed
SessionProgressrecord, so cumulative cost, usage, and loop history continue across interruption instead of resetting. - Tracing no longer strips provider response metadata (#605) — the traced path (which every agent call takes) now carries the provider id and the returned model (it used to report the requested alias), plus
Retry-Afteron streaming errors, matching the untraced path on those fields. (Raw provider body can’t be reconstructed from an SSE stream, and warnings/rate-limit headers aren’t threaded through the streamed path yet — #617; unchanged from before, no regression.)
Changed
- Session outcome has one explicit
Disposition(#601) — a typed enum with centralized precedence; the outcome booleans remain as derived, deprecated accessors for one release. Non-breaking.
SIFT simplification audit (#592), batches 3–4 — closes the audit's remaining public findings bar the standalone #605.
Fixed
- Normalized token totals are a derived invariant (#597) —
Total = fresh_input + outputuniformly across all four providers, so--max-tokensis provider-neutral under caching. - Ownership-aware SWE-bench cleanup + timeout watchdog (#598/#608) — a second harness no longer destroys the first's running container, and a timed-out child still emits its result summary before the host watchdog fires.
- A gate timeout no longer corrupts later human gates (#599) — gate-scoped cancellation vs run-wide teardown (transport-boundary change).
- Chat rendering runs behind the bounded event queue (#600) — a slow Slack call no longer stalls the engine goroutine.
- Search owns the activity-log viewport (#611) —
n/Nnow scroll off-screen matches into view.
Changed
- Goal-gate state is one checkpoint state machine (#602) — replaces four order-dependent maps in this safety-critical routing path; legacy checkpoints migrate on load.
- DIP is the shipped-example authority (#614, first slice) and one authoritative Bedrock/gateway guide (#610).
Removed
- Retired the undeployed
docs/site/**website copy (#615) —site/**is the sole source; use the live site.
Second batch from the 2026-08-24 SIFT simplification audit (#592).
Changed
- Per-loop-target restart budgets (#603) — the restart/fix budget is no longer one global scalar, so a fix loop on one loop target can't drain another's budget. Old checkpoints still load.
- Single graph-finalization boundary (#606) —
pipeline.PrepareForExecutionclones, rebuilds adjacency, freezes, and enforces edge-endpoint integrity before execution. - Activity-log reader schema generated from one authority (#613) — retires triplicated field lists; new drift gate. Wire-neutral (
ActivityEntrybyte-identical). - Conformance uses the shipped LLM client (#609) and centralized CLI command metadata (#612).
First batch from the 2026-08-24 SIFT simplification audit (#592).
Fixed
- Parallel-branch child usage now reaches the parent budget (#595) — the parallel handler dropped
ChildUsage, so token/cost spend from subgraph/manager-loop branches vanished fromBudgetGuardand cost totals. Now folded in viaCombineChildUsage.
Changed
- Fail-closed Dippin validation gate (#594) — a single pinned
scripts/dippin/gate.shthat fails loud on command error, malformed JSON, or empty glob; the|| echo "0"fail-open paths are gone. - Verifiable release build identity (#593) — goreleaser binaries now stamp version/commit/date (were reporting
dev/unknown, which disabledtracker update). - TUI consumes the authoritative terminal status (#604) instead of reconstructing it, covering budget and pause terminals.
- One parsed condition model from load through routing (#607) — validation, lint, evaluation, and manager-loop checks share
ParseCondition/CompiledCondition, closing the typed-vs-raw paren divergence.
Added
- Sub-node turn checkpointing for mid-node resume (#427). A default-off
turn_checkpointattr lets a long multi-turn agent node checkpoint between turns, sotracker -rresumes mid-node instead of re-running the whole node. Snapshot lives in the secure run dir; a moved git HEAD fails safe. - Branch-scoped counter primitive for parallel milestones (#420) — the parallel handler seeds
ctx.branch_idinto each branch so per-branch on-disk counters no longer clobber each other. - Tiered
--help(#463) —tracker --helpshows the flags most runs need;--help-allshows the full surface.
Changed
- Retired the last hand-maintained model catalog (#570) — models and display names now derive entirely from
dippin-lang/pricing(bumped to v0.68.0), so the model set can never drift from the pin. Removed: tracker-only short aliases (sonnet-4-6,claude-haiku, …) — use full dippin IDs.
Documentation
- build_product vs superspec documented + spec-coherence preflight backport + examples/workflows dedup (#307). Design analyses for verification cadence (#490), review fan-out cost (#353), run-flag presets (#463), and test-fidelity heuristics (#532).
Added
- Subscription usage-limit is now a resumable pause, not a retry-then-hard-fail (#590, #591). A Claude subscription usage-limit ("resets at X") previously slipped past
IsBillingError, got retried into the same wall, and hard-failed — discarding resumable WIP. It now routes to the resumableOutcomePausedBillingterminal (checkpoint +tracker -r), non-retrying, on both the native and claude-code backends. Newllm.IsUsageLimit/llm.UsageLimitResetAtclassify + parse it; the transient-429 retry path is unchanged. PauseErrorand thebilling_pausedevent carry aResumeAfterreset timestamp (#591) — serialized asresume_after(RFC3339) across all three schemas so a downstream scheduler can hold a paused run until the subscription resets instead of thrashing.
Added
- Stream-idle / read deadline on the Anthropic, OpenAI & Gemini streaming adapters (#575, #576, #577). A hung provider stream that opens then goes byte-silent no longer blocks the caller forever: an idle guard keyed on raw SSE socket bytes cancels after a configurable deadline (
llm.DefaultStreamIdleTimeout, 10m;WithStreamIdleTimeoutto override) and surfaces a retryablellm.StreamErrorthat composes with the #574 turn-level retry. Keys on socket bytes — not tracker events — so a keepalive-paced slow stream (sonnet turns reach ~304.5s; gpt-5 has 32s+ silent reasoning gaps) is never aborted, andclassifySSEReadno longer folds an idle cancel into a silent EOF truncation (#576).
Changed
- Homepage reframed around the trust story (#578, #579, #580) — "Multi-agent pipelines you can trust to run unattended", all four guardrails surfaced as named cards with honest-limits notes, a stars badge, and a reserved (empty) Benchmarks slot (no SWE-bench number until #465).
- Standardized "Tracker by 2389" and qualified "Dippin" (#582, #584) — copy/metadata only; the binary, module path,
TRACKER_*env, and.tracker/dirs are untouched.
Tooling & verification
- Re-bucketed companion-binary changelog/CLI entries + new "How we verify tracker" page (#587, #588).
tracker-conformance/tracker-swebenchnow group under a dedicated heading;verify.htmlframes golden-trace conformance as the lockstep drift guarantee alongside the complexity ratchet, docs-drift gate, and provider round-trip tests. Docs-only.
Changed
- Context compaction now reads the real per-model context window straight from dippin for every priced model (#572). A new
llm.ModelContextWindowresolves the window fromdippin-lang/pricingfor every priced model — not just the ~26 hand-catalogued ones — including tail-provider models (Coherecommand-a-03-2025→ 256K) andfamily@selectoraliases. The real window is used only when the operator left the generic 200K default; unknown windows keep the fallback; explicit limits are always respected. - Bumped
dippin-langto v0.67.0 (addspricing.ResolveModelReffor alias resolution).
Fixed
- A retried mid-stream attempt no longer leaves dangling partial content in the trace (#550). The client now emits an explicit
abortedfinish boundary for a failed attempt so a reader can collapse it. Cost/usage was never affected — usage is recorded once from the final response, now pinned by a regression test.
Fixed
- A truncated / over-long / empty streamed content block no longer aborts the turn (#573). The SSE parser is now uncapped (
bufio.Reader.ReadBytes) so >1 MB deltas read in full; an unparseable delta is skipped instead of fatal. Fixed across Anthropic, OpenAI, and Google adapters. - A late transient stream error no longer re-runs the whole node (#574). Transient mid-stream read failures are now retryable, so the completion is retried with accumulated context — resuming a deep multi-turn episode instead of discarding it to a node retry.
Fixed
- Context compaction uses the model’s real context window (#572). A 1M-window model was being compacted as if it had a fixed 200K limit — now derived from the model’s dippin-sourced window (explicit limits still respected).
Changed
- Removed hand-maintained cache multipliers (#570) — dippin now carries verified cache read+write rates for every model, so tracker’s catalog is pure identity. The cache-pricing guard now asserts the effective billed rate via dippin.
Changed
- dippin pinned to v0.66.2 — capability data now populated for all 11 providers. tracker’s native providers already source capabilities from dippin; this bump refreshes the Models table and flags 5 newly-deprecated Cohere/Mistral models as retired.
Fixed
- Corrected wrong model context-window / max-output metadata. dippin v0.66.1’s verified capability data for Anthropic/OpenAI/Gemini revealed 15 of tracker’s 26 hand-maintained values were wrong (e.g.
claude-fable-5guessed at 200K context, actually 1M;gpt-5.2128K → 400K) — a wrong context window mis-tunes compaction. All corrected by sourcing from dippin.
Changed
- dippin pinned to v0.66.1; retired tracker’s hand-maintained capability catalog (#570/#571).
GetModelInfo/ListModelsnow source capabilities from dippin; the catalog carries only identity + cache premium (130 hand-maintained lines removed). A guard test prevents any capability value being re-added for a model dippin populates.
Changed
- dippin-lang pinned to v0.66.0 — tracker begins retiring its parallel model catalog.
GetModelInfonow prefers dippin’s capability metadata (context window / max output / capabilities) where populated, with the catalog as fallback; the cache-rate overlay is scoped to dippin’sCacheGaps()with a guard test so tracker never guesses a rate for a model dippin prices. Website model table regenerated (106 models).
Added
- Models & Providers page — backends, providers, gateway routing, cost governance, and a complete model catalog with prices. The table is generated from dippin-lang/pricing and drift-gated (pre-commit + CI), so it can never disagree with what the engine bills. Models/providers/prices are DRY-derived from dippin.
claude-opus-4-8added to the capability catalog (was priced by dippin but uncatalogued).
Changed
- Navigation regrouped into Concepts / Reference / Project dropdowns for scannability; Models & Providers lives under Reference.
Added
- Claude 5 models in the capability catalog —
claude-opus-5,claude-sonnet-5,claude-fable-5(the bareclaude-opus/claude-sonnetaliases now resolve to the Claude 5 flagships). With #567/#568 fixed and dippin v0.64.0 pricing all three, the Claude 5 family is fully usable. Metadata mirrors the prior flagship generation pending confirmed Claude 5 specs.
Changed
- Docs & website refreshed for Claude 5 — the model showcase and illustrative examples lead with the Claude 5 family;
llm.mddocuments the empty-thinking / empty-tool-input replay faithfulness and the conformance harness.
Fixed
- Anthropic no-argument tool calls could lose the required
inputfield on streaming replay (#568, same class as #567). A no-arg tool call accumulated empty arguments, whichomitemptydropped on replay → Anthropic 400input: Field required. Fixed at the source: the stream accumulator normalizes an empty tool-call argument to{}.
Added
- Provider round-trip conformance harness — a CI gate that mechanically catches the “
omitemptydrops a required-but-empty field on replay” bug class (#567/#568) across Anthropic, OpenAI, and Google. A dynamic replay-fidelity test per provider plus a static structural guard failgo testthe moment a translate struct becomes lossy — before any model hits it in production.
Fixed
- Anthropic empty-thinking compatibility break with Sonnet 5 / Opus 5 / Fable 5 (#567). Those models default to omitted thinking and return a signature-only block with an empty
thinkingstring; tracker’s request serializer dropped the empty field viaomitempty, so Anthropic rejected the next request (thinking.thinking: Field required) — breaking every multi-turn / tool-use run on those models. Fixed by making the sharedThinkingfield a*stringso an empty thinking block still serializes"thinking":""while non-thinking blocks omit it.
Changed
- dippin-lang pinned to v0.64.0, which fixes all three items tracker filed while externalizing the bundled workflows:
dipx.OpenReaderfor embedded bundles (drops the temp-file bridge for the coming.dipxbuilt-in delivery), thedefaultsprompt cascade no longer synthesizes a prompt on body-less start/exit passthrough nodes (a shared-prefix-fragment refactor is now inert on structural nodes, no opt-out needed), and the fixed\n\nprompt-join separator is documented. Transparent adoption — no tracker code change, all examples stay A-grade.
Changed
- dippin-lang pinned to v0.63.0. dippin renamed the dip-2 subgraph call-site binding keyword from
params:toinputs:(dip-1 keepsparams:). Both spellings parse to the same IR field, which tracker’s adapter already serializes to thesubgraph_paramsthe runtime reads — so the new keyword is adopted with no code change, backed by a new round-trip regression test. tracker’s built-in examples are dip-1 and unaffected; all remain A-grade.
Changed
- dippin-lang pinned to v0.62.1. dippin now marks Mistral and Cohere as verified to have no cached-input discount (cache reads bill at the full input rate). tracker's cache overlay already defers to any dippin-supplied cache rate, so this is adopted with no code change — the overlay's fallback set shrinks to just MiniMax and Qwen, the last two providers dippin hasn't yet verified.
Added
tracker doctorwarns on models retired from the first-party provider API. Checking a pipeline file, it flags any node pinning a model dippin marks deprecated (404s on the first-party endpoint, still bills via a Bedrock/Vertex passthrough). Gateway-aware: suppressed whenTRACKER_GATEWAY_URL/TRACKER_GATEWAY_KINDis set, since the model then routes to a passthrough. Catches “you pinned a retired model for a first-party run” before it 404s.
Changed
- dippin-lang pinned to v0.62.0. Adds a workflow-level
defaults: system_prompt_file:fallback (a shared system prompt for agents that declare none). Resolved by dippin into each node’ssystem_prompt, so tracker reads it transparently — no adapter change. All examples A-grade.
Changed
- dippin-lang pinned to v0.61.0. Ships cache-read rates for DeepSeek/GLM/Grok/Kimi (dippin#232, filed by tracker), so tracker's cache overlay self-disables for those too and now covers only the shrinking remainder (Mistral, Cohere, MiniMax, Qwen). No tracker code change; all examples A-grade.
Declared-inputs epic complete. A subgraph can now drive its child's declared inputs, and tracker rides dippin's dip-2 retry channel + corrected OpenAI cache pricing.
Added
- Subgraph call-site input binding (#556). A
subgraph … params:call site now drives the referenced child's declaredinputsthe same way a top-level run does: the parent's params are validated against the child's declared value-kind inputs (fail closed on a missing-required / invalid value) and seed the child's closed${inputs.*}namespace. The runtime half of dippin's DIP160 arity lint (requires dippin ≥ v0.58).file/secretinputs aren't bindable from a call site (a params string can't be staged) and are resolved by the child itself. Completes the declared-inputs epic (#553).
Changed
- dippin-lang pinned to v0.59.1. Adopts Option A for the dip-2 retry channel (
retry_target/fallback_retry_targetnode attributes the engine already reads — no engine change; migration now lossless, #563), and drops tracker's OpenAI cache-rate override now that dippin fixed the per-familycache_read_mult(gpt-4o 0.5×, gpt-4.1 0.25× — dippin#225, reported by tracker). Cache pricing for Anthropic/Gemini/OpenAI is now sourced straight from dippin; the overlay only fills the still-unpriced tail providers.
Changed
- dippin-lang pinned to v0.58.0 (from v0.57.0). Adds DIP160, a cross-file lint that flags a
subgraph … params:call site omitting a required input of the referenced child (inputs epic Phase 3). Lint-only, no API/schema/pricing change; all examples remain A-grade.
Changed
- dippin-lang pinned to v0.57.0 (#562):
gpt-5.2-codexnow prices (no more $0 /--max-costescape), the DIP159 false-positive on stagedfile/secretinputs is fixed (workaround removed from the build workflows), and cache pricing is now sourced from dippin for Anthropic/Gemini/GPT-5. tracker keeps its own per-model cache overrides for the gpt-4o (0.5×) and gpt-4.1 (0.25×) families — dippin v0.57 applies a flat 0.1× to all OpenAI models, which would underprice those cache reads 2.5–5× (reported upstream). All examples stay A-grade.
Checkpoint integrity. The resume-authoritative checkpoint moves out of the tool-reachable workdir into the secure state dir.
Security
- Authoritative checkpoint relocated out of the tool-reachable workdir (#559).
checkpoint.jsonis authoritative for resume (its edge selections pick the next edge, its context is restored), yet lived under the workdir where an unjailed tool subprocess could tamper it to inject control flow ontracker -r. The authoritative copy now lives in the secure state dir (the same location as the activity log, #213) and resume reads it secure-first; a best-effort snapshot under the artifact dir keepsdiagnose/auditworking (non-authoritative — tampering it corrupts only diagnostics, never resume routing). Residual, same as the activity log: relocation removes the relative-path tamper vector, not the same-UID absolute reach — thewritable_pathsjail remains the boundary for untrusted tool nodes.
Fixed
- In-flight tool call no longer dropped on stream truncation (#546). A stream truncated mid-tool-call (start + deltas, no end/finish — a dropped connection or provider truncation) left the call un-flushed, so the response looked like the model called no tool.
StreamAccumulator.Response()now flushes the partial call so the fail-closed truncated-tool-call guard acts on it instead of it vanishing.
Changed
- dippin-lang pinned to v0.56.0 (from v0.55.0; #561) — inputs Phase 2 declaration lints (DIP158/DIP159), no API/schema/pricing change. All examples stay A-grade.
Changed
- dippin-lang pinned to v0.55.0 (from v0.54.0). All examples remain A-grade; golden traces, complexity, and the #558 pricing integration unchanged.
Checkpoint write hardening.
Security
- Checkpoint atomic write is
O_NOFOLLOW/ symlink-refusing (#559, partial).checkpoint.jsonsits in the tool-reachable workdir, so an unjailed tool could pre-plant a symlink atcheckpoint.json.tmpto redirect the runtime's own write to an outside target. The temp write now refuses a symlink there. (The checkpoint remaining workdir-resident and authoritative-on-resume is tracked in #559; keep tool nodes jailed withwritable_pathsas the mitigation.)
Secret inputs. Workflows can now take secret-kind inputs without the value leaking into prompts, logs, traces, or checkpoints.
Added
- Secret inputs (#555). A
secret-kind input (previously refused) is staged to a0600file at<workDir>/.tracker/inputs/<name>and${inputs.<name>}resolves to the path, never the value — so the secret never enters a prompt, the provider wire, the trace, or the checkpoint. Supply it withtracker.SecretInput(name, value)and read it from the staged path in a tool..tracker/is now git-excluded from artifact repos so staged secrets never reach a commit or bundle. Residuals (documented): the 0600 file on same-UID local disk, and the value on the provider wire once the agent uses it.
Rate-limit backoff fix.
Fixed
- Server
Retry-Afterhonored on rate-limit retries (#549). The retry middleware readRateLimitError.RetryAfterbut nothing populated it — a dead branch, so 429 retries ignored the server's requested delay and used only local exponential backoff (risking harder throttling). Adapters now parse theRetry-Afterheader and the middleware honors it (wrapped errors included, viaerrors.As), capped at 2 minutes.
One pricing source of truth. LLM model prices now come from dippin's
pricing package instead of a hand-maintained table in tracker — one cost
implementation, nothing to drift.
Changed
- Model prices sourced from
dippin-lang/pricing(#558; requires dippin ≥ v0.53, pins v0.54.0). tracker's price table is retired; base input/output rates + model/alias/version-fold resolution come from dippin, andEstimateCostcallspricing.Cost.llm.ModelInfodropsInputCostPerM/OutputCostPerM(capabilities + cache multipliers stay). Reasoning is not double-counted (tracker'sOutputTokensalready includes it). Cache rates stay in tracker as an overlay until dippin'sprices.jsoncarries them. A guard test fails if a catalog model stops being priced by dippin (silent $0 / escapes--max-cost).
Fixed
- DIP157 lint error in the build workflows — a Setup comment contained the literal
${inputs.spec}token, which dippin's linter flagged as an error, droppingbuild_product/build_product_with_superspecbelow grade A under dippin ≥ v0.51 (shipped so in v0.58.0). Reworded; both back to grade A.
Tool-safety hardening from the security-boundary hunt.
Fixed
- Pipe-to-shell denylist bypass (#554). The pipe-to-shell deny patterns require spaces around the pipe, but pipe spacing was never normalized —
curl x|sh,cat p|bash, and half-spaced variants evaded every pattern. Pipe operators are now normalized before matching. (The Landlock jail remains the real enforcement; this restores the defense-in-depth guard.)
Security
inputs.*reserved from declared writes — an agent declaringwrites: inputs.foocould otherwise forge a "caller input" downstream.- Audit-log threat model clarified — an unjailed same-UID tool subprocess can reach/truncate the secure activity log (inherent same-UID residual, not a new bug); the
writable_pathsjail is the boundary for untrusted tool nodes.
Declared pipeline inputs. A workflow can now declare a typed input signature
(a dippin inputs block) that the engine introspects, validates, and binds at run
start — the seam a host (tracker-runner, web/Slack) uses to collect caller data instead of
the agent proceeding with nothing. Plus RunManager cancel/isolation fixes and dippin v0.51.1.
Added
- Declared pipeline inputs (#553; requires dippin ≥ v0.51).
tracker.DescribeInputsintrospects the schema without running;tracker.ValidateInputsreturns structured per-input errors so a host re-prompts precisely;Config.Inputs(StringInput/FileInput/FileInputBytes) is validated and bound at run start — a missing required input (empty/whitespace counts) fails closed before any node runs instead of expanding to empty string. Values read via a dedicated, closed${inputs.*}namespace that is untrusted by construction (never interpolated into atool_command). - File inputs are staged into
<workDir>/.tracker/inputs/<name>— a fixed path derived from the declared name, written0600/O_NOFOLLOWwith a 10 MiB cap — so a workflow's shell reads the staged file directly (never the untrusted value).build_productandbuild_product_with_superspecnow take aspecfile input and adopt it asSPEC.md, so a host can drive the flagship build headlessly. Resume-safe; a suppliedsecretvalue is refused until value-redaction lands.
Changed
- dippin-lang pinned to v0.51.1 (from v0.49.0) — adds the native
inputsdeclaration IR. All examples remain A-grade; golden traces unchanged.
Fixed
- RunManager.Cancel unblocks a run parked at a webhook human gate (#551).
WebhookInterviewernow implementsContextSetterand releases the gate on context cancellation, instead of leaking the capacity slot and goroutine until the ~10-minute webhook timeout. - RunManager workdir isolation no longer collides on sanitized keys (#552). Distinct keys that fold to the same sanitized segment (e.g.
feature/xvsfeature_x) previously shared one working tree; the per-run directory now appends a short deterministic hash of the raw key.
Provider-layer reliability. Six silent-failure and misclassification bugs found by adversarial review of the LLM adapters and the retry/error-classification layer — the code a production client hits hardest — each fixed with a proof-carrying regression test.
Fixed
- Quota exhaustion now pauses resumably, not burns retries (#547, critical). An OpenAI/compat
insufficient_quotaHTTP 429 was mapped to a retryable rate-limit by status code alone — retried against the empty balance, never paused (#487), never failed over (#486). It now maps to a non-retryable quota error and routes to the resumablepaused_billingterminal; a plain rate-limit 429 stays retryable. - claude-code auth/budget/OOM errors hard-fail instead of retrying a dead credential (#548). The subprocess classifier's fail verdict was discarded and defaulted to retry, burning the global restart budget against a permanently-broken credential; it now hard-fails (credit-balance still pauses resumably).
- LLM streaming silent-failure classes (#542, #543, #544, #545): a truncated tool-call stream no longer masks as complete (bypassing the #507 guard); a stream ending before its terminal marker now fails closed instead of a bogus empty success; a Gemini prompt safety-block surfaces as an error; and openai-compat cached/reasoning tokens are counted (were priced at the full input rate).
Resume, parallel & context-management correctness. Nine bugs found by adversarial review of the checkpoint/resume, parallel fan-in, and agent compaction paths — several silent failures that could crash or corrupt a run — each fixed with a proof-carrying regression test.
Fixed
- Context-window utilization now counts cache tokens (#539). Under Anthropic prompt caching (the default), utilization read ~1% on a nearly-full window because only
InputTokenswas counted (cache tokens are separate) — so auto-compaction and the window warning never fired and context grew until a hard HTTP 400.Updatenow sums input + cache-read + cache-write. This had silently disabled the whole compaction subsystem in the default config. - Passed goal gates stay satisfied on resume (#533). Per-node outcomes are now persisted in the checkpoint, so a run interrupted after a goal gate passed no longer re-enters escalation (or flips success→fail) on
tracker -r. - Parallel branch billing pause halts resumably (#538). A fan-out branch hitting billing/quota exhaustion now propagates the resumable
paused_billingterminal instead of being flattened to a fail — or, under the “any” policy, masked as node success. - Empty-response retry no longer sends an invalid message (#540); auto-compaction no longer drifts off across repair turns (#541); memoization won’t replay a stale outcome at non-full fidelity (#534); child overrides don’t double-count across resume (#535); revisited nodes don’t replay a stale edge selection (#536); parallel branch-target events carry the run id (#537).
Changed
- Internal dedup pass (#395, partial): collapsed three byte-identical/near-identical clusters (terminal
EngineResultconstruction,isStandardProvider, the review-modal markdown renderer) with no behavior change.
Richer edge conditions and a tidier outcome contract. Edge when guards gain numeric
and regex operators, and the tool handler's outcome fields move under a self-documenting sub-struct.
Added
- Numeric and regex edge-condition operators (#504). Edge
whenconditions gain numeric comparison —<,<=,>,>=(float-coerced) — and a regexmatches/not matchesoperator, on top of the existing=/!=/contains/startswith/endswith/in/&&/||. Numeric andmatchesoperators require surrounding spaces (like==). A malformed right-hand literal (a non-numeric numeric operand, an invalid regex) is an author error surfaced at validate time, while a non-numeric runtime value on the left warns and yields false, so a valid condition still passesdippin doctor. An unspaced numeric comparison (ctx.count>=5) fails loudly with a “use spaces” hint instead of silently degrading into an always-false equality.
Changed
- Grouped the tool handler's outcome detail fields into a
Tool ToolDetailsub-struct (#454).Truncations,MissingMarker, andMissingRoute— all populated only by the tool handler — now live underOutcome.Tool, so the struct documents its own population rules and the zero value is unambiguous. Behavior-preserving (golden-trace parity). Scoped deliberately: cross-cutting fields (Stats) and single fields stay top-level.
Test-fidelity and routing cleanup. A verify gate can now fail tests that only pass by timing luck, and the parallel fan-in join hint rides the typed outcome channel instead of leaking into user-visible context.
Added
tracker verify-tests --raceruns the Go race detector as a fidelity gate (#489, partial). On top of the existing duplicate/near-duplicate test-body detection,--raceadds ago test -race ./...pass so aVerifyMilestone-style gate fails on tests that pass only by timing luck — one of the concrete fidelity holes #489 identified (a real data race slipped through a green verify). Opt-in, a no-op for non-Go milestones (nogo.mod/go.workorgotoolchain), and anchored to the detector’sWARNING: DATA RACEbanner so a failing test’s own output can’t spoof it; a cgo-unavailable run reports a skip, not a spurious failure. New library entry pointtracker.DetectTestRaces. The fuzzy fidelity heuristics from #489 remain tracked separately as design-first work (#532).
Changed
- Parallel fan-in join hint rides the typed
Outcome.SuggestedNextNodeschannel, not a raw context string (#451). The parallel handler previously encoded its fan-in-join routing hint as a comma-joined string written directly into user-visible pipeline context; it now sets the typedOutcome.SuggestedNextNodes []stringfield, and the engine’sapplyOutcomeis the single documented serialization point that edge selection, checkpoint routing-hints, and memo replay read. Behavior-preserving for routing/resume/memoization; as a side benefit the per-stagestatus.jsonrecords the hint in its semanticsuggested_next_idsfield.
Runaway-detection fix and the security-boundary documentation batch. The no-progress detector now
catches a tight-looping agent that never advances the workspace, and the writable_paths jail gains its
reference docs, audit checklist, and a security-PR process pattern.
Added
- “Freeze and prove” process pattern for security-boundary PRs (#286). New
docs/architecture/security-pr-process.mdcaptures the discipline that would have shortened #275’s 13-round review: spec-first threat model → freeze the public API before implementing → prove the contract with invariant/property tests → audit-class sweep → keep the implementation a small patch against the frozen contract. - Linux security-primitives reference doc (#284). New
docs/architecture/linux-security-primitives.mddocuments the kernel-level primitives thewritable_pathsjail relies on — Landlock ABI v3, theopenat2RESOLVE_BENEATH | RESOLVE_NO_SYMLINKS | RESOLVE_NO_MAGICLINKSflag set, theEACCES/EXDEV/ELOOPclassification, the race-safemkdirat/unlinkatpattern, the/proc/self/exe __jail-execre-exec, the refuse-to-start gates, and the residual escape classes — each claim citing its source file. Docs only, no behavior change. - 9-class reviewer audit checklist for
writable_pathsjail changes (#285). Newdocs/architecture/writable-paths-audit-checklist.mdenumerates the nine recurring failure classes from the #275 review rounds, each with what to verify, the audit prompt, and what pass/fail looks like, plus a copy-paste PR checklist snippet.
Changed
- codergen claude-code/ACP config parsers route through the typed
AgentNodeConfigaccessor (#393). Eight rawnode.Attrs[...]reads now go throughnode.AgentConfig(...), behavior-preserving; a guard test pins that exactly one justified raw read (max_budget_usd, which surfaces a parse error the lenient accessor swallows) remains. cmd/trackerrun configuration is threaded through an explicitrunOptionsstruct (#396) instead of ~11 mutable package-level globals — configuration is now passed, not smuggled. Behavior-preserving: no CLI flag, default, or precedence changed.- Relocated the enriched-sprint domain workflow out of the primitive tool library (#452).
write_enriched_sprint.go(1,250 lines) and its companion moved to a newagent/tools/sprintwriterpackage soagent/toolsstays generic primitives. Tool names, schemas, and output are unchanged. - Split the 1,687-line
tracker_doctor.gomonolith into concern-based files (#453). Behavior-preserving pure extraction —tracker doctor’s checks, ordering, and report shape are unchanged; each new file is under the 500-line ratchet ceiling, with new per-check unit tests exercising the now-isolated helpers.
Fixed
- No-progress detector keys on workspace edits, not raw tool-call activity (#531, follow-up to #304). The shipped detector counted a turn as progress whenever any tool was called, so a tight-looping agent that re-reads files or re-runs a failing command every turn reset the counter forever and never tripped — the precise runaway #304’s AC2 set out to catch. Progress now keys on a successful edit landing (an in-session proxy for a new commit / verify-state change); read-only investigative agents keep the legacy tool-call heuristic as a fallback, so their behavior is unchanged.
- Corrected the Landlock ABI-v3 kernel version in the new security docs (#284, #285). ABI v3 (
LANDLOCK_ACCESS_FS_TRUNCATE) first shipped in Linux 6.2, not 6.7 (6.7 is ABI v4, TCP network rules, which the jail does not use). Docs/prose only — the runtimeabi < 3gate was already correct. - Fail closed on length-truncated tool calls (#507). When a turn was cut off by the token limit and the partial response carried tool calls, the session executed them anyway — running a tool on arguments the model never finished emitting. The turn loop now rejects all tool calls in a truncated turn without dispatching, replies asking the model to re-issue with complete arguments, and surfaces the rejection via
EventError. Complete calls are unaffected. - Validation-override flip-point dedup is scoped to the current checkpoint generation (#279). A workflow that legitimately re-traverses the same gate and accepts the same label in a later generation (after a resume) no longer has that re-occurrence silently suppressed; within-generation duplicates are still collapsed to one entry.
Cost observability. An uncatalogued model is no longer silently priced as $0,
and the catalog’s prices are pinned to a published source with a build-time drift guard.
Added
unpricedsignal so an uncatalogued model isn’t silently$0(#518).llm.IsPriced/EstimateCostCheckedreport when a cost couldn’t be computed because the model isn’t catalogued;RunTotalsandrun.jsongainunpriced+unpriced_models. When a cost ceiling is set (--max-costor a.dip defaults:block) and unpriced usage occurs, a warning names the model the ceiling couldn’t bound — a signal, not a hard failure, so a genuinely-free local model still runs.- Published-price provenance + a build-time drift guard (#518). Each catalogued model records its provider-published input/output/cache prices and the source page they came from; a test fails the build — naming the model and its source — if a catalog price and its provenance disagree, closing the class of silent-pricing bug the #519 review found.
The public website and docs were also refreshed to match everything shipped since v0.46.0.
Embedder ergonomics and cost accuracy. A library caller now gets the same on-disk run capture the CLI produces from a single config flag, and multi-model runs are priced correctly.
Added
Config.Captureseam — embedders get the same run capture the CLI produces (#530). SettingConfig.Capture = &tracker.CaptureConfig{}wires the activity-log capture, the trace observer, the session-owned de-dup, and therun.jsonfinalization internally — no more hand-wiring three event seams.cmd/trackernow routes its own capture through the same option, so CLI and library share one path and can’t drift.
Changed
- Converged the duplicate per-turn cost keys onto one name set (#520) — wire/log schema change. The
cache_read_tokens/cache_write_tokens/estimated_costspelling survives; the duplicatetoken_cache_read/token_cache_write/turn_cost_usdkeys are removed from the NDJSON wire,activity.jsonl, and the supported readers. Values are unchanged — a consumer that read the dropped keys switches to the surviving ones.
Fixed
TokenTrackerprices per (provider, model), not provider-only last-model (#527). Two models of one provider in a run were previously priced at whichever model was observed last, so the total flipped on call ordering. Usage is now keyed by(provider, model)and each bucket priced at its own rate, so the total is correct and order-independent.
Security patch hardening the run-capture files shipped in v0.50.0.
Fixed
- WIP-preserve no longer stages
.tracker/into git objects or exported bundles (#528). Terminal-failure WIP preservation now excludes.tracker/unconditionally, so tracker’s own capture artifacts (verbatim prompts, tool output,request_raw) never travel in--allbundles, independent of the user project’s.gitignore. - Hardened post-run capture-file writes (#521, #529). The spec artifacts and
run.jsonare now created0600with the mode applied before any content and withO_NOFOLLOWplus a symlink pre-flight — closing a world-readable window and refusing a pre-planted symlink, matching the activity-log mirror.
Run capture and cost correctness. A finished run is now reconstructable from the
executed spec, the verbatim provider request bodies, per-call/turn/session identity, and a
run.json manifest — shipped alongside a batch of cost-accuracy and security fixes.
Added
- Run capture (#519). Persists the executed spec (
workflow.dip+workflow.ir.json+ input files, since dippin expands subgraphs at compile time), the verbatim wire request body sent to each provider (from all four adapters), and session/turn/call identity, node kind, attempt number, untruncated tool input, per-turn economics, and finish reason onto everyactivity.jsonlline. Arun.jsonmanifest is assembled at run end;tracker run-json -w <dir> <runID>backfills it for archived or SIGKILLed runs.
Fixed
- Phantom cache-write premium on OpenAI/Gemini (#522). The cache-write premium default is now 0 (OpenAI prompt-cache writes are free; Gemini bills time-based storage); the 1.25× premium is set explicitly only on the Anthropic models that charge it.
- Cache-write cost was priced at 0.25× instead of 1.25× (#519), understating every cached Anthropic run’s cache-write cost by 5×. Cache multipliers moved onto
ModelInfowith per-model overrides. Also fixed Gemini dropping billed thinking tokens, mixed-backend token undercount inrun.json(#523), and session cost now prices with the response’s actual model so--max-costholds through a provider failover (#524). - The run-dir
activity.jsonlmirror was world-readable (#525). Post-capture it holds verbatim provider request bodies and full tool output; it is now created0600-in-0700, restoring the #213 secure-log model. The capture identity fields also now reach the live--jsonwire, not just the audit log (#526).
Coverage & stability. Pins the previously-unverified handler/terminal contracts with golden-trace fixtures and publishes an API-stability policy backed by an exported-surface snapshot that fails on any accidental signature change.
Added
- API-stability policy + exported-surface golden snapshot (#462). New
docs/api-stability.mdstates the one supported embedding surface (the roottrackerpackage), the open-enum rule, and the pre-1.0 deprecation contract. A golden-snapshot test enumerates every exported identifier of the root package and fails with a readable diff on any add/remove/signature change, so no public-API change ships silently. - Golden-trace fixtures for the five previously-unverified handler/terminal contracts:
validation_overridden,subgraphwith child-usage rollup,stack.manager_loop,mode=interview, and the recoverablepaused_billingterminal (#487) — each a path a control plane exercises that had no drift guard.
Fixed
tracker auditno longer classes a resumablepaused_billingrun asfailed(#514). Thestatus_classset gains a thirdpausedbucket so a credit-exhausted-but-resumable run is no longer indistinguishable from a dead one.RunManagerrace fixes (#516): no spuriousErrAtCapacitywhen a same-key run is resumed the instant it terminates, and a genuine failure is no longer misclassified asRunCanceled. Also alignedtracker diagnose’s blank-line injection counting withScanActivityLog(#517).
Policy, hygiene & coherence, built on the v0.47.0 embedding surface. An embedder now controls tool policy and diagnostic logging, and gate identity reaches the interviewer callback.
Added
- Fail-closed pre-execution tool-call guardrail hook in the agent loop (#506). A new
agent.GuardrailPolicyinterface runs before a tool executes, deciding on(tool, args, context); on deny the tool’sExecuteis never reached and the denial reason is returned to the model as the tool result. Fail-closed: a policy that errors or panics denies exactly that one call, and the built-in denylist (eval, pipe-to-shell,curl|sh) is enforced first. - Gate identity on the interviewer callback via an optional
handlers.GateAwareside-interface (#509 callback side).BeginGate(GateInfo)carries{RunID, NodeID, GateID, Mode, Label}, sharing one id with thegate_openedevent so an out-of-process transport can correlate the event with theAsk*callback it must answer. Purely additive and assertion-gated.
Changed
- Library diagnostics route to an injectable sink instead of the process logger (#449). ~40
log.Printfsites in the embeddable packages now emit through alog/slogsink that defaults to a no-op; inject one withtracker.SetDiagnosticLogger(*slog.Logger). ThetrackerCLI installs a stderr sink so its output is unchanged.
Fixed
- Closed three cross-cutting gaps a post-release audit found between the v0.47.0 event/wire landings:
run_idis now stamped on all handler-originated events, per-turn agent usage round-trips throughactivity.jsonl, and the buffered event handler never splits agate_opened/gate_resolvedpair under a lossy overflow policy.
Embedding surface completeness. The public event surface an out-of-process control plane drives Tracker through is now complete: every structured payload the in-process stream carries is on the documented NDJSON wire and readable back from the audit log.
Added
- The public NDJSON wire is at payload parity with
activity.jsonl, and the supported activity-log reader is lossless.tracker.StreamEventandActivityEntrynow carry every structured payload the engine emits — cost snapshot, edge-decision detail, tool-node diagnostics, gate lifecycle, per-turn agent usage (#508 attribution), and the run-start node snapshot — all purely additive, with field names identical across the writer and both readers, enforced by a mechanical parity test. - Gate lifecycle events on the pipeline stream (#509). The human gate handler emits
gate_opened/gate_resolvedaround every interviewer call, each carrying a sharedgate_id, so an event-sourced consumer can reconstruct which question was asked and which answer came back from the stream alone. paused_billingis a first-class, resumable state for embedders.RunManagergained aRunPausedstate mapped from the engine’s recoverablepaused_billingterminal (#487), so a credit-exhausted run no longer looks dead to a control plane;ManagedRun.ResumeRunID()returns the id to feed back for resume.- Submit-time variable-availability validation (#505).
validate/simulate/doctorand the library’sValidateSourcereject actx.<key>reference that no node can produce — a misspelled routing key fails at submit instead of silently mis-routing at runtime. - Bounded/async event-handler seam —
pipeline.BufferedPipelineHandler/BufferedAgentHandlerdrain a bounded queue on a background goroutine with a caller-chosen overflow policy, so a slow subscriber (a control plane POSTing events) can’t block the engine. A terminal-status event is never dropped under any policy.
The transport-boundary release. The core is now fully UI-agnostic, with the Slack bot
(trackerbot) and a terminal REPL (trackerchat) as two first-class peers on one
library path, plus mid-run steering and per-run cost estimation.
Added
trackerchat— a terminal REPL transport, the second boundary consumer. Built from the sametransport/chatopscore as the Slack bot, adding only a terminalThreadUIand a stdin loop — concrete proof the boundary is I/O-only.trackerbotSlack Tier-3 surfaces: the/trackerslash command (run from anywhere, no@mention) and an App Home tab showing a how-to plus a live list of active runs.Config.SteeringChan— mid-run context injection. Maps sent on the channel merge into the pipeline context between nodes, visible to edge selection and the next node’s prompt (namespaced understeer.). Backstrackerbot’ssteer <guidance>command, plus thebump <dollars>budget-recovery command.
Escape-aware condition parsing, reconciled with dippin v0.49. dippin-lang bumped
v0.48.0 → v0.49.0; no breaking changes; core embedded workflows hold their A grades on
dippin doctor.
Changed
- Condition parsing is now escape-aware (#444 reconciliation, #470). Logical splitting (
||/&&) and comparison-operator discovery (=/==/!=and the word operators) both ignore content inside double quotes; exactly one surrounding quote pair is stripped from every RHS and\"/\\are decoded inside it. An unmatched double quote is now a loud error — carrying the original expression and, at load, the.dipsource location — instead of a silent mis-parse. Semantic validation applies the same quote-aware grammar to every logical branch independently. - dippin-lang bumped v0.48.0 → v0.49.0. The library dep, the CI
dippinCLI, andtracker_doctor.go's pinned version move together. tracker-swebenchmain()decomposed (#469). The 200-line entrypoint (cyclomatic 44 / cognitive 74) was split into behavior-preserving helpers under the complexity-8 gate; each container is now bounded to 1 CPU and the prompt temp file is co-located under the run's results dir.- Added a rolling
ROADMAP.md(Now/Next/Later) and a matching website Roadmap page; corrected inaccurate CLI/workflow examples on the site.
Engine-correctness hardening and a real, CI-enforced complexity gate. dippin-lang bumped
v0.43.0 → v0.48.0; no breaking changes; core embedded workflows hold their A grades on
dippin doctor.
Fixed
- Condition evaluator no longer misroutes on
||/&&inside values (#444). Splitting is now quote-aware, so a condition value containing||/&&(a URL, regex, or stderr fragment) evaluates correctly instead of silently splitting into phantom clauses. - Human-gate timeouts no longer leak the interviewer goroutine (#446). On timeout the handler now cancels the interviewer, turning a permanently-blocked goroutine into orderly teardown.
- claude-code error classification is no longer flipped by benign output (#447). Network/budget matchers are anchored to error-shaped phrases instead of bare words, so an unrelated log line can't turn a hard failure into an infinite retry.
- The interview-result handler fails the node instead of panicking (#448) on a JSON marshal failure.
- The
make cicomplexity gate is real, green, and CI-enforced (#468). A checked-in ratchet baseline grandfathers existing debt and may only shrink; a new or worsened violation now fails the build.
Changed
Outcome.Statusis now typedTerminalStatus(#445). A typoed status string now fails to compile instead of silently routing to the wrong branch.
An autonomous spec-forge loop lets build_product self-heal a failing spec-coherence gate
instead of dead-ending at a human. No breaking changes; core embedded workflows hold their A
grades on dippin doctor.
Added
- Autonomous spec-forge loop in
build_product. A failingSpecLintgate now routes to aForgeSpecagent that editsSPEC.mdto resolve findings, aCheckSpecFidelityoracle proves no requirement was silently dropped, and the loop re-lints until clean — capped at 3 attempts and failing closed viaSpecForgeFailedso autopilot/auto-approve can't ship a broken spec. The original spec is snapshotted and every ruling logged to.ai/decisions/, surfaced to the operator atApprovePlan.
Fixed
- Packed
.dipxruns fail loud when a workflow needs${graph.workflow_dir}(#430). A content-addressed bundle has no source dir, so the value was silently empty; a packed run referencing it now errors before any node executes, naming the offending node(s).
build_product's milestone gate loop gets six fixes closing a cluster of
false-positive "missing output" and lint-scope bugs found during dogfooding. No breaking changes; core
embedded workflows hold their A grades on dippin doctor.
Fixed
- Milestone lint gate is now milestone-scoped (#436), so a milestone no longer fails on earlier, already-accepted milestones' lint debt.
- FixMilestone now sees the failing gate's real stdout (#437) instead of being blind to the lint/CI failure it never ran.
- CheckMilestoneOutputs no longer flags unbuilt future milestones (#439) or misparses declared paths from prose instead of backticked tokens (#440).
- Milestone retry cap is exact (#443) — a cap of 3 now runs exactly 3 attempts, not 4.
Changed
Review-fidelity hardening for build_product plus two new engine-resilience primitives:
opt-in node-output memoization and sleep-aware budgets. No breaking changes; core embedded
workflows hold their A grades on dippin doctor.
Added
- Opt-in content-hash node-output memoization (#421). Annotating an agent node with
params: { memoize: true }lets a loop-restart replay a stored successful outcome instead of re-running — keyed on a SHA-256 of resolved inputs; off by default; never applied towritable_pathsnodes. - Sleep-aware budgets (#422). Opt-in
--sleep-aware-budgetexcludes OS-suspend spans frommax_wall_timeandstall_timeoutaccounting via the monotonic clock, so a laptop that sleeps mid-run no longer spuriously trips the budget on resume. build_productderives spec-contract artifacts (#306) and catches self-ratifying tests (#417) and contract-fidelity gaps at external seams (#416).
Fixed
- Silent error swallowing in the Gemini translator, TUI review, and CLI env loading (#397) is now surfaced instead of dropped.
Removed
- Dead code and unwired middleware abstractions removed (#394) — deletion-only, no behavior changes.
Human gates now display their authored prompt: body, and build_product's
plan-approval gate finally shows the actual plan. One fix, one behavioral change; no breaking
changes; core embedded workflows hold their A grades on dippin doctor.
Fixed
- Human gates now display their
prompt:body, not just thelabel:. Thewait.humanhandler built the gate prompt fromnode.Labelalone and silently droppednode.Attrs["prompt"]for freeform/choice/yes_no modes. Every human gate that authored a multi-lineprompt:— including v0.40.1'sEscalateMilestone"Verify currently" block (#407) and the newApprovePlanplan review — was therefore showing only its one-line label at runtime, and any${ctx.tool_stdout}/${ctx.*}interpolation the gate relied on to surface live state never rendered.resolveHumanPromptnow appends the expanded prompt body to the label; label-only gates are unchanged. (Surfaced by automated review of the #414 super-PR; the prior #407 test only asserted the attribute's content, never that it was displayed.)
Changed
build_productplan-approval gate now shows the actual plan. TheApprovePlanhuman gate previously displayed only its one-line label, so an operator had no plan to review and could only rubber-stampapprove. A newShowPlantool node cats.ai/decisions/milestones.md(the milestone plan) and.ai/decisions/requirement-coverage.md(the spec-coverage table, with theCOVERAGE_GAPScount) intoctx.tool_stdout(with a 1MBoutput_limitso a large plan isn't clipped), andApprovePlaninterpolates it under a## Plan under reviewheading — rendering the full plan in the fullscreen review modal.Decomposesuccess now routes-> ShowPlan -> ApprovePlan; the approve/adjust/reject routes are unchanged.
build_product dogfood hardening — a single super-PR fixing a real case-study
cascade (run 634a2527ff56) where a finished, green build was discarded. Five fixes
plus the automated-review hardening pass; no breaking changes; core embedded workflows hold their A
grades on dippin doctor.
Fixed
- Turn-limit green fix no longer abandoned uncommitted (#406). The
commit-if-greenbreach guard (#303/#297) never fired on theImplement/FixMilestonefix loop because noverify_commandwas wired, so a breach with a passing tree classified asoperator_decisioninstead ofverified_green. Fixed with one shared green gate:Setupwrites.ai/build/verify.sh,TestMilestonedelegates to it, and the fix-loop nodes setverify_command: sh .ai/build/verify.sh; aFixMilestone -> CommitIfDirty when ctx.turn_breach_class = verified_greenedge commits the green tree before escalating. - Build artifacts no longer swept into checkpoint commits (#405).
Setupseeds.gitignorewith toolchain build outputs (not just.ai/), andCommitIfDirtydetects untracked executable binaries (git-nativegit diff --no-index --numstatbinary check) and gitignores them instead of committing — a compiled binary likegoblinno longer FAILs Verify as out-of-scope work. - Escalation no longer discards green trees silently (#407).
EscalateMilestonesurfaces the live verify result (${ctx.tool_stdout}), defaults to "mark done" to preserve finished work on the unattended path, and reframesabandonas a destructive last resort. - Behaviorally-correct code no longer FAILed over prose identifiers (#408).
VerifyMilestonegains an ADR-aware three-tier severity system (FAIL / WARN / PASS): a prose-named identifier that differs from the spec wording but whose behavioral tests are green and which is documented in a.ai/decisions/ADR now WARNs instead of hard-FAILing. Scope, unmet-behavior, and test-contract violations remain categorical FAILs. - Tests that pass but don't verify the contract (#409).
Implementself-applies the sameTEST-VERIFIES-CONTRACTrubricVerifyMilestonegrades against, including a test-shape rule: a "built binary" smoke test must spawn the built binary, not call an unexported function in-process. - PR #411 review hardening (automated multi-bot review of the super-PR).
verify.shcollapses every non-zero language-test-runner exit to1, reserving exit2strictly for the "makepresent but not installed" escalation (a pytest collection error exiting2no longer masquerades as the env-missing escalate);CommitIfDirtywrites the runtime binary-artifact ignore to the local, untracked.git/info/excluderather than the tracked.gitignore, so the skip itself is not an out-of-scope tree change; and theFixMilestonegreen-breach commit edge is guarded by awhen ctx.outcome = fail -> TestMilestoneshort-circuit declared ahead of it, so a stale (sticky)verified_greenclass from an earlier milestone can no longer checkpoint a non-green tree on a normal fail.
dippin-lang v0.43.0 lands four newly-wired IR fields, the engine grows per-node cost
and no-progress guards, and the commit-only node gains a real filesystem jail. Six
additions, three behavioral changes, six fixes; no breaking changes; core embedded workflows
hold their A grades on dippin doctor.
Added
override:edge attribute wired end-to-end (dippin-lang v0.40.0, closes #271 input gap).override: trueon a.dipedge now flows through the adapter intopipeline.Edge.Override, producingOutcomeValidationOverriddenon success. The runtime field finally has a.dipinput path.last_response_truncate:agent attribute enforced (dippin-lang v0.40.0, #56 chain-attack mitigation). Caps the Unicode character count of the prior node's response injected into a prompt, on both agent nodes and per-branch parallel overrides. The stored context value is unchanged — only the injected excerpt is capped.choice:edge attribute wired for human-gate routing (dippin-lang v0.42.0, DIP150).choice: approvedeclares a stable machine-readable routing key separate from the human-readablelabel:. The TUI always showslabel:;choice:becomes thectx.preferred_labelvalue matched by the edge selector in both choice and freeform mode.stall_timeoutgraph-level default enforced at runtime (#310). When no node completes within the declared wall-clock window, the run aborts through the sameOutcomeBudgetExceeded/on_failurecascade as other budget breaches. Set via thedefaults:block; flows through the adapter intograph.Attrs, resolved byResolveBudgetLimits, enforced byBudgetGuardwith a per-node progress clock reset on each node completion.- Per-node cost ceiling and no-progress detector (#304).
max_cost_usd: "0.50"halts a node when its cumulative session cost exceeds the threshold;no_progress_turns: 5halts after five consecutive turns with no tool calls. Both routeOutcomeRetryinto the existing retry/escalation path, support graph-level defaults with per-node overrides, and emitEventNodeCostLimitExceeded/EventNodeNoProgressDetected.max_turnsis now a coarse backstop that should rarely bind. commit_onlynode attribute for codergen agents (#349).params: commit_only: trueprepends a hardcoded scope-restriction block to the system prompt, preventing the agent from authoring new implementation even when failure context is present. Enforced across all three backends (native, claude-code, ACP).
Changed
- dippin-lang upgraded to v0.43.0 (from v0.39.0). Wires the four IR fields above and adapts two internal load paths to the v0.43.0 API cleanup that removed
validator.Result.Errors()(now countsSeverityErrordiagnostics directly) anddipx.Bundle.Lookup(replaced byBundle.Workflow(ctx, refPath, relativeTo)); also carries validator explain-text fixes (DIP109/113/114/116). The v0.43.0else ->section-level funnel default is not yet wired in the adapter (follow-up). build_product.dipFinalCommit is now mechanically commit-only (#349). On top of thecommit_onlyprompt/engine guard, FinalCommit declareswritable_paths: .git/**, .ai/**, wiring the native fs-jail (#272) so its file-mutation tools can only write under.git/and.ai/. It physically cannot author product source even when failure context primes it to "just fix it." Linux native backend only.- build-context.md refreshed with active source files at each MarkMilestoneDone (#351 item 3).
MarkMilestoneDoneappends an updated "Active source files" entry per milestone so agents reading the orientation file see what's being actively worked, not just the initial entry-point list.
Fixed
- Git subprocesses no longer inherit leaked
GIT_DIR/GIT_INDEX_FILE(#399). When tracker runs from inside a git hook, git honors inherited repo pointers over a command's-C <dir>, redirecting the artifact-repogit init/add/commit— andExportBundle'sgit bundle create— at the outer repository.gitSafeEnvnow stripsGIT_DIR,GIT_INDEX_FILE,GIT_WORK_TREE,GIT_OBJECT_DIRECTORY, andGIT_COMMON_DIRunconditionally (even underTRACKER_PASS_ENV=1), with case-normalized key comparison. build_productmilestone test-gate scope andacceptverification bypass (#392). (T1)TestMilestoneran a whole-treego test ./...inside a milestone-scoped fix loop, so an unrelated package failure was un-fixable on every retry; the test run is now scoped to the packages the milestone actually changed, with the whole-tree suite still gating atFinalBuild. (T2) Theacceptescalation option bypassed cross-review + final build/test + spec-check; it now routes throughCheckMilestoneOutputsso accepting still earns every gate. No engine changes.- Remaining example workflows now hold A grades under
dippin doctor(#335 scope 3).megaplan,megaplan_quality,ralph-loop, andsemportwere structurally hardened with graph-levelon_failure: Exitcatch-alls andmax_restartsdefaults (resolving DIP134 restart-budget confusion). All four now reach A/100. build_productFinalCommit commit scope (#349). TheFinalCommitagent node now carriescommit_only: true(engine-level scope guard) to prevent it from authoring new implementation when failure context is present in the conversation window. The prompt already contained explicit "do NOT implement" language; the engine-level guard makes that restriction structurally enforced rather than advisory-only.- Non-embedded example workflows now hold A grades under
dippin doctor(#335 scope 2). Nine example.dipfiles (consensus_task,consensus_task_parity,sprint_exec,human_gate_showcase,parallel-ralph-dev,semport_thematic,fix-tracker-visibility,human_gate_test_suite,reasoning_effort_demo) were structurally hardened withon_failurecatch-alls,max_restartsdefaults, corrected subgraphref:paths, and removal oflabel:edges on interview-mode nodes (DIP129). All now reach A/95–A/100; core embedded pipelines unchanged. ${ctx.tool_stdout}/${ctx.tool_stderr}in agent prompts render as fenced blocks (#352 item 3). Interpolating these keys mid-sentence previously pasted raw tool output inline, garbling instructions. The expansion layer now wraps the values in a```textfenced block under their own heading, including per-node scoped references.
Case-study hardening rounds 5–6 — the audit log stops double-counting, per-branch
security overrides actually apply, a silently-inert failure route is refused at dispatch, and the
embedded built-ins clean up their act. Six fixes, no breaking changes; all four embedded
workflows hold A grades on dippin doctor.
Fixed
- activity.jsonl no longer logs every LLM event twice (#354). The client-level trace observer and the agent session both wrote the same stream — doubling audit volume and skewing
tracker diagnoseevent counts. One writer now owns the stream. - Per-branch
tool_access:/writable_paths:overrides reach the runtime (#368). The adapter silently dropped the per-branch security overrides the IR carries; they now propagate with the same fail-closed canonicalization and jail refuse-to-start treatment as agent-level attrs, and duplicate-target branches resolve deterministically (last branch wins). - Inert per-branch
fallback_targetis refused at parallel dispatch (#313 defect 2 — closes the issue). A fallback declared on a parallel branch target never did anything: branches bypass the engine's strict-failure path. The handler now fails fast before dispatching any branch — either attr spelling, on the target node or anybranch.N.*group (shadowed duplicates included) — pointing at the supported pattern:fan_in_policy+ a conditional fail edge at the aggregating node. - build_product_with_superspec: a failed cross-reviewer can no longer be masked (#313).
ReviewParalleldeclaresfan_in_policy: allwith a fail edge toEscalateToHuman, so a single failed reviewer escalates instead of vanishing into success-if-any aggregation. - Embedded deep_review goes from F/35 to A/100 under
dippin doctor(#335 scope 1). A graph-levelon_failurecatch-all + escalation gate replace eleven dead-stop failure paths; the review goal flows verbatim via the per-node scoped context key instead of an LLM-copied file; the analysis fan-out demands all three analyzers succeed. - build_product stops leaking tracker's own issue/PR numbers into agent-visible text (#316). 64 references excised from prompt text and tool echo strings — rules now state themselves on their own terms (#355 swept the last template-domain cruft, too).
The case-study hardening arc lands — agents stop hallucinating their location, injected context stops lying, and the expensive review fan-out is guarded by a sub-second structural check. All six changes trace to one real run that shipped an empty final milestone — and the review pipeline spent ~$10 rediscovering it.
Added
- Runtime-facts block in every codergen prompt (#347). Every agent prompt opens with a machine-written
# Runtimesection: the absolute working directory (“all commands already run here; never cd elsewhere”), the current date, and the run/node identity. In the case-study run, an agentcd'd to a hallucinated path, read the resulting clean tree as “the milestone is already complete,” and shipped nothing. Covers all three backends uniformly; codergen nodes only. - build_product: structural existence gate before the review fan-out (#350). A sub-second
CheckMilestoneOutputstool node reconciles the outputs Decompose declared (per-milestone**Files**:lists) against what is on disk, on both fan-out entries. A missing declared directory or a brokengo build ./...escalates to a human before any reviewer token is spent; missing files alone only warn — deletion milestones are legitimate.
Fixed
- Top-level
tool_access:now actually restricts the agent (#366). The dippin adapter dropped the typed IR field, so the documentedtool_access: nonespelling was silently ignored at runtime — the agent ran with its full tool surface. The typed field now propagates into node attrs, with precedence over theparams:spelling (which already worked). - Context injection capped and one-shot (#352 items 1–2). The auto-appended “Previous Node Output” is capped at 4KB — head+tail truncation with an explicit elision marker, per-node
injection_capoverride — instead of pasting whole 9KB transcripts into every downstream prompt and mis-anchoring reviewers. And a human gate's response is consumed by the first prompt-consuming node instead of replaying one stale “approve” into every later prompt as implied sign-off; the gate's scopednode.<id>.human_responsecopy keeps it for explicit reference, and the clear persists across checkpoint resume. - FinalCommit is commit-only in both build_product workflows (#349). A failure report in context once led the commit node to author an entire missing milestone (~700 lines) through zero quality gates. FinalCommit now stages and commits existing work only, fails closed on truncation, and escalates instead of implementing.
.tracker/run metadata stays out of the product repo (#351 items 1–2). CommitIfDirty'sgit add -Aswept run internals into checkpoint commits, polluting history and drowning the per-node build-context file. Setup now excludes.tracker/via.git/info/excludeand untracks anything a pre-fix run already committed.
Two gate-integrity holes that let a failed run report success are closed. Both came from one
real case-study run that finished “completed” with its final quality gate at outcome: fail:
the gate's verdict arrived as a markdown heading the parser missed, and the goal-gate retry replayed the
escalation tail without ever re-running the gate.
Fixed
- auto_status no longer fails open on goal gates, and heading-mangled STATUS lines parse (#346). Leading markdown heading markers are stripped so
## STATUS:failregisters like the bold shapes from #233; and when anauto_statusnode completes with no parseable STATUS line,goal_gate: truenodes resolve tofail— an unparseable verdict on a gate is an anomaly, not a pass. Plainauto_statusnodes keep the legacy success default. Either way the anomaly is observable: a newauto_status_missingaudit event lands in activity.jsonl andtracker diagnosesurfaces a per-node suggestion. - Goal-gate retry re-executes the gate instead of replaying the escalation tail (#348 defect 1). A persisted
gate_recheck_pendingmarker keeps redirected gates visible to the exit-time check even afterclearDownstreamdrops them from the completed set, and a still-pending gate re-enters at the gate itself so remediation done on the escalation path gets re-judged — a budget-free completion of the redirect cycle that fires even withmax_retries: 1. A never-satisfied gate now drains its budget and fails the run instead of reporting plain success. Checkpoint-resume deterministic; fix-loop retry targets behave exactly as before.
A parallel fan-out can finally demand that every branch succeed — and four LLM stream adapters
stop swallowing mid-stream errors. Parallel and fan_in nodes accept a configurable aggregation policy
(any / all / quorum), so a single failed adversarial reviewer can no
longer be masked by success-if-any. A full-project fresh-eyes review landed 25 individually verified fixes,
and build_product now tests every detected language stack in polyglot repos.
Added
- Configurable parallel fan-in aggregation policy (#313 defect 1). Parallel and fan_in nodes accept a
params:block (dippin-lang v0.39.0) withfan_in_policy: any | all | quorum(plusquorum: <n>). Default staysany(success-if-any, back-compat); both aggregation code paths honor the policy, misconfiguration fails fast, and a policy-caused failure names the failed branch IDs inEventParallelCompletedand thefan_in.policy_detailcontext key.build_product'sReviewParallelopts intoall, routing a single failed reviewer toEscalateReviewinstead of silently proceeding with a partial review set.
Fixed
- Fresh-eyes review: 25 verified fixes across stream-error surfacing, pipeline core, CLI, handlers, TUI, and examples (#356). Mid-stream SSE errors now surface on all four providers (Anthropic
overloaded_error/rate_limit_errorevents, Gemini{"error":...}chunks, OpenAI unparseable error payloads, openai-compat code/type-only errors); interleaved tool-call starts no longer drop the first call's arguments;ExpandGraphVariablesis a true single pass (no prefix clobbering, substituted values never re-scanned);tracker updatestages binaries under collision-proof temp names with close-error checks; ACP init failures reap the subprocess; webhook gates abort their in-flight POST on cancel; TUI search highlighting survives UTF-8 width-changing lowercasing. - build_product runs ALL detected build stacks in
TestMilestoneandFinalBuild(#305). The first-match if/elif chain skipped every stack after the first — a Go+JS repo never rannpm test. Every detected stack now runs and failures accumulate; sentinels,known_failuresskips, and single-language behavior unchanged.
Tool subprocesses learn the run's identity, and build_product's CI gate stops crying wolf
to humans. Locally-executed tool subprocesses now carry TRACKER_RUN_ID /
TRACKER_RUN_DIR / TRACKER_WORKDIR, closing the tool_access: none
agent → tool data-flow gap without the concurrent-run-unsafe ls -dt mtime heuristic. Multi-file
workflow trees also get two quality-of-life fixes: graph.workflow_dir seeding and
command_file: resolution on the raw .dip load path.
Added
- Run identity env vars in tool subprocesses (#323). Locally-executed tool commands receive
TRACKER_RUN_ID,TRACKER_RUN_DIR(the same rootWriteStageArtifactsuses), andTRACKER_WORKDIR— so a downstream tool cancat "$TRACKER_RUN_DIR/<NodeID>/response.md"for a specific upstream agent's output. Injected after sensitive-env filtering (and underTRACKER_PASS_ENV=1); operator exports of the same names are overridden, never duplicated. Env-only — no new${ctx.*}expansion keys. graph.workflow_dirseeded from the pipeline file's parent directory (#332). Workflows reference sibling files from any cwd:command: bash ${graph.workflow_dir}/scripts/setup.sh. Not seeded for embedded built-ins or.dipxbundles; an author-declared attr is never clobbered.
Fixed
- build_product: a failing
make ci/check/linttarget no longer escalates to a human as “make not installed” (#320). GNU make exits 2 on any recipe error, colliding with the rc=2 the CI probe reserves for make-missing — so fixable failures skipped the fix loop and reset the circuit breaker. Make failures now collapse to rc=1; rc=2 is sole-sourced to thecommand -v makecheck. exec *denylist no longer false-positives on fd-only redirects (#333). POSIX idioms likeexec 3>"$tmp"/exec 3>&-are exempt from the built-in pattern; the exemption fails closed (command substitution, unbalanced quotes, or any bare command word stays denied), and denylist matching is now whitespace-normalized so tab-separated commands can't evade space-separated patterns.- Raw
.dipload path resolvescommand_file:/prompt_file:/system_prompt_file:(#331). Multi-file workflow trees run viatracker /path/to/foo.dipfrom any cwd; resolution failures are fatal and name the node ID and path.
Epic #308 Phases 0–2 land —
build_product no longer dead-stops, loses work, or builds on a broken spec. The epic's case
study was a real run that went green, exhausted its turn budget before committing, and silently halted the whole
pipeline — skipping the entire cross-review safety net. Phase 0 stops the bleeding (failure routing at both
the engine and workflow layers), Phase 1 preserves work (commit-WIP, commit-first discipline, turn-limit guard),
Phase 2 enforces quality regardless of spec or language (language-native gates, requirement coverage, spec-coherence
preflight). Also ships reasoning_effort for Anthropic/Gemini and the dippin v0.38 pin.
Added
- Spec-coherence preflight (
SpecLint) in both build_product workflows (#301). Runs once, strictly before any decomposition; hard-fails dangling doc/section refs, contradictory constants, contract/signature mismatches, and unassignable mandated tests; warns on uncheckable guarantees and contradicting example code. Findings with evidence land in.ai/decisions/spec-quality.md; fails closed via theauto_statuslast-line-wins STATUS contract. reasoning_effortwired through to Anthropic and Gemini (#329). Anthropic maps tooutput_config.effort; Gemini 3+ maps tothinkingConfig.thinkingLevel(dropped for Gemini 2.5 and earlier, which reject it).- Operator-decision gate + warm
continue +Nfor steady-progress turn breaches (#318). A turn-limit breach that classifies as steady progress routes to a freeform human gate (stop / abandon / commit-advance / continue) with a deterministically safestopdefault for unattended runs. - Graph-level
on_failuredefault failure route (#309, with dippin-lang v0.38). Authors declare one fallback instead of wiring fail edges per node; node-level routes take precedence.
Changed
- dippin-lang pin bumped v0.35.0 → v0.38.0 (consumes graph-level
on_failure, the agent-failure-route lint, and declarable budget/limit attrs). - build_product: no-requirement-left-behind (#300) — Decompose builds a requirement-coverage table (every spec-mandated verification owned or explicitly deferred); Verify enforces owner-or-fail.
- build_product: language-native quality gates when no Makefile exists (#299) — go vet / golangci-lint / tsc / eslint / ruff / mypy / cargo fmt+clippy fallbacks in the shared CI probe.
- build_product: per-node build-context file (#298) cures rediscovery amnesia — agents stop re-burning ~20% of each turn budget re-reading the repo.
- engine: turn-limit breach = guard, not guillotine (#303) — verify → commit-if-green → classify before any routing decision.
- engine: commit-WIP to a recoverable ref before routing a failed/exhausted node (#302); build_product: commit-first / stop-when-green +
CommitIfDirtycheckpoint (#297). TRACKER_GATEWAY_KINDrouting dispatch for bedrock-style gateways (#276–#278) withtracker doctorrouting notes and operator docs.
Fixed
- Unhandled agent failure no longer dead-stops the pipeline (#295) — routes to
fallback_target/ graphon_failure; every build_product agent node routes failure/turn-exhaustion to escalation (#296). - A silently-missing cross-review report can no longer reach
FinalBuild(#313 guard) —CheckReviewsCompletefails the gate when any of the three review reports is missing or empty.
Joint-release closeout for tracker v0.35.0 ↔ dippin v0.35.0. go.mod swaps the joint-release-window pseudo-version (v0.34.1-0.20260601154018-792e6e644e9f) for the published dippin v0.35.0 tag. PinnedDippinVersion in tracker_doctor.go updated in lockstep. No functional changes vs v0.35.0 — the underlying dippin code is byte-identical (792e6e6 is the commit dippin v0.35.0 tagged). Same shape as the v0.31.0 → v0.32.0 closeout.
Changed
- dippin-lang dependency bumped to v0.35.0 tag.
go.modswaps the joint-release-window pseudo-version (v0.34.1-0.20260601154018-792e6e644e9f) for the publishedv0.35.0tag.PinnedDippinVersionintracker_doctor.goupdated in lockstep.
Closes Gap 5.2 of #233
(#271). Runs that accept a failed validation
through a human gate now terminate as a new fourth status — validation_overridden —
instead of being silently bucketed as success. The audit trail, JSON output, TUI completion row,
and exit code can all distinguish operator-accepted overrides from clean successes.
Added
- New terminal
EngineResult.Statusvaluevalidation_overridden. Runs that traverse await.humangate edge markedoverride: truein their.dipworkflow now terminate withStatus == "validation_overridden"instead of"success", recording in the audit trail that a non-workflow decision (a human at the TUI, an autopilot persona, or a webhook callback) accepted a result the automated checks flagged as failed. TheOverride:line intracker audittraces the routing; the--jsonoutput carries a stablestatus_class: succeeded|failedcompanion field for downstream consumers. - New edge attribute
override: truein.dipsyntax, valid only on edges fromwait.humannodes. Adapter rejects misuse at compile-time. - New CLI flag
--fail-on-override(and env varTRACKER_FAIL_ON_OVERRIDE=1, strict-=1parsing matching the existingTRACKER_PASS_*convention). Causestracker runto exit code2(distinguishable from generic-fail exit1) when a run terminates asvalidation_overridden. Default unchanged: exit0. - New
dippin doctorlint rule TRK102 (warn-level): fires onwait.humanedges with labelaccept/mark done/approve(case-insensitive) that route to a forward-progress target, are upstream-reachable from anoutcome = failedge, and lackoverride: true. Surfaces the migration opportunity without false-positive on plan-approval gates. - TUI live override rendering:
MsgPipelineCompletedgains a typedStatusfield; the TUI completion row renders the amber override badge in real-time. No more identical green badges forsuccessandvalidation_overridden.
Changed
- Library-API delta:
EngineResult.Status,tracker.Result.Status,tracker.AuditReport.Status,tracker.RunSummary.Statusare re-typed fromstringtopipeline.TerminalStatus(a named string type). Existing literal-string comparisons (result.Status == "success") continue to compile and produce the same answer. Assignments from astringvariable into aStatusfield require an explicit cast — this is the one breaking change in the re-typing. Embedded library callers should migrate to the newTerminalStatus.IsSuccess()helper for forward-compat across future status additions. - New exported symbols (additive):
pipeline.TerminalStatus(type),pipeline.OutcomeValidationOverridden,pipeline.OverrideDetail,pipeline.Actor(type),pipeline.ActorHuman/ActorAutopilot/ActorWebhook/ActorUnknown,pipeline.Edge.Override,pipeline.Outcome.ChildOverride,pipeline.Outcome.OverrideActor,pipeline.EngineResult.ValidationOverrides,pipeline.Checkpoint.ValidationOverrides,pipeline.EventValidationOverridden,pipeline.PipelineEvent.Override,pipeline.ErrValidationOverridden. Plus mirrored fields ontracker.Result,tracker.AuditReport,tracker.RunSummary,tracker.DiagnoseReport. tracker_audit.AuditReport.Recommendationsis no longer alphabetically sorted — entries appear in priority order (override notes first, then per-override entries chronologically, then retry/budget notes). Downstream tools that sort on receipt are unaffected; tools that displayed in receive-order will see a different order.
Fixed
tracker_audit.classifyStatuspreviously collapsedbudget_exceededevents to"fail", sotracker auditandtracker listreported budget-halted runs as failures. Audit surfaces now surfacebudget_exceededcorrectly. User-visible behavior change: scripts filtering onstatus == "fail"will see budget-halted runs move out of thefailbucket into a newbudget_exceededbucket. Update filters tostatus_class == "failed"(introduced in this release) for a stable bucket that survives future enum extensions.
Operator notes
Two surfaces silently change behavior on upgrade:
- Monitoring dashboards filtering JSON output on
status == "success"will silently undercount completed runs. Override runs surface asstatus == "validation_overridden"— update filters tostatus_class == "succeeded"or to the unionstatus IN ("success", "validation_overridden"). - Scripts counting failed runs via
status == "fail"will see budget-halted runs disappear from the failure bucket. Update tostatus_class == "failed"for a stable bucket.
CI integrations that should NOT auto-deploy on overrides must opt in with tracker run --fail-on-override workflow.dip && deploy.sh. Without the flag, override runs exit 0 — the default treats them as audit-positive deliberate operator decisions.
The workflows/ mirror is gone — built-in workflows are now embedded directly from examples/ via explicit-file //go:embed lines (#256). No functional change for CLI users. The library-API WorkflowInfo.File field now reports paths with the examples/ prefix instead of workflows/ — the only externally visible delta.
Changed
- Collapsed
workflows/mirror intoexamples/via explicit-filego:embed(#267). The repo previously kept four byte-identical copies of the built-in workflow.dipfiles underworkflows/andexamples/, synchronized by amake sync-workflows/make check-workflowstarget, a pre-commit gate, and a CI step — guardrails that still let the two copies drift three times. The//go:embed workflows/*.dipglob is replaced with four explicit//go:embed examples/<name>.diplines pointing directly at the canonical copies. Theworkflows/directory and all of the sync infrastructure (Makefile targets, pre-commit gate #9, CIEmbedded workflows in syncstep) are deleted.WorkflowInfo.File(library API) now reports paths with theexamples/prefix instead ofworkflows/; that's the only externally visible delta. ~3,200 lines deleted net.
The last two visible build_product audit gaps from #233 close. The eight-gap audit thread is now down to a single design-scope follow-up — Gap 5.2 (OutcomeHumanOverride), tracked separately because pipeline.Outcome has wide blast radius across every consumer.
Changed
- Gap 5.1 —
parseAutoStatustolerates markdown-emphasis on STATUS lines (#263). The audit observed**STATUS: fail**(bold) silently falling back to the defaultsuccessbecauseHasPrefix("**STATUS:", "STATUS:")returns false, and LLMs commonly bold/italicize STATUS lines when they want the verdict to draw the eye.parseStatusLinenowstrings.Trims the line and value with the"*_"cutset, so**STATUS: fail**/*STATUS: fail*/STATUS: **fail**/__STATUS: success__all parse correctly. Locked semantics fromTestParseAutoStatus_V3FailFirstContractare unchanged: last-line-wins + default-success-on-empty. New regression coverage inTestParseAutoStatus_Gap5_1_AuditedShapes(11 sub-tests) — six previously RED, now GREEN; five pin existing behavior (uppercase value, whitespace trimming, last-wins under emphasis, code-fence skip). - Gap 5.3 —
build_productre-runs reviewers afterApplyReviewFixeswith a one-shot budget (#264). Pre-fix, the workflow only ran reviewers once and routed straight toFinalBuildafter fixes, so a fix commit introducing W4 (zero-assertion stubs), W5 (wrong-target tests), or W13 (DI bypass) regressions reachedDoneunchecked —FinalSpecCheckper Gap 8 v5 scopes to W17 + iface +SPEC.mdand explicitly delegates W4/W5/W13 to reviewer rubric point 3, which only ran pre-fix. NewCheckReviewFixBudgettool node increments.ai/build/review_fix_attemptsand routesApplyReviewFixes → CheckReviewFixBudget → ReviewParallel restart:true(one re-review pass) or→ EscalateReview(budget exhausted,MAX_ATTEMPTS=1). AResetReviewBudgettool node sits on theEscalateReview "retry" → Decomposeedge so the counter clears when the human picks retry — otherwise the stale counter would immediately fail-close the retry's first re-review pass (caught by Codex / Copilot during PR review). Pattern mirrors the existingTestMilestonefix_attemptsprecedent.
Joint-release closeout for #258 / dippin-lang#41. v0.31.0 shipped with a transient pseudo-version pin pointing at the dippin-lang#41 merge SHA because dippin v0.32.0 didn't exist yet when tracker was tagged. dippin v0.32.0 (whose go.mod pins tracker v0.31.0) is now published, so tracker v0.32.0 swaps the SHA for the tag. No functional changes vs v0.31.0 — the underlying dippin code is byte-identical. Joint-release loop closed: tracker v0.32.0 ↔ dippin v0.32.0 mutually pinned, both tags published.
Changed
- dippin-lang dependency bumped to v0.32.0 tag (#261).
go.modswaps the joint-release-window pseudo-version (v0.31.1-0.20260526211025-53c24f13a4d0) for the publishedv0.32.0tag.PinnedDippinVersionintracker_doctor.goupdated in lockstep. The window-godoc paragraph explaining the transient pin was removed since the swap is now complete.
Two more build_product audit gaps closed plus new tool_access runtime enforcement bounding the v0.28.2 single-agent multi-tool-call vector. Seven of the eight #233 audit gaps now closed (1, 2, 3, 4, 6, 7, 8); only Gap 5 (engine-level auto_status audit + OutcomeHumanOverride + post-ApplyReviewFixes re-check) remains as a follow-up.
Added
tool_access: noneruntime enforcement on agent nodes (#258, joint with dippin-lang#41). Bounds the v0.28.2 single-agent multi-tool-call vector: an LLM emitting[bash(...), write(...), bash(...)]in one response would otherwise dispatch all of them beforemax_turnschecks the cap. Withtool_access: noneset on an agent node, tracker hands the LLM zero tools so the response comes back as plain text. Defense in depth: built-in registry gate + post-WithToolsregistry clear inNewSession+request.ToolChoice = ToolChoiceNone()+executeToolCallsearly-exit + Params-bypass defense (allowed_tools/disallowed_tools/permission_modeignored whentool_accessis set). Backend coverage: native full honor; claude-code best-effort via--disallowedToolsenumeration; ACP refuses session creation with a clear error pointing to #258 (no verified deny-equivalent yet). Fail-closed for typos —noen/None/offall disable tools so a misspelling can't ship full tools.
Changed
build_productGap 7 — interface-method reachability (#254).FinalSpecChecknow enforces that every interface method defined in the codebase has a non-test production caller, with shown-work grep evidence.Setupwrites.ai/build/iface-reachability-rubric.md(language detection + per-language enumeration patterns + caller-discipline rules + stdlib carve-out principle + waiver discipline + known-limitation skips) and the prompt + three reviewers reference it so the discipline lives in one place. The prompt opens with an inverted STATUS contract —STATUS:failas the FIRST line,STATUS:successas the LAST line only on full pass — so a truncated response fails closed under last-line-wins parsing, defending against theparseAutoStatusdefault-success-on-empty shape that was the original Gap 7 bug. Reviewer rubric point 2 strengthened from 3-line prose to 9-line shown-work-grep requirement matching point 1's standard.build_productGap 8 — test-quality smells (#257).FinalSpecCheckadds a sleep-as-fence section restricted to tracked test files viagit grep(so dependency dirs likenode_modules/,vendor/,.venv/,target/are excluded by construction); each hit needs disposition — cite a SPEC.md timing-contract section, OR cite a deterministic primitive that replaced the sleep, OR a.ai/decisions/waiver naming the specific test. Reviewer rubric point 3 strengthened across all three reviewers: Gemini's "stdlib-only tests FAIL" sentence promoted to ReviewClaude and ReviewCodex (covers W5 wrong-target), plus shown-work demands for zero-assertion test bodies (W4) and DI bypass when a Clock/Random/IO seam exists (W13). The legacy STATUS tail atFinalSpecCheckwas rewritten to align with PR #254's inverted contract. W21 (Go subtest case-fold collision) is explicit non-goal — golangci-lint territory.
build_product workflow hardened against five of the eight failure modes the #233 audit uncovered. The audit ran build_product end-to-end on a real Phase 1 spec and found 38 issues the workflow declared "Done" on — wrong API shape, off-by-one retries, red CI, dead interface methods, Phase-6 features built in Phase 1 with green tests pinning the bug. Five gaps closed in this release; three (Gap 5 auto_status engine audit, Gap 7 interface reachability, Gap 8 TestQuality step) remain.
Changed
build_productGap 1 — project CI gate (#246).TestMilestone+FinalBuildnow probe for a projectMakefileand run the first defined target out ofci/check/lint(parsed via sed+awk that handles multi-target rules and rejects variable assignments); a missingmakebinary fails loud instead of silently skipping. The probe lives in a shared.ai/build/ci-probe.shwritten bySetup, sourced by both nodes — round-5 review found the awk had silently drifted between the two pre-shared-file copies; this prevents that class of bug.build_productGaps 3 + 6 — spec-literal anchoring + DO NOT list (#246).Implementcarries explicit "spec literals are contracts" + "tests verify the contract, not your code" + "snapshot tests must be hand-verified" rules.Decomposenow produces a per-milestone "DO NOT implement" list (Phase 2+ scope) so deferred-phase affordances stay inert — green tests on Phase-6 features built in Phase 1 was a top-5 audit finding.build_productGap 2 — cross-reviewer rubric overhaul (#249). The three cross-reviewers (ReviewClaude,ReviewCodex,ReviewGemini) now share a 5-point structured rubric (spec literals grep, interface reachability, test-verifies-contract, scope, architecture); each carries a "do not trust spec ✅ markers" warning;ReviewGeminiis now explicitly adversarial ("faithful and high-quality realization" is a forbidden phrase);SynthesizeReviewsweights findings by evidence not vote count, so a single reviewer with grep / file:line evidence flipsSTATUS:failover two evidence-free PASSes. Synthesis missed ~33 of 38 real audit findings pre-#249.build_productGap 4 —VerifyMilestonereads SPEC.md (#249).VerifyMilestonenow readsSPEC.mddirectly (not just the milestone notes), runs spec-literal greps inline (with a.ai/decisions/documented-deviation carve-out matching Implement's contract), applies the test-asserts-contract check, and confirms tests + CI passed via thetests-passsentinel rather than scanning for visible test output (which the 64KB stdout tail-cap routinely elides).- dippin-lang dependency bumped v0.27.0 → v0.29.0 across two passthrough PRs (#248, #250).
ir.ToolConfiggainsMarkerGrep/RouteRequired/OutputLimit/Outputsadapter forwarding; dippin-side polish lands lint suppression onmarker_greptools (DIP138),parseBoolAttrnormalization (yes/1/TRUEnow parse correctly ongoal_gate/auto_status/cache_tools/route_required), andOutputsDOT round-trip parity.
Maintenance release — picks up dippin-lang v0.27.0 (2026-05-18 model/pricing catalog refresh). Combined with v0.29.1's lint deduplication, DIP108 now covers the full current catalog so workflows using gemini-3-flash-preview, redirected grok-4-1-fast-* IDs, and other recent models validate clean. No tracker-side feature changes; no breaking changes.
Changed
- dippin-lang dependency bumped v0.26.0 → v0.27.0 (#242). Picks up the 2026-05-18 model / pricing catalog refresh and a bug fix where redirected
grok-4-1-fast-*IDs stay callable when their target gets renamed. No IR or adapter changes — drop-in.PinnedDippinVersionintracker_doctor.goupdated in lockstep sotracker doctor's dippin-version mismatch check uses the new pin.
Defers all DIP-coded lint to dippin-lang. Tracker had been maintaining a parallel implementation of DIP101–DIP112 + DIP120–DIP121 in pipeline/lint_dippin*.go while already calling validator.Lint() from dippin on every .dip / .dipx load — and the duplicates had drifted, producing false-positive warnings on current model names like gemini-3-flash-preview even though dippin doctor accepted the same file cleanly.
Changed
- Single source of truth for DIP-coded lint (#239, #240). Deleted
pipeline/lint_dippin.go+lint_dippin_extra.go(660+ lines covering DIP101–DIP112 + tracker's local DIP120 / DIP121, which semantically collided with dippin-lang's own DIP120 / DIP121 — different checks under the same codes). Tracker now has zero DIP-coded lint implementations;dippin-lang/validator.Lint()is the sole authority and a model catalog edit happens in one place. Lint warnings still surface throughtracker validate/simulate/doctorvia the newGraph.LintWarningsfield, populated byLoadDippinWorkflowFromIRfrom dippin's lint output and appended toValidateAll's warnings channel. validateNodeAttributesgated behind!g.DippinValidated. The typed-attribute checks (max_retries,cache_tool_results,context_compaction,context_compaction_threshold) were re-validating fields that dippin's typed IR already enforces at parse time and DIP116 lints for range. Worse, tracker errored whencompaction_threshold = 0.0while dippin only warned — exactly the kind of "dippin doctor and tracker validate disagree" divergence the architecture is supposed to prevent. The gate matches the existing pattern for DIP001–DIP009 structural checks:.dipsources defer fully to dippin; DOT graphs and programmatically-constructed graphs still get the runtime sanity checks.- TRK1XX rules retained.
LintTrackerRules(currently TRK101, the tool_stdout tail-window routing pitfall from #208 / #210 / #211) stays — those encode tracker-runtime concerns that don't belong upstream.
Workflows can now declare environmental dependencies in the header (requires: <list>) and tracker preflights them before any node runs — failing in seconds with a copy-paste remediation instead of burning $20–$100 of LLM spend before the first git operation. Requires dippin-lang v0.26.0.
Added
requires:workflow header keyword (#234). Comma-separated dependency list in the.dipworkflow header. v0.29.0 implementsgit: when declared, tracker verifies git is installed AND the workdir is inside a real work tree AND HEAD points at a real commit before any node executes. Unrecognized entries (docker,gh,jq, etc.) parse fine and emit a "not yet implemented" warning so workflow authors can forward-declare deps that future tracker versions will check. The mechanism lives at the library + CLI boundary, not inside the engine —pipeline.Preflightis invoked once at run start; subgraph andmanager_loopchildren inherit the parent's check.--git=auto|off|warn|require|initCLI flag overrides policy per run. Defaultautorespects the workflow'srequires:.offbypasses,warndowngrades hard-fails to warnings,requireforces the check even if not declared,init(with mandatory--allow-initlatch in non-interactive runs, or a[Y/n]prompt in interactive runs) auto-runsgit init+ an empty initial commit so HEAD is born and worktree workflows work immediately. Safety refusals fire for$HOME,/, and nested git contexts — bare repos, linked worktrees (.gitis a file), submodules (same);$HOMEand root comparisons fold case on Windows. Auto-init refuses in non-empty workdirs rather than silentlygit add -A-ing content that might be secrets / build artifacts. All git probes run underLANG=C LC_ALL=Cso the "not a git repository" stderr classifier is stable on localized installs.- Born-HEAD check via
git rev-parse --verify HEAD^{commit}.--is-inside-work-treereturns true for agit init'd repo with no commits, so without this probe arequires: gitworkflow could pass preflight and crash mid-run ongit worktree add ... HEADafter burning LLM turns.^{commit}forces commit peeling so a dangling/non-commit OID at HEAD doesn't masquerade as born. Stderr is inspected via the two upstream-stable phrases (Needed a single revision,unknown revision or path not in the working tree) so corruption-class errors surface as wrapped errors instead of collapsing to "unborn." tracker doctorGit Requires check previews the runtime decision:OK(env satisfiesrequires: git, or--git=init --allow-initwould succeed in this clean dir),Warn(downgrade under--git=warn, or auto-init OK + unknown deps surfaced),Error(hard-fail),Skip(--git=off). TheHintfield carries the exact remediation command including dual paths for non-repo / unborn-HEAD cases (capture existing files vs empty baseline).- Library API surface.
tracker.Config.Git *GitConfigfor embedded callers (zero value resolves toGitPreflightAuto).GitPreflightconstants re-exported from thetrackerpackage.tracker.WithGitConfig(policy, allowInit)functional option fortracker.Doctor.pipeline.SafetyLatches,pipeline.HasBornHEAD,pipeline.WorkdirHasContentexported so Doctor preview models the same latches the runtime preflight enforces.tracker.NewEngineWithContext(ctx, source, cfg)is the ctx-aware constructor so cancellation reaches every git subprocess including the--git=initside effect. - Built-in workflows that commit / branch / merge mid-run declare
requires: git—ask_and_execute,build_product,build_product_with_superspec. Running them in a non-git directory now fails in seconds with copy-paste remediation instead of burning LLM spend before the first git operation.tracker workflowsshows a new REQUIRES column.
Changed
- dippin-lang dependency bumped v0.25.0 → v0.26.0. Picks up
ir.Workflow.Requires []stringand the parser / formatter support for therequires:workflow header keyword.
Patch release fixing a runaway-agent bug in three of the four built-in workflows. No engine changes; no breaking changes.
Fixed
- Built-in workflows no longer run an unconstrained agent in
Start/Done(#230).workflows/ask_and_execute.dip,workflows/build_product.dip, andworkflows/build_product_with_superspec.dip(and theirexamples/mirrors) defined Start/Done asagentnodes withprompt: Initialize pipeline./prompt: Pipeline complete.. Because the prompt attribute was present,ensureStartExitNodesskipped the passthrough handler and these nodes became real codergen sessions — system message limited to the file-path reminder, full default tool access (read/write/bash/glob/edit/grep_search), no per-node turn cap. A realbuild_productrun was observed spending ~10 minutes and ~39k output tokens insideStart, implementing an entire separate Go project from a SPEC.md found on disk, before gettingcontext canceledand being classifiedoutcome: retry. Dropping the prompt lines makes Start/Done passthroughs (matchingdeep_review.dip, which was already correct). The broader engine policy gaps surfaced by this incident —outcome: retryon cancellation, no defaultmax_turnscap, runaway nodes invisible totracker diagnose, missing tool-call args inactivity.jsonl, suspect per-node token accounting — are tracked in #230 for separate follow-up.
Maintenance release — picks up dippin-lang v0.25.0 (.dipx format v1.1). No tracker-side feature changes; no breaking changes.
Changed
- dippin-lang dependency bumped v0.24.0 → v0.25.0. Three bundle-load improvements arrive automatically:
dipx.Opencycle detection now walks every manifest-listed workflow (was: rooted only atm.Entry, could miss cycles in entry-unreachable workflows thatparseAllWorkflowshad already loaded);dipx.OpenenrichesErrManifestInvalid/ErrUnsupportedFormatVersionerrors with the bundle path;dipx.Packcorrectly classifies subgraph parse failures asErrSubgraphParseinstead ofErrEntryParse. Plus context-cancellation throughOpen/Packhot paths (per-entryctx.Err()checks). TheSource.Workflowsignature change in dippin-lang v0.25.0 doesn't affect tracker — we useBundle.Entry()/Bundle.Lookup()directly.
The full #208 follow-up arc lands: typed routing channels, validate-time lint, and integrity-protected audit log.
Added
marker_grep:typed routing channel on tool nodes (closes #210). Declarative regex on a tool node populatesctx.tool_markerfrom the last match's capture group — routing readswhen ctx.tool_marker = passinstead of greppingctx.tool_stdout. Missing match fails the node withOutcomeFailand emitsEventToolMarkerMissingrather than silently falling through.ctx.tool_markeris LLM-origin so it's blocked fromtool_commandinterpolation. Compatible with the v0.27.0 tail-window: regex runs over the captured tail, so end-of-output markers survive by construction._TRACKER_ROUTE=<value>reserved routing sentinel (closes #212). Complement tomarker_grepfor tools that can't change schema. Runtime scans every tool node's captured stdout for^\s*_TRACKER_ROUTE=(.+?)\s*$, takes the last match, populatesctx.tool_route. Author pattern:printf '_TRACKER_ROUTE=tests-pass\n'once the routing decision is known, thenwhen ctx.tool_route = tests-pass. Optionalroute_required: trueopts in to strict mode — node fails withOutcomeFail+EventToolRouteMissingif no sentinel was emitted.TRK101validate-time lint for risky tool-stdout routing (closes #211). NewTRK1XXnamespace (distinct from dippin'sDIP1XX). Fires attracker validate/tracker doctortime on tool nodes that route onctx.tool_stdoutvia one conditional + unconditional fallback + nomarker_grep+ nooutput_limit+ volume-emitting body (teeor2>&1). Suggestsmarker_grepas the primary remediation. Heuristics tuned for low false-positive rate acrossexamples/*.dip.- Activity log integrity hardening (closes #213). Pre-fix, the audit log at
<workDir>/.tracker/runs/<runID>/activity.jsonlmode0o644was reachable via relative path from any tool subprocess withcmd.Dir = workDir— an LLM-controlled subprocess could inject fakedecision_edgelines, truncate to suppresstool_output_truncated, or forgepipeline_completed. (A) Live writes relocated to$XDG_STATE_HOME/tracker/runs/<runID>/activity.jsonl(default$HOME/.local/state/tracker/, mode0o600,TRACKER_AUDIT_DIRoverride,%LOCALAPPDATA%fallback on Windows). (B) Every runtime-written line prefixed with\x1f\x1e(pipeline.ActivityLogSentinel);tracker diagnosevalidates and surfaces non-sentinel lines asSuggestionAuditLogInjection. OnClose()a sentinel-stripped snapshot is written to the legacy run-dir path (mode0o644,O_NOFOLLOWon unix) so bundle export and git_artifacts keep working. Detection-only, not authentication — by design; per-line HMAC was explicitly out of scope. - Property-based tests for
tailBuffer(closes #214). New dev deppgregory.net/rapidv1.3.0. Generalizes the v0.27.0 hand-rolled boundary tests to the full state space: for any sequence of writes with totalNbytes and limitL,tb.String()equals the lastmin(N, L)bytes. 100 random cases per property, < 2ms per property.
Tail-window tool output capture + diagnostic events for routing-marker truncation.
Fixed
- Tool stdout/stderr truncation now keeps the tail, not the head (closes #208). Pre-fix, the per-stream 64KB cap in
agent/exec/local.gokept the first 64KB of output, silently dropping routing markers (printf 'tests-pass') past the boundary — pipeline routing then fell through the unconditional fallback edge and could ship broken code as if it had passed. Thenotebook_smokepipeline reproduced this twice in one day. NewtailBufferring-buffer keeps the trailinglimitbytes (O(1) amortized per-byte, singlelimit-sized allocation, exact tail match regardless of write boundaries).CommandResultgains structuredStdoutTruncated/StdoutBytesDropped/StderrTruncated/StderrBytesDroppedfields; the in-band"...(output truncated at N bytes)"suffix is gone — consumers must read the new flags (or the newEventToolOutputTruncatedevent below) to detect truncation. Symmetric for stderr, closing a pre-existing zero-stderr-truncation-tests coverage gap.
Added
EventToolOutputTruncatedactivity event (Tier 1). Emitted once per truncated stream after each tool node, withTruncationDetail{Stream, Limit, CapturedBytes, DroppedBytes, TotalBytes}. Written toactivity.jsonlsotracker diagnose,tracker.Audit, and NDJSON consumers can detect truncation retrospectively.tracker diagnosesurfaces aSuggestionToolOutputTruncatedsuggestion explaining the elision and pointing atoutput_limitas the escape hatch.EventConditionalFallthroughactivity event (Tier 2). Fires when at least one conditional outgoing edge from a node was evaluated, all evaluated false, and routing fell through to a fallback (label,suggested, orweight). Carries the list ofConditionEval{EdgeTo, Condition}entries that missed. Does NOT fire on intentional all-unconditional routing — distinguishes "stated routing intent missed" from "fallback is the only option".tracker diagnosecorrelates this withEventToolOutputTruncatedon the same node and surfaces a combined suggestion ("your routing marker may have been dropped") — the canonical diagnostic narrative for the #208 failure shape.- Five hardening follow-ups filed from the 6-expert design panel that reviewed the #208 fixes: #210
marker_grepprimitive, #211 validate-time lint for risky stdout-routing patterns, #212_TRACKER_ROUTEreserved sentinel, #213activity.jsonlintegrity, #214 property tests viapgregory.net/rapid.
Native .dipx bundle support across the entire CLI.
Added
- Native
.dipxbundle support (#209): tracker now accepts content-addressed.dipxbundles (produced bydippin pack) anywhere it accepts a pipeline file —tracker validate,tracker simulate,tracker run,tracker doctor, andtracker -r <runID>resume. Pre-fix, tracker read the bundle's ZIP bytes as.dipsource and failed with bogusDIP001/DIP002validation errors. Newpipeline.LoadDipxBundleopens the bundle viadipx.Open(SHA-256 verifies every file inmanifest.jsonbefore any content reaches the parser), uses the bundle's pre-parsed*ir.Workflowdirectly, and bypasses the filesystem subgraph walker entirely since dipx already verifies ref closure + acyclicity onOpen. The bundle's content-addressed identity (sha256:<hex>) is stamped on every line ofactivity.jsonl(engine emissions, parallel/manager_loop emissions that bypass the engine's emit chokepoint, and agent/llm JSONL writes that bypass both — three composable layers), persisted intocheckpoint.jsonfor resume verification, and surfaced intracker list(newBundlecolumn) andtracker audit(newBundle:header line). Bundle identity is exposed ontracker.Result.BundleIdentityandtracker.RunSummary.BundleIdentityfor embedded library callers. Resume against a.dipxstrictly verifies the stored identity matches the one being resumed — mismatch aborts with both hashes shown;--force-bundle-mismatchis the escape hatch (loud warning to stderr).
Changed
- dippin-lang dependency bumped v0.23.0 → v0.24.0 for the new
dipxpackage (Open,Bundle.Workflow,Bundle.Identity).PinnedDippinVersionintracker_doctor.goupdated to match sotracker doctor's version-mismatch check reflects the new pin. pipeline.LoadDipxBundlereturns diagnostics instead of writing toos.Stderr: the library API no longer prints to the process-global stderr; the signature gains a[]validator.Diagnosticreturn so embedded callers can route them through their own logger. CLI callers (cmd/tracker/loadDipxPipeline,tracker doctor's bundle check) print to stderr as before. Mirrors the existingpipeline.LoadDippinWorkflowcontract for the.dippath.
Bedrock Gateway integration polish + Gemini token-usage fix.
Changed
- Bedrock Gateway integration guide refreshed for upstream gateway fixes (#4 Gemini
/v1betapaths and #5 Cloudflare AI Gateway native routing prefixes/anthropic/openai/google-ai-studio/compat, both closed 2026-04-30). Tracker's--gateway-urlflag now works end-to-end againsthttps://bedrock-gateway.2389-research-inc.workers.devandprovider: geminiis no longer broken.docs/bedrock-gateway.mdrewritten to lead with the recommended--gateway-urlrecipe; compatibility matrix flips Gemini to working; obsolete workarounds dropped.provider: openai(Responses API) row stays as broken pending gateway #3, which was reopened after discovering it had been auto-closed by an unrelated commit's "Fix #3" wording. - Gemini SSE parser coalesces split finish + usage chunks into a single
EventFinish: when an upstream emits the finish reason andusageMetadatain two separate chunks (the bedrock gateway does this; real Google can too), the parser now buffers the finish reason ingeminiStreamState.pendingFinishand emits one combined event when the usage chunk arrives. AflushPendingFinishhelper guarantees the buffered reason is emitted before every early-return path — clean stream exit, scanner error, JSON parse error — so partial-failure streams still produce a terminalEventFinishahead of anyEventError, preserving accumulator bookkeeping. Net effect: thellm finishtrace line prints exactly once per turn regardless of upstream chunking shape.
Fixed
- Gemini token usage no longer reports 0 when the upstream emits
usageMetadataas a standalone trailing SSE chunk: tracker'sllm/google/adapter.goSSE parser used to bail on any chunk with nocandidatesarray, which dropped trailing usage-only chunks on the floor. Surfaced while smoke-testing tracker against the bedrock gateway where the streaming reply is three chunks: text →finishReason:"STOP"→usageMetadata. End-to-end verified:provider: geminismoke run now reports1,408 in / 4 outinstead of0 in / 0 out, and per-provider cost rollup is correct (no double-counting becauseAggregateUsagefolds per-nodeSessionStats, not perTraceEvent).
Architect-side machinery for local codegen + self-healing declared writes.
Added
- Architect-side machinery for local codegen: new agent-runtime primitive
TerminalToolends the session loop the moment a tool flagged terminal succeeds — no wasted post-dispatch turns. Three new tools land via env-gated registration (TRACKER_SPRINT_WRITER_MODEL/TRACKER_CODEGEN_MODEL):dispatch_sprintsreads a{path, description}JSONL plan and runs the per-sprint author+audit pipeline once per line via a deterministic in-tool loop with bounded retry+backoff for transient provider errors (5xx, rate-limit, timeout, network);write_enriched_sprintcalls a mid-tier LLM once per sprint with a 4-strategy SEARCH/REPLACE matcher (exact → indent-preserving → whitespace-insensitive → fuzzy with Levenshtein ratio ≥ 0.9), partial-apply semantics distinguishingPATCHED-PARTIALfrom cleanPATCHED, and a tolerant audit-verdict parser;generate_codeexpands a contract into one or more files via a cheap/fast model. Validated end-to-end on Notebook synthetic (41/41 pytest passing, ~$2, 28min) and NIFB architect-only (16 sprints, ~$5, 47min). - Self-healing JSON extraction cascade for declared writes: when an LLM responds with prose instead of valid JSON for a node with
writes:, the runner now attempts (1) direct JSON parse; (2) extraction of any```...```fenced block via a strict-shape regex (so atext/bashpreamble and stray inline backticks don't fool it); (3) balanced-brace scan for the first top-level{…}span that parses as an object (handles prose with stray brace pairs around real JSON;{inside JSON-string values and inside[…]arrays correctly skipped); (4) single-key fallback to the raw response withwrites_warning, capped at 8 KiB. Multi-key writes still hard-fail. Fallback is gated on "no extractable JSON found" — a model that returned valid JSON missing the declared key gets a hard contract failure with a specific error. - Bedrock Gateway integration guide: new
docs/bedrock-gateway.mdwalks through pointing tracker at the 2389 Cloudflare Worker — per-provider*_BASE_URLrecipes, provider compatibility matrix, authentication via Cloudflare AI Gateway tokens, and verification guidance.
Changed
writes:declarations are rejected when they collide with reserved key names: two reserved sets — (a) thetool_commandsafe-key allowlist (outcome,preferred_label,human_response,interview_answers); allowingwrites: outcomewould funnel LLM-controlled content into a reserved name and bypass the sanitization that keeps LLM output out of shell input. (b) the writes-signal keys (writes_error,writes_warning); allowing them via writes would let an LLM spoof a failure/healed signal thattracker diagnoseandwhen ctx.writes_error != ""edges branch on. Collision rejection runs before any value is written. No existing pipelines used these collisions.
Fixed
tracker doctorprovider probe restored to 16-token max output: the probe had been usingmaxTok:= 1, but OpenAI's Responses API requiresmax_output_tokens ≥ 16and returns HTTP 400 (Invalid 'max_output_tokens': integer below minimum) below that — breakingtracker doctorfor OpenAI keys entirely.
Security and correctness fixes from a 13-expert panel audit of v0.24.1.
Fixed
- ACP
CreateTerminalnow validates commands against the built-in denylist and constrainscwdto the working directory: previously an LLM-directed ACP agent could execute arbitrary commands, completely bypassing the denylist that protectstool_command. Bare denylisted commands (e.g.evalwith no args) are also blocked. - Claude Code backend kills subprocess process group on pipeline cancellation: added
SysProcAttr.Setpgid+cmd.Cancel(SIGKILL to process group) to prevent orphanedclaudesubprocesses consuming API credits after ctrl-C or budget breach. TRACKER_PASS_API_KEYSnow requires=1instead of any non-empty value: previouslyTRACKER_PASS_API_KEYS=falseor=0silently leaked all API keys to the claude subprocess.tracker doctorenv warning updated to match.- Engine fails on unknown outcome status instead of treating as success: the
default:case inhandleOutcomeStatuspreviously calledMarkCompleted, silently promoting handler bugs to success. Now emitsEventStageFailedand setsOutcomeFail. - Pipeline goroutine panic recovery:
runPipelineAsyncnow hasdefer/recoverso a handler panic produces a clean error instead of crashing the TUI. PinnedDippinVersionupdated tov0.23.0to matchgo.mod.DefaultModelupdated toclaude-sonnet-4-6.- Autopilot LLM calls now respect pipeline context cancellation: all call sites used
context.Background()— pipeline cancellation had no effect on in-flight autopilot requests. - Example
manager_loop_child.dipupdated forsteer.*namespace. escapeOsascriptnow escapes newlines to prevent injection in macOS notification strings.
Changed
stack.manager_loopsteer_contextkeys namespaced understeer.*: prevents collision with safe-allowlisted bare ctx keys used intool_commandvariable expansion. Behavior change: pipelines reading steer values via${ctx.hint}must update to${ctx.steer.hint}.
Fixed
- claude-code backend now parses cache-token usage from the NDJSON result envelope: the Claude CLI already emits
cache_read_input_tokensandcache_creation_input_tokensin itsresultmessage, butstoreResultwas silently dropping both. For heavy-cache Sonnet workloads (typically 60–90% cache-read), that meant ~3× input-cost overcount becausellm.EstimateCostpriced every input token at the fresh rate. Fix populates*intpointers onllm.UsagesoEstimateCostprices cache reads at 10% and cache writes at 25% of the input rate (Anthropic pricing convention).TotalTokensstays fresh-input + output per the cross-provider convention soBudgetGuard's--max-tokenssemantics stay uniform.
Added
TRACKER_ACP_CACHE_READ_RATIOenv var for ACP cost-estimate tuning: ACP protocol doesn't expose cache tokens, so the estimator defaulted to pricing all estimated input as fresh. Conservative but up to ~3× high for bridges that keep a stable prompt cache. SettingTRACKER_ACP_CACHE_READ_RATIOto a value in(0, 1]routes that fraction of estimated input toUsage.CacheReadTokens. Typical values for stable-context Claude workloads: 0.5–0.8. NaN/Inf/out-of-range ignored with a one-time warning.--tool-denylist-add <glob>CLI flag +tool_denylist_addgraph attribute: operators and workflow authors can extend the built-in tool-command denylist with additional glob patterns for defense in depth. User patterns are strictly additive — cannot remove a built-in;--bypass-denyliststill disables everything (built-in + user-added); allowlist and user-denylist are independent gates (both must pass). Completes the deferredWorkflowDefaults.ToolDenylistAddadapter wiring from v0.24.0's dippin v0.23.0 bump.- Estimated-usage flag plumbed from ACP backend through trace → CLI → TUI → NDJSON:
ACPUsageMarkeronllm.Usage.Rawis now preserved by a newextractEstimateMarkerhelper that populatesSessionStats.Estimated+EstimateSourcebeforeUsage.Rawgets dropped.Trace.AggregateUsageOR-propagates the flag across sessions and child rollups, so any estimated session taints the per-provider bucket and summary. CLI "Tokens by Provider" table suffixes estimated providers with(estimated), total-cost line renders as~$X.XXXX (estimated), TUI header cost badge prefixes with~, and NDJSONcost_updated/budget_exceededevents carryCostSnapshot.Estimated. Mixed native+ACP runs no longer misleadingly present heuristic spend as metered.
Fixed
subgraphnodes no longer bypass--max-tokens/--max-costbudgets: pre-fix, a pipeline author could place cost-intensive nodes inside a subgraph and both token and cost ceilings became silently non-binding. Now child engines inherit the parent'sBudgetGuard+ baseline-usage snapshot via a newChildRunContextFromContext(ctx)channel;OutcomeandTraceEntrygain aChildUsagefield so child spend rolls up throughTrace.AggregateUsageinto parentProviderTotalsand fires the parent's guard at the next between-node check. A child-sideOutcomeBudgetExceededpropagates asOutcomeSuccess+ChildUsageso the parent's own guard reports the correct halt status instead of tripping the strict-failure-edges rule. Four regression tests cover rollup, delayed parent-halt, mid-subgraph child-guard halt, and two-level nesting.stack.manager_loopnodes no longer bypass budgets: same shape of bug as the subgraph fix.ManagerLoopHandler.Executenow readsChildRunContextFromContextand threadsBudgetGuard+ baseline usage into the child engine, andhandleChildResultsetsOutcome.ChildUsageon every return path. Especially load-bearing given manager_loop is the canonical place where tokens pile up — specifically designed for cycle-heavy async supervision (Attractor spec 4.11). Three regression tests mirror the subgraph suite.SessionResult.Providernow populated forclaude-codeandacpbackends: previously left empty, soTrace.AggregateUsagebucketed their usage under the"unknown"provider. Dashboards and library consumers readingEngineResult.Usage.ProviderTotalsnow see populated buckets.trackExternalBackendUsagethreadscfg.ModelintoTokenTracker.AddUsage: previously the model arg was omitted, soTokenTracker.CostByProvider's resolver fell back to (often empty)graph.Attrs["llm_model"]and priced at$0. Librarytracker.Result.Cost.ByProvider["claude-code"|"acp"]and--max-costenforcement were silently disabled for those backends.llm.EstimateCostlogs a one-time warning per unknown model instead of returning$0silently: violates the project's "never silently swallow errors" rule and hides the real consequence that--max-costceilings can't apply to usage priced under a model that isn't in the catalog.steer_contextkeys with:rejected at adapter time: dippin-lang's block-form formatter writes entries askey: value, so a colon in a key breaks.dip → IR →.dipround-trip.flattenSteerContextnow returnsErrInvalidSteerContextKeyinstead of letting the parser drop the key with a diagnostic.manager_loopnodes with nilir.ManagerLoopConfigfail at graph-build time:convertNodepreviously let a nil Config flow through as a no-op, producing a graph node withoutsubgraph_refthat failed vaguely at Execute-time. ReturnsErrMissingManagerLoopCfginstead.
Added
- ACP backend surfaces approximate per-prompt token usage: the Agent Client Protocol spec has no native usage surface, so
estimateACPUsagesynthesizesllm.Usagefrom rune counts across six billable channels —cfg.Prompt,cfg.SystemPrompt, assistant text, reasoning chunks, tool-call argument payloads, and tool-call result payloads (fullContent+RawOutput, not display summaries). UTF-8 aware viaunicode/utf8,ceil(runes/4)applied per side.Usage.ReasoningTokenspopulated from reasoning runes for future catalog-level per-reasoning pricing.ACPUsageMarker{Estimated: true,...}onUsage.Rawfor consumers readingSessionResult.Usagedirectly; a one-time log perACPBackendinstance announces that ACP numbers are estimates.--max-tokensand--max-costnow enforce against ACP sessions. Remaining intrinsic undercount: the bridge's own injected system prompt + tool schemas are invisible to the heuristic (requires a bridge-specificMetaextension we don't have). - Example manager_loop pipelines:
examples/manager_loop_demo.dip+examples/subgraphs/manager_loop_child.dipexercisesubgraph_ref+ poll interval + steering against a real child. Both grade A viadippin doctor; the Makefiledoctortarget runs them in CI. - Diagnostic warnings for manager_loop authoring mistakes: warn when both unprefixed and legacy
manager.*attrs are set on the same node (accidental shadowing); warn onstack.child.<word>references instop_condition/steer_conditionthat aren't among the three keys tracker actually publishes (status,cycles,exit_status).
Changed
- dippin-lang dependency bumped v0.22.0 → v0.23.0: upstream ships DIP28 tool-safety defaults.
extractWorkflowDefaultswiresWorkflowDefaults.ToolCommandsAllow→graph.Attrs["tool_commands_allow"]; theToolDenylistAddfield is deferred until the matching--tool-denylist-addCLI flag lands. - Docs relocated under
docs/architecture/:docs/pipeline-context-flow.md→docs/architecture/context-flow.md;docs/manager-loop.md→docs/architecture/handlers/manager-loop.md. All inbound links updated.
Added
ir.NodeManagerLoopadapter support + dippin-lang v0.22.0 bump:.dipauthors can now declarestack.manager_loopsupervisors directly via the new IR kind instead of reaching for DOT. The adapter mapsir.NodeManagerLoop→shape=house→handler=stack.manager_loopand flattensir.ManagerLoopConfiginto six unprefixed attrs:subgraph_ref,poll_interval,max_cycles,stop_condition,steer_condition,steer_context.steer_contextuses canonical sortedk=v,k=vpairs with percent-encoding for,/=/%, matching dippin-lang'sexport.flattenSteerContextfor lossless round-trips. When a manager_loop is the workflow's Start or Exit, shape is overridden toMdiamond/Msquarebut the handler and flat attrs are preserved so the supervisor still executes.ManagerLoopHandleraccepts both v0.22.0 unprefixed contract names and legacymanager.*prefixed variants: backward-compatible; unprefixed wins when both are set.parseSteerContextpercent-decodes reserved chars so round-trips complete through the handler. Partial steering configs (steer_conditionwithoutsteer_context, or vice versa) are now rejected at parse time instead of silently rendering the supervisor inert.- Three new tool-safety CLI flags:
--bypass-denylist(bool, defaultfalse) disables the built-in denylist with a loud stderr warning on startup — use only in sandboxed environments.--tool-allowlist <pattern>is repeatable and accepts comma-separated glob patterns; every tool command must match at least one allowlist entry when the flag is set.--max-output-limit <bytes>sets the hard ceiling (default 10 MB) applied to per-nodeoutput_limit:attrs. Allowlists union with thetool_commands_allowgraph attr; the denylist always wins.
Fixed
formatManagerLoopConditionExpremits Go-style&&/||instead of Englishand/or. Parsed-only IR conditions (Raw empty) now evaluate correctly throughpipeline.EvaluateCondition.CondNotcontinues to emitnot, matching the evaluator's native negation.managerAttruses comma-ok lookup so an explicit empty string on the unprefixed key wins over a non-empty legacymanager.*value. Previously the zero-value check let authors accidentally resurrect values they thought they had cleared.parseManagerLoopConfigdistinguishes empty vs. malformedsteer_context: error now reports "steer_context %q is invalid" with the raw value if it was non-empty, and "steer_context is empty — nothing to inject" only when truly unset.tool_commands_allowgraph attribute wired so the allowlist is enforceable from a pipeline file as well as the CLI.
Added
tracker-swebench analyze <results-dir>: Bulk-triage tool for completed SWE-bench runs. Readspredictions.jsonl, run logs, and empty-patch diagnostics, then emits resolved/unresolved/empty/error counts, per-repo breakdown, top-10 empty-patch and longest-unresolved instances, and error class distribution. Auto-detects SWE-bench evaluator reports;--jsonemits the structuredAnalyzeReport.- Typed
NodeConfigaccessors on*pipeline.Node:AgentConfig(),ToolConfig(),HumanConfig(),ParallelConfig(), andRetryConfig()return typed structs parsed fromNode.Attrs. Graph-default-then-node-override merge is centralized; three-state booleans expose companion*Setflags so callers can distinguish "explicitly configured" from "absent".
Changed
- Codergen handler now consumes
AgentNodeConfiginstead of eight separateapply*methods re-parsingNode.Attrs. No behavior change. - Human, tool, and parallel handlers consume typed configs via
HumanConfig(),ToolConfig(), andParallelConfig()accessors. DirectAttrsreads reduced substantially across handlers. - Tool node
timeouterrors on zero or negative durations: previously fell through tocontext.WithTimeoutcausing immediate cancellation with a confusing error. Validated at handler entry with a clearnon-positive timeoutmessage.
Added
- Declarative
writes:/reads:unified structured output: Agent, human, and tool nodes can declare the keys they produce and consume. Declared writes are extracted from handler output into the pipeline context and validated; missing required fields fail the node.reads:pins fidelity for the keys a node consumes. Replaces node-type-specific workarounds for threading typed outputs between nodes. tracker.SimulateGraph(ctx, graph): Graph-in variant ofSimulatefor callers that already parsed the pipeline.Simulate()is now a thin wrapper.- Repository localization pre-processing: Optional pre-scan of the working directory for files relevant to the task prompt, injected as a structured context block before the first LLM turn. Pure text analysis, zero LLM calls. Reduces wasted turns on
glob/grepfor repository-level tasks. - Agent episodic memory across retries/resumes: Native codergen sessions record a structured per-tool episode log and inject prior summaries into subsequent retry/resume attempts so the model can avoid repeating failed approaches.
- Plan-before-execute phase: Optional single planning LLM call before the main execution loop. Opt-in via
SessionConfig.PlanBeforeExecuteor theplan_before_executenode attr. - Library API godoc, stability policy, and runnable examples: Pre-1.0 stability callout in
doc.go; runnableExampleDiagnose/ExampleAudit/ExampleDoctor.
Changed
tracker simulateoutput is now deterministic: Graph attrs sorted alphabetically; orphan nodes appended in sorted order. No more random-map-iteration diffs between runs.
Fixed
tracker simulatenow parses the pipeline source exactly once: Previously double-parsed. No more duplicated dippin-lang lint warnings.- Cost accounting consistent across runtime and CLI summaries: CLI uses
EngineResult.Usage(trace aggregate) instead ofTokenTracker; repair turns applyEstimateCostcompensation; OpenAIresponse.completedpreserves reasoning tokens; Gemini adapter falls back to requested model whenmodelVersionmissing.
Added
stack.manager_loophandler: A supervisor node that launches a child pipeline in a goroutine, polls at a configurable interval, and optionally steers the running child by injecting context mid-execution. Config viasubgraph_ref,manager.poll_interval,manager.max_cycles,manager.stop_condition,manager.steer_condition,manager.steer_context. Emitsstage_started,manager_cycle_tick, and terminal stage events. Seedocs/manager-loop.md.- Engine steering channel: New
pipeline.WithSteeringChanengine option. Between node executions, the engine drains the channel and merges updates into the run'sPipelineContext. Used bymanager_loopand available to any supervisor. PipelineContext.MergeWithoutDirty: Writes updates without marking keys as dirty so externally-injected values never leak into a node's per-node scope.- Accurate cost estimation via catalog + cache token pricing:
EstimateCostresolves prices through the model catalog instead of a duplicate hardcoded map. Cache reads at 10% of input rate, cache writes at 25%.TokenTrackerrecords observed model per provider. - Model catalog April 2026 refresh: Adds
claude-opus-4-7,gpt-5.4-mini/gpt-5.4-nano, thegpt-4.1family,o3,o4-mini, GA Gemini 2.5 models, andgemini-3.1-pro-preview. Fixesclaude-opus-4-6pricing and bumps Sonnet/Opus 4.6 context windows to 1M.
Fixed
- ACP path validation rejects
..segments before symlink resolution: Security hardening against a symlink +..sandbox-escape vector. Splits on both/and\so Windows paths are protected. - Manager loop hardening: Poll-timer vs. child-completion race, crash path always returns non-nil error, config validation hard-fails on malformed values,
EvaluateConditionerrors surface for both stop and steer conditions,EventStageFailedemitted on context cancellation and condition-parse errors.
Added
- Workflow params via
${params.*}with CLI/library overrides: Top-level Dippinvarsbecomeparams.*graph attrs available in prompts, tool commands, and edge conditions. Repeatable--param key=valueon the CLI plustracker.Config.Paramsfor library callers. Overrides hard-fail on unknown keys. New lint rules DIP120 (undeclared reference) and DIP121 (unused var). - Per-human-gate
timeout/timeout_actionin.dip: Exposed by dippin-lang v0.21.0'sHumanConfigIR and copied into node attrs consumed by the human handler. - Workflow-level budget ceilings from
.dip:max_total_tokens,max_cost_cents, andmax_wall_timein a workflow'sdefaults:block feedResolveBudgetLimits. Explicit config/CLI flags still win. - Library API hardening for v1.0: Typed
CheckStatusandSuggestionKindenums;tracker.WithVersionInfo(version, commit)functional option;DiagnoseConfig.LogWriter/AuditConfig.LogWriter;Doctor,Diagnose,Audit,Simulateall takecontext.Context; provider probe error bodies sanitized; NDJSON writer closures recover from sink panics;Diagnosestreamsactivity.jsonlviabufio.Scanner. - TUI pre-populates subgraph children in the sidebar: Subgraph reference nodes no longer appear as opaque single rows until child events arrive.
- Agent QoL from SWE-bench: Turn-budget checkpoints at 50%/75%, two-phase verify-after-edit, tool polish (
grepcontext lines,readoffset/limit,editmiss context), subprocess group kill for orphan safety. Defaults promoted:MaxTokens: 16384, auto-continue on truncation,LoopDetectionThreshold: 4— SWE-bench Lite 59% → 70% baseline shift. --artifact-dirCLI flag overrides the node state directory.
Changed
- dippin-lang dependency v0.20.0 → v0.21.0. Picks up
${ctx.node.<id>.*}scoped-read lint support. - BREAKING (library):
Doctor,Diagnose,DiagnoseMostRecent,Audit,Simulateall now takecontext.Contextas the first argument.tracker.NDJSONEventrenamed toStreamEvent.NDJSONWriter.Writereturnserror.DoctorConfig.TrackerVersion/TrackerCommitremoved — useWithVersionInfo.
Fixed
- OpenAI Responses API
function_call_output/function_callserialization:omitemptyon required fields caused OpenRouter's strict Zod validator to reject requests (GLM, Qwen, Kimi). Replaced withMarshalJSONthat always emits required fields.
Added
- CLI↔library feature parity — Phase 1 (NDJSON) + Phase 2: Four CLI commands (
diagnose,audit,doctor,simulate) and the NDJSON event writer are now public library APIs. Consumers can reuse CLI behavior without shelling out. tracker.NewNDJSONWriter: Public writer producing the same wire format astracker --json. Factory methods forPipelineHandler,AgentHandler,TraceObserver.tracker.Diagnose/DiagnoseMostRecent/Audit/Doctor/Simulate: Structured reports with typed suggestions, timelines, check statuses, execution plans, and unreachable-node lists.tracker.ListRuns: Sorted[]RunSummaryfor enumerating past runs.tracker.ResolveRunDir/MostRecentRunID/LoadActivityLog/ParseActivityLine: Shared run-directory and activity-log helpers.tracker.PinnedDippinVersion: Constant exposing the dippin-lang version pinned ingo.mod.- SWE-bench harness (
cmd/tracker-swebench): New orchestrator binary evaluating tracker against SWE-bench. Includes Dockerfile, container lifecycle, dataset JSONL parsing, resumable results writer, resource limits, secure--env-file, and an in-containeragent-runnerthat captures changes viagit diff. WithExtraHeadersoption for Anthropic and OpenAI adapters: Injects gateway auth headers (e.g.cf-aig-token).- Structured reflection prompt on tool failure: When a tool call fails, the session injects a user-role reflection message before the next turn. Default on (
ReflectOnError: true), capped at 3 consecutive reflection turns. - Verify-after-edit loop with auto-test: Session optionally runs tests after any turn with file-edit tool calls. Auto-detection for Go, Rust, npm, Make, and pytest projects. Repair turns don't count toward
MaxTurns.
Changed
- CLI
diagnose.go/audit.go/doctor.go/simulate.goare now thin printers over the library APIs. Stdout and--jsonwire format are byte-identical. - dippin-lang dependency v0.19.1 → v0.20.0.
Added
- Webhook-based human gates for headless execution: New
tracker.Config.WebhookGatefield and matching CLI flags wire aWebhookInterviewerthat POSTs gate prompts to a user-configured webhook URL and blocks on a callback. Per-gate shared-secret tokens (X-Tracker-Gate-Token), per-gate timeout with configurable action, optional outboundAuthorizationheader, HTTP timeouts and 64 KB body cap. Flags:--webhook-url,--gate-callback-addr,--gate-timeout,--gate-timeout-action,--webhook-auth. Mutually exclusive with--autopilotand--auto-approve. - Cloudflare AI Gateway support:
TRACKER_GATEWAY_URLenv var and--gateway-urlflag route every provider through Cloudflare's AI Gateway. NewResolveProviderBaseURL(provider)helper with priority<PROVIDER>_BASE_URL> gateway URL + provider suffix > empty. - Library API for workflow catalog and resolution:
tracker.Workflows,LookupWorkflow,OpenWorkflow,ResolveSource,ResolveCheckpoint.Config.ResumeRunIDequivalent of the CLI's-r/--resume. ExportBundlelibrary API +--export-bundleCLI flag: After a run completes,git bundle create --allpackages the artifact directory into a single portable file. Clone withgit clone <bundle>anywhere.WithGitArtifactsengine option: Initializes the artifact run directory as a git repo and commits after every terminal-outcome node. Checkpoint tags (checkpoint/<runID>/<nodeID>) mark each save point.tracker doctorcomprehensive preflight: Per-provider API key validation with format hints,--probeflag for live auth validation, dippin version compatibility,.ai/writability, disk space warning (< 10 GB),.gitignoreline-by-line check, dangerous env var warnings,--backend claude-codeawareness. Exit code 2 for warnings-only.- Per-node context scoping (
ScopeToNode): After each node, every key written during that node's execution is copied intonode.<nodeID>.<key>. Downstream nodes readnode.MyAgent.last_responsefor specific upstream output. Bare keys retain last-writer-wins for backward compatibility. - Cost governance:
Result.Costwith per-provider rollup;pipeline.BudgetGuardenforcingMaxTotalTokens/MaxCostCents/MaxWallTime. Halts withpipeline.OutcomeBudgetExceededand firesEventBudgetExceeded. CLI flags--max-tokens,--max-cost(cents),--max-wall-time. - Per-node backend selection overrides global
--backend: A node withbackend: nativealways uses native even when--backend claude-codeis set globally. Enables mixed-backend pipelines.
Fixed
- Start/exit node handler overwrite broadened fix:
ensureStartExitNodesnow bases the decision onHandler, notprompt. Any non-codergenhandler is preserved; previouslytool,human,parallel,conditional,subgraph, andwait.humanhandlers on start/exit nodes were silently overwritten.
Added
- ACP (Agent Client Protocol) backend (v0.16.0): Third execution backend alongside native and claude-code. Spawns ACP-compatible coding agents as subprocesses via JSON-RPC 2.0 over stdio. Per-node via
backend: acp+acp_agentparams, global via--backend acp. - ACP provider-based agent routing (v0.16.0):
anthropic→claude-agent-acp,openai→codex-acp,gemini→gemini --acp. Override per-node viaacp_agent. - ACP environment scoping, model bridging, terminal management, and file ops (v0.16.0): API keys stripped from subprocess env;
mapModelToBridgemaps tracker model names to bridge model IDs; fullCreateTerminal/TerminalOutput/KillTerminalCommandwith process group isolation;ReadTextFile/WriteTextFilescoped to the node's working directory. - Dedicated
executeYesNohandler (v0.16.1):mode: yes_nohuman gates now present fixed Yes/No choices and correctly map toOutcomeSuccess/OutcomeFail. - Comprehensive human gate test suite (v0.16.2):
examples/human_gate_test_suite.dipexercises all 4 gate modes plus timeout, default_choice,ctx.outcomerouting, and interview cancel. ContextKeyTurnLimitMsgconstant (v0.16.4): New context key for turn-limit and loop-detection diagnostics.
Fixed
- ACP data races and safety (v0.16.0): handler mutex on empty-response check,
syncBufferfor subprocess output, protocol version validation, emptyCwdfallback,Pid > 0guard beforesyscall.Kill. mode: yes_nooutcome mapping (v0.16.1): No longer falls through to choice mode (which always returnedOutcomeSuccess). Pipelines usingctx.outcome = failwithyes_nonow route correctly.- Thinking signature dropped in streaming (v0.16.3): Anthropic SSE handler now captures
signature_deltaevents andredacted_thinkingblocks. Previously caused multi-turn Opus sessions with extended thinking to crash. - Turn-limit exhaustion treated as success (v0.16.4): Agents that exhausted their turn limit (or entered a tool call loop) were silently treated as
OutcomeSuccess, allowing pipelines to advance past nodes that wrote zero files. Now returnsOutcomeFail.
Changed
- dippin-lang v0.18.0 (v0.16.2): Adds
flattenpackage for inlining subgraph refs into a single flat workflow.
Added
- Per-node response context keys: Codergen and human handlers write
response.<nodeID>alongsidelast_response/human_response, so downstream nodes can reference specific upstream outputs. - Parallel concurrency limits:
max_concurrencyattr on parallel nodes bounds concurrent branch goroutines via semaphore, context-aware. - Parallel branch timeout:
branch_timeoutattr sets a per-branch context deadline so slow branches fail without blocking fan-in. - Human gate timeout:
timeoutattr on human nodes withtimeout_action(default/fail) anddefault_choicefallback — applied to freeform, choice, and interview modes. - Edge adjacency indexes:
OutgoingEdges/IncomingEdgesuse O(1) map lookup via adjacency indexes built byAddEdge. - Format constants:
FormatDipandFormatDOTtyped constants for pipeline format identification.
Fixed
- P0: Goal-gate infinite fallback loop:
FallbackTakenguard persisted in checkpoint prevents one-shot fallback/escalation from looping. - P0: Parallel branch context loss on fan-in:
PipelineContext.DiffFromcaptures side effects from parallel branches. - Adapter nil pointer guards, sentinel errors (
ErrNilWorkflow, etc.), deterministic map iteration, variable expansion single-pass, case-insensitiveauto_status, word-boundary fidelity truncation, and condition parser hardening (==operator, quote stripping). - Empty API response retry: Empty API responses (0 output tokens, 0 tool calls) now trigger
OutcomeRetryinstead of hard-failing.
Changed
- Retry backoff jitter:
ExponentialBackoffandLinearBackoffnow apply ±25% random jitter to prevent thundering herd.
Deprecated
- DOT format support:
ParseDOTis deprecated. Use.dipformat instead. DOT support will be removed in v1.0.
Added
- Interview mode for human gates:
mode: interviewenables structured multi-field form collection driven by upstream agent output. One question at a time with progress bar, selection feedback, and pre-fill on retry. - Structured JSON question format: Agents output
{"questions": [...]}with text, context, and options. Validated before presenting. Falls back to markdown heuristic parsing. response_formatsupport: Agent nodes can force structured JSON output at the API level viaresponse_format: json_object. All three providers supported (Anthropic, OpenAI, Gemini).- Agent
paramsmap: Generic key-value pass-through from.dipfiles (dippin-lang v0.16.0). Enablesbackend: claude-codeand other runtime features without IR schema changes. deep_reviewbuilt-in workflow: Interview-driven codebase review with 3 structured interview gates, parallel analysis (correctness/security/design), and remediation plan. Run withtracker deep_review.interview-loop.dipsubgraph: Reusable interview loop pattern (ask → answer → assess → loop) inexamples/subgraphs/.- Empty API response diagnostics: Session-level retry with diagnostic logging for 0-token API responses. Hard-fails after 2 retries.
Fixed
- Cancel returns OutcomeFail: Canceled interviews return
failinstead ofsuccess. Partial answers preserved. - Autopilot hard-fails on parse error: Both
AutopilotInterviewerandClaudeCodeAutopilotInterviewerretry once, then hard-fail. No more silent auto-approve. - Goroutine leak in flashDecision: Fixed with
donechannel anddefer/recover. - Mode 1 tea.Cmd propagation: All three TUI runners now propagate commands from content updates.
- Context leak in retry loop: Explicit
cancel()instead ofdeferinside for loop. - Empty response guard: Agent sessions with 0 output tokens and 0 tool calls now fail instead of silently succeeding.
Added
- TUI: Progress bar with ETA: Amber ASCII bar in status bar shows completed/total nodes.
- TUI: Log verbosity cycling (
v): All → Tools → Errors → Reasoning. - TUI: Zen mode (
z): Hide sidebar, full-width agent log. - TUI: Help overlay (
?): All keyboard shortcuts in styled table. - TUI: Agent log search (
/): Inline search withn/Nnavigation. - TUI: Per-node cost tracking: Cost badges on completed nodes in sidebar.
- TUI: Node drill-down (
Enter): Focus log on selected node. - TUI: Copy to clipboard (
y): Copies visible filtered log text. - Claude-code autopilot: Routes gate decisions through
claudeCLI subprocess. - Strict failure edges: Pipeline stops on uncaught failures instead of silently continuing.
Fixed
- Claude Code subprocess killed after 10s: Go's
exec.CommandContext+WaitDelaysent SIGKILL. Switched to plainexec.Command. - Claude Code auth failure: Stripped env prevented OAuth token discovery. Now passes full parent environment.
- NDJSON subagent content: Handles both string and array
contentformats from Claude Code's subagent tool results.
Added
- Autopilot inside the TUI:
--autopilotno longer forces--no-tui. Decisions flash in a modal for 2s. - Header tags: Orange
claude-codeand purpleautopilot:laxtags in the TUI header bar.
Added
tracker update: Self-update command downloads the latest GitHub release, verifies SHA256 checksum, extracts the binary, smoke-tests it, and atomically replaces the current binary with.bakrollback. Detects Homebrew,go install, and direct binary installs.- Non-blocking update check: On every
tracker run, a background goroutine checks for new releases (24h cache). Prints a one-line hint if an update is available. Disabled withCIorTRACKER_NO_UPDATE_CHECKenv vars. - Claude Code backend: Pluggable agent backend abstraction.
--backend claude-codespawns theclaudeCLI as a subprocess and parses NDJSON output, giving agents file editing, terminal access, and MCP tool servers. Per-node viabackend: claude-codein.dipfiles, or global via the CLI flag. AgentBackendinterface: Minimal contract inpipeline/backend.go— one method to execute a node and return an event stream. NativeBackend wrapsagent.Session(existing behavior), ClaudeCodeBackend wraps the subprocess.- dippin-lang v0.12.0: Upgraded from v0.10.0 — picks up
preferred_labelon freeform gates,immediately_afterassertions, tool command lint rules, subgraph validation, anddippin test --coverage.
Fixed
- PickNextMilestone silent skip: Flexible milestone header matching, fails loudly if no milestones found or extraction empty
- Removed
evalof LLM-generated verify commands: Was arbitrary code execution from free-form text - TestMilestone known_failures: Strips comments, uses
go test -skipinstead of unsupported negative lookahead - PickBest winner parsing: Uses
grep -ioEregardless of markdown formatting
Fixed
- Provider errors now hard-fail per CLAUDE.md (only parse failures retry)
- AskFreeform logs warning instead of silently swallowing errors
- Default model picks cheapest from configured provider (not hardcoded to Anthropic)
- Autopilot forces
--no-tui matchChoiceuses longest-match to prevent "a" matching before "abandon"decide()returns errors instead of silently falling back
Added
--autopilot <persona>: Replace human gates with LLM-backed decisions (lax / mid / hard / mentor)--auto-approve: Deterministic auto-approval for CI and testing- dippin-lang upgraded to v0.10.0
v0.10.x
2026-03-26Added
- Embedded built-in workflows: 3 flagship pipelines in the binary (
tracker build_product) tracker workflowsandtracker initcommands- Bare name resolution: filesystem → embedded → error
- Deterministic failure detection in
tracker diagnose - 23 dippin simulation tests for build_product.dip
Changed
- Split
EscalateToHumanintoEscalateMilestone+EscalateReview - Escalation gates have
prompt:blocks with rich context
Fixed
- TestMilestone early-exit bug (attempt counter fired before tests)
ContextKeySuggestedNextNodesconstant (6 string literals eliminated)- Complexity violations in diagnose.go
v0.9.x
2026-03-25Added
tracker diagnose: Deep failure analysis with tool output, timing, suggestionstracker doctor: Preflight health check- Provider status in
tracker version - Freeform "other" option in review hybrid gates
- Runtime error surfacing in TUI activity log
- Subgraph loading with cycle detection
- Hybrid radio+freeform gates
- Decision audit trail in activity.jsonl
v0.8.0
2026-03-25Added
- Decision audit trail (edge selection, condition evaluation, node outcomes)
- Checkpoint resume with edge replay
- Per-node working directory for git worktree isolation
- Conditional edge evaluation with
ctx.namespace
Full changelog: CHANGELOG.md on GitHub