Changelog
Every feature, fix, and breaking change. All GitHub Releases →
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