All notable changes to taskflow are documented here. This project follows Keep a Changelog format.
- HOL plugin-scanner highs: dummy test credentials use scanner placeholders / short literals; no
eval (in comments; smoke/runner no longer place a${…}template within 30 chars ofspawn. Local scan 81/100, 0 high. Not GA.
Hotfix on
0.3.0-beta.1.1. npmbetadist-tag. Not GA. Does not implement#137(/tf web) or#95(adaptive-authority isolation). Does not steal0.3.0-beta.2.
taskFileload-time include (issue #143). A phase or parallel branch may declaretaskFileinstead oftask. Trusted loaders (defineFile/ saved flow) resolve the literal path against the definition file's directory (same class of source asscriptCwd: "flow"), inline the UTF-8 body intotask, and deletetaskFilebefore validate / interpolate / cache / FlowIR. XOR withtask; leftovertaskFileon inlinedefineisTF_TASKFILE_NO_PROVENANCE; generated sub-flows reject it asTF_DYNAMIC_RESOURCE_FORBIDDEN. Path is not interpolated. Include cap is 256 KiB (taskFile exceeds). TS DSL:agent({ taskFile: "prompts/x.md" })only — a bareagent({ model })is not opts-only. Compile XOR keeps both fields. Nested inlinedefis not inlined.taskflow-dslcheck does not read the file.
- Absorb open Dependabot PRs into this line: pnpm/action-setup 6.0.10 (#129); codeql-action 4.37.7 (#145, #146); Pi 0.84.2 + typebox ^1.3.14 + biome 2.5.8 + @types/node 26.2.0 (#147); next 16.3.1 (#136); lucide-react ^1.31.0 (#135); fumadocs-ui 16.14.4 (#134); fumadocs-mdx 15.2.3 (#133).
fumadocs-corestays 16.14.0 (no Dependabot PR). Pin workspacenanoidoverride 3.3.17 → 3.3.18 (Dependabot alert #33, GHSA-2v37-7h3g-55p8).
- All publishable surfaces aligned to
0.3.0-beta.1.2.
Hotfix on
0.3.0-beta.1. npmbetadist-tag. Not GA. Does not implement#137(/tf web) or#95(adaptive-authority isolation).
- Windows detached runs (issue #139).
detached-runnernow converts hostrunnerModulefilesystem paths tofile://specifiers before dynamicimport(), so a nativeC:\\…\\runner.jsis no longer rejected as protocolc:. The Pi adapter re-enters the installeddist/cli.jsviaprocess.execPath(and serializesPI_TASKFLOW_PI_ENTRYinto the detached child) instead ofspawn("pi")/spawn("pi.cmd"), which are ENOENT/EINVAL on npm's Windows shims. - Docs search 404 (issue #138). Static GitHub Pages export now pre-renders the Fumadocs search index (
/api/search) and the client loads it withtype: 'static'.
- All publishable surfaces aligned to
0.3.0-beta.1.1.
Pre-release candidate:
0.3.0-beta.1is prepared for npm'sbetadist-tag. It is not GA. The 0.3-C Control Plane remains a follow-on candidate track, not part of this beta's shipped product definition.
- Trusted Effects MVP (
packages/taskflow-core/src/effects/):- EffectIR (
EFFECT_KINDS), PathRef reuse, SecretRef/ServiceRef (type-only fail-closed) - closed TypeBox EffectIR + confidentiality/integrity source-to-sink validation
- resource-controlled FS transaction: durable snapshot → persistent lease → journal intent/permit → stage → Commit or Restore+Reject
- declaration-only bridge in
effects/runtime-apply.ts; no second changeset/gateway authority - ledger-backed
whyAuthorized/whyContext/whyEffect
- EffectIR (
- Optional phase
effects[]; FlowIR translate/compile/hash include effects - Built-in
detectEffectsIssues(categoryeffects) +effectsLintVerifier - Every imperative phase fast path finalizes declared
fs.writethrough the resource transaction; event-kernel-enabled runs use the same safe imperative path - Honest host baseline:
conformance/workspace/host-support-baseline.json - Docs:
docs/internal/0.3.0-trusted-effects-mvp.md,0.3.0-agent-goal.md,0.3.0-ga-scoreboard.md - Example:
examples/trusted-effects-write.json - Tests:
test/effects*.test.ts,test/verify-effects.test.ts
- Resource-bearing inline/saved/expanded/
ctx_spawnchildren can no longer be skipped by parent cache or resume reuse. - Information-flow labels compose across nested flow boundaries; unresolved dynamic definitions remain tainted, malformed non-array
effectsfail admission/compile, andwhy-effectfollows DAG dependencies. - Durable commit/abort results survive staging/lease cleanup faults, activation double faults release leases, and clean-terminal/aged-orphan before-images are garbage-collected.
- SecretRef/ServiceRef have no vault/network backends in this cut.
- Resolve-only is not an OS sandbox. Direct writes to declared targets are detected and restored; writes outside declared targets remain host-policy dependent.
- Historical Control Plane (
feat/0.3.0) is not this release definition. - Beta release; not GA.
- Organized saved-flow discovery. Project and user flows may be nested recursively below the bounded
flows/convention directory (for example.pi/taskflows/flows/release/publish.json) while legacy top-level saved flows remain compatible and take same-scope precedence on duplicate names. - Flow-relative script execution. A file-backed flow can declare
scriptCwd: "flow"so script phases without an explicitcwdexecute from the definition file's directory. Canonical loader provenance is persisted across foreground, background, resume, saved subflow, Pi, MCP, imperative, and event-kernel paths; inline definitions fail closed because they have no trusted file source.
- Nested discovery is deterministic and bounded by one shared user/project budget: 1,000 flows, 10,000 entries, 512 directories, 8 MiB total definitions, 1 MiB per definition, and 16 levels. Symlinks below trusted storage boundaries through definition leaves, dot directories, sidecars, and FlowIR artifacts are excluded; duplicate flow names emit diagnostics. A configured user agent-directory boundary may itself be a symlink for home relocation, while project
.piboundaries remain no-follow. Re-saving a discovered nested flow updates its existing definition and adjacent library sidecar in place; listing/search avoids repeated namespace scans.
- Legacy top-level hidden JSON flows and flow names ending in
.flowirremain discoverable, directory-entry I/O failures remain fail-soft instead of escapinglistFlows, and nested flow/sidecar saves revalidate the physical parent at each atomic-write stage so a concurrently replaced directory cannot receive definition/sidecar bytes or a promoted final file.
- Saved-flow and
defineFiledefinitions are read through bounded no-follow descriptors with pre/post file identity checks. Canonical source path plus parent directory identity travel together through run persistence and are revalidated immediately before flow-relative script spawn. New project storage is validated and created one plain directory component at a time before any descendant side effect; flow and sidecar writes recheck the physical target directory before locking and each write. Flow-relative script cwd cannot expand an inherited cwd-bridge boundary.
- Hermes Agent host. New
hermes-taskflowdelivery package andtaskflow-hostsHermes runner, with a stdio MCP scaffold, generated skill, English/Chinese guides, and first-release install pinhermes-taskflow@0.2.9. - Hermes read-only toolsets. Every child receives an explicit
-t; local reads usetaskflow_readonly_files, while model-only phases use an emptytaskflow_model_onlytoolset. Read-only web access remains an explicit opt-in.
- Pi
/tf verify <name>. The slash handler now implements the advertised command and preserves schema-valid flow names containing spaces, matching the tool action and autocomplete surfaces. - Project flow discovery. Ancestor discovery canonicalizes relative/symlink paths and stops at the user home and shared OS temp root, preventing
/tmp/.pior~/.pifrom being mistaken for project state. - Hermes plain-text protocol. Blank lines and stdout text beginning with
session_id:are preserved as answer content; session metadata is parsed only from stderr. Abort, idle, and empty-output diagnostics remain fail-closed. - Hermes installation examples. CLI examples use the live
hermes mcp add --command … --args …syntax.
- Hermes child isolation. Read-only
read_fileandsearch_filespaths are canonicalized and confined to the resolved phase cwd, including.., absolute paths, and symlink escapes. The child home receives a filtered inference-provider-onlyauth.json, an exact provider dotenv allowlist, and non-secret model/fallback routing; unrelated application credentials and parentHERMES_*control-plane state are not copied. - Mutating authorization. The read-only plugin is loaded only for read-only children. Explicitly authorized
PI_TASKFLOW_HERMES_UNSAFE_YOLO=1phases default to localfile,terminal; network requires explicit web aliases, while delegation/skills/memory/browser/cron/control-plane toolsets remain denied.
- Root, all ten publishable packages, plugin manifests, MCP install pins, server-version contracts, website metadata, and deterministic publish/packed-consumer gates are aligned to 0.2.9.
- Pi approval confirmation. Approval choices are now selection-only:
A/E/R, arrows, and Tab move the pending decision, while Enter confirms it. The dialog starts on Reject, so an accidental Enter fails closed; Escape and Ctrl-C still reject immediately. - Large approval proposals. Long proposals start collapsed,
Vtoggles an inline scrollable preview, and the decision footer remains visible while reviewing. Short proposals remain expanded by default. - Kitty keyboard handling. Press, repeat, and release events are decoded explicitly so key-release frames cannot trigger a decision and repeated preview keys cannot double-toggle the proposal.
- Root, all nine publishable packages, plugin manifests, MCP install pins, and server-version contracts are aligned to 0.2.8.
- Force patched transitive versions of
brace-expansion@5.0.9andnanoid@3.3.17via pnpm overrides; the production dependency audit is clean.
- Preflight plan (
taskflow_plan/action=plan//tf plan). Zero-token dry-run: bind typed invocation args, run structural verify + optional lint verifiers, project topo-ordered phase plan with bound/unresolved/dynamic bindings, and compute a worst-case agent-call bound (loops usemaxIterations; dynamic maps reportunbounded). No subagent is spawned. - Budget upper bound in plan.
PreflightResult.budget.maxAgentCalls(number or"unbounded") with explicit assumptions — the static cost spine for “plan before spend”. - Incremental savings summary. Shared
formatSavingsLine/formatRecomputeSavingsHeader/formatRunCacheLine: recompute MCP/Pi output leads withreused N · rerun M · cutoff K · saved ~P% phases; run/background status can show cache-hit counts. - Flow hooks (
hooks.onComplete/onFail/onBlocked). Fire-and-forgetwebhook|file|command(argv only) with summary payloadtaskflow.hook.v1(never transcripts).httpsandhttp://127.0.0.1|localhostallowed. Hook failure never changes run status. Dispatched from imperative and event-kernel terminal paths. - Approval
timeoutMs+onExpire. Optional HITL wait bound (>= 1000ms).onExpire:reject(default) |fail|approve(explicit footgun). Infinite wait when omitted (backward compatible). - Read-only analytics (
taskflow_analytics/action=analytics//tf analytics). Last-N run aggregation: status histogram, p50/p95 duration, per-phase fail/cache rates. No writes, no auto-tune. - Templates:
examples/templates/plan-first.json,examples/templates/background-with-hooks.json. - MCP tool roster is now 19 (
taskflow_plan,taskflow_analyticsadded).
- Force
undici@8.9.0via pnpm override (Dependabot high/medium: CRLF, cache directives, cookie attributes, retry desync). - Bump pinned CodeQL Action init/analyze to the v4.37.4 commit SHA.
- Website: fumadocs-core/ui 16.14.0, fumadocs-mdx 15.2.2, lucide-react ^1.28.0, biome 2.5.6, @types/* patch bumps.
- Dev peers: Pi SDK 0.83.0, typebox ^1.3.10.
- Plugin manifests and MCP install pins aligned to 0.2.7.
- Shell CLI
taskflow planis intentionally not in this cut (taskflow-cliremains control-plane oriented). Use MCP or Pi surfaces. - Control-plane / daemon packages are unchanged by this release narrative (0.3 work stays separate).
- Pi oversized terminal history handling. After a complete final assistant
message_end, the Pi runner can now discard an oversized redundantagent_end.messagesrecord without retaining its full history payload, then continue waiting for the compact authoritativeagent_settledevent. Oversized records still fail closed before a valid final message, for other event types, or when the discarded record is unterminated. - Deterministic process-supervisor CI checks. Replaced sub-second wall-clock assertions with outcome-based checks and wider watchdog windows, avoiding false failures on loaded Windows runners while preserving process-tree and terminal-grace coverage.
- Upgraded Next.js to 16.2.12 and forced patched transitive versions of
sharp0.35.3,postcss8.5.23, andbrace-expansion5.0.8. The production dependency audit is clean. - Updated pinned GitHub Actions revisions to
actions/checkout7.0.1 andgithub/codeql-action4.37.3.
- Refreshed Pi development dependencies to 0.82.1, TypeBox to 1.3.8, React/React DOM to 19.2.8, Lucide React to 1.27.0, Tailwind CSS to 4.3.3, and Biome to 2.5.5.
- Upgraded the repository package manager to pnpm 11.17.0 so root workspace overrides are enforced and recorded reproducibly in the lockfile.
- Four responsibility-based model roles. The built-in agent roster now uses
steward,expert,builder, andscoutinstead of coupling workflow responsibilities to six model capability labels. The recommended OpenRouter bindings are Claude Fable 5 for long-horizon goal stewardship, Claude Opus 5 for deep specialist judgment, Claude Sonnet 5 for default implementation and review, and Claude Haiku 4.5 for fast reconnaissance and mechanically checkable work. - Role ownership is separate from expertise and authority. Planning/final synthesis use
steward; analysis, critique, plan gates, risk, and security useexpert; implementation, UI, review, testing, docs, and recovery usebuilder; discovery, trivial execution, and mechanical verification usescout. Tool permissions, phase side effects, gates, budgets, and approvals remain runtime/agent concerns rather than model-tier concerns.
- Existing 0.2.4 model-role settings remain valid. The legacy
fast,strong,thinker,arbiter,vision, andreasonerkeys are preserved. Until a new semantic role is configured, each built-in agent falls back to the exact legacy key it used in 0.2.4; new keys always take precedence. User and project agents that still reference legacy keys continue to resolve them normally.
- Updated generated host skills plus English and Chinese agent/model-role guides for the four-role contract, current defaults, upgrade behavior, and model-neutral rebinding guidance.
- Event kernel concurrent DAG layers. The event kernel now executes phases within a topological layer concurrently via
Promise.allwith atomic layer-boundary commit. Gate and budget decisions are checked between layers, not within. This removes the single largest blocker to kernel default-ON: previously any flow with independent parallel phases (the common case) was forced to the imperative runtime. - Kernel retry support.
phase.retry = { max, backoffMs, factor }is now honored on the event kernel path for ALL failures (not just transient errors), matching the imperative contract. BothrunOneAgent(step.ts) andrunAgentCall(step-kinds.ts) implement the full retry curve with budget-aware admission. - Kernel context pre-read.
phase.contextfiles are now read and prepended to the prompt as<context file="...">blocks on the event kernel path, matching the imperative runtime'sresolvePhaseContextbehavior. - Kernel per-phase literal cwd. Literal string
phase.cwdvalues (absolute or relative to the flow cwd) are now resolved and passed torunTaskon the event kernel path. Interpolation placeholder cwds ({args.*},{steps.*}) and workspace keywords (temp/dedicated/worktree) remain imperative-only. - Built-in script-lint verifier. New
scriptLintVerifier(exported fromtaskflow-core/verifiers) statically analyzesscriptphaseruncommands for common shell mistakes:greppatterns starting with-without a--separator, unbalanced regex brackets/parens ingrep/sed, and pipelines ending with a filter (grep/awk/head/tail/wc/sort) withoutset -o pipefailorPIPESTATUS. Auto-included bycompileTaskflow(opt out withlint: false). Motivated by issue #82. - Verifier discovery. New
discoverVerifiers(cwd)API loads project-local verifiers from.pi/taskflows/verifiers/(project scope, shadows user) and~/.pi/taskflows/verifiers/(user scope). Each.ts/.js/.mjsfile is dynamically imported; default,verifier, andverifiersexports are collected. Fail-open: broken modules are skipped with a warning. Convention over configuration — no config file needed. - MCP
taskflow_linttool. The 17th MCP tool. Runs built-in and discovered pluggable verifiers on a flow without executing it. Exposes the verifier ecosystem to all MCP hosts (Codex, Claude Code, OpenCode, Grok Build). Providename,define, ordefineFile. - Compile subgraph visualization. Inline flow definitions (
phase.def) now render as Mermaidsubgraphblocks showing child phases and their dependency edges. Saved-use flows (phase.use) remain plain nodes. The subgraph title shows the child flow's name. - S5.0 kernel parity harness. New
kernel-parity.test.ts: 11 golden fixture flows run on BOTH engines (imperative + event kernel) with a deterministic mock runner, asserting status, per-phase output, error, and gate-decision parity. Covers: linear chain, concurrent independent agents, gate, reduce, when-guard skip, join-any with optional dep, script, loop, three-layer wide DAG, retry, and gate-in-concurrent-layer. This is the differential gate that must stay green before the kernel can be flipped to default ON (S5.2).
- MCP tool count is now 17.
taskflow_lintjoins the existing 16 tools. Marketplace metadata, host adapter tool-roster tests, and skill discovery assertions are synchronized. - Kernel admission expanded.
canUseEventKernelnow admits flows with: concurrent DAG layers, explicitretry,contextpre-read, and literal per-phasecwd. Remaining imperative-only features:scoregates,expectcontracts,reflexion,onBlock:retry,reduceStrategy:"tree", cross-run cache,shareContext, per-branch cwd, script stdin input, interpolated script argv, budgeted fan-out, and budgeted loop/tournament.
- Gate parity normalization. The kernel parity harness normalizes gate objects to strip
reason: undefinedvs absent-key serialization differences between the two engines.
- Durable MCP background runs.
taskflow_runnow acceptsmode: "background"on Codex, Claude Code, OpenCode, and Grok Build, returning a durablerunIdimmediately instead of tying a long DAG to one MCP request timeout. The newtaskflow_runstool lists background runs and supportsstatus, bounded/repeatablewait, and explicitcancel; lists report the total active count and can filterrunningversusterminalruns. Detached runs persist their final output and trace, preserve incremental-cache and library-reuse behavior, detect orphaned processes, and use a file-backed cancellation control plane that survives MCP request and server boundaries. Starting a sixth concurrent background run emits an explicit resource-contention warning because Taskflow intentionally has no hidden global cross-host scheduler.
- Release maintenance rollup. The 0.2 frontier assessment is now linked from both READMEs; Pi development peers move to 0.80.7, Fumadocs packages move to their mutually compatible 16.11.5/15.2.0 set, Biome moves to 2.5.4, and every workflow uses the verified
actions/setup-nodev7.0.0 tag commit. These changes absorb the independently opened maintenance PRs into the fully tested 0.2.3 release transaction.
- Atomic terminal results. The runtime now persists
finalOutputandoutputSourcePhaseIdin the same terminal-state write, so a crash or immediate poll can never observecompletedwithout its result. - Detached-run ownership and cleanup. Cancellation and process-heartbeat records now live in a user-private control directory keyed by canonical invocation root, preventing project symlinks and sibling worktrees from redirecting or cross-cancelling runs. Current workers carry a versioned instance identity; stale authenticated workers are killed before terminalization and their registered Host CLI process groups are reaped, inherited detached-runner environment variables cannot claim an outer run, the spawn-only worker consumes only user-owned private temp contexts, and ambiguous legacy runs fail closed for cancellation.
- Foreground/background parity. MCP and Pi detached launches snapshot the same agent scope, model roles, global thinking, runner profile, incremental settings, and retention policy as the foreground invocation. Pi now uses fully detached stdio and transactionally reaps the worker plus its private launch context when post-spawn setup fails. Launch failures preserve their real cause, and post-spawn roster diagnostics can no longer misreport a successfully started run as a launch failure.
- Bounded run history and accurate rosters. Retention now applies to every inactive state (
completed,failed,paused, andblocked) while never pruning active runs. Cleanup is throttled independently per project, uses short fail-open lock waits, validates physical directories as well as indexed paths, and rechecks the selected snapshot under the run lock, preventing cross-project starvation, symlink traversal, and resume/delete races. Detached control records are removed only when their persisted cwd belongs to the run store being retained. MCP background lists compute counts from the complete project roster without the former 1000-run cap or per-row reload pattern. - File-lock ownership. Expired locks are stolen only when their recorded owner process is definitively dead, stale-lock stealers serialize on a generation claim, and release verifies both inode and a random owner token. A slow live writer can no longer overlap a replacement writer or unlink its successor's lock.
- Run-id and status compatibility. Dot-leading flow names now produce persistable run IDs without weakening traversal guards. Background status ignores synthetic launch phases in progress totals and tolerates malformed legacy optional output instead of crashing the MCP request.
- Release surface synchronization. Package/plugin versions, installation pins, built-dist MCP expectations, the 16-tool roster, background-run documentation, and English/Chinese host guides are synchronized for 0.2.3.
reduce.from+{previous.output}now aggregates all sources. Areducephase's{previous.output}previously resolved to only the last completed dependency. It now resolves to all completedfrom[]outputs in from-array order: one completed input → its raw output; multiple →### <id>\n\n<output>sections joined by\n\n---\n\n.join: "any"includes only completed branches (skipped/failed omitted). Explicit{steps.ID.output}refs are unchanged. If your reduce task used{previous.output}expecting only the last dep, it now receives everyfrom[]output — switch to explicit{steps.ID.output}refs to address individual sources. This fixes a long-standing dogfood issue where reducers silently lost inputs.- Resume now accepts only
failedorpausedruns. Attempts to resumecompletedorblockedruns are rejected before a fork is created. To execute completed work again, start a fresh run; to refresh a changed phase and its stale downstream frontier, userecompute. - MCP
serverInfo.nameis now host-specific. Clients that asserted the former generic"taskflow"name must accept"taskflow-codex","taskflow-claude","taskflow-opencode", or"taskflow-grok"for the corresponding adapter. Tool names and protocol behavior are unchanged.
- Configurable idle watchdog (
idleTimeout). New flow-level and phase-level DSL fieldidleTimeout(ms). A positive value (≥ 1000) overrides the host default (300000 ms): if a subagent produces no output for this long it is killed as stalled.idleTimeout: 0disables the watchdog — but validation then requires a finite walltimeout(≥ 1000) on every agent-running phase that can use it, so a flow can never hang forever (critical invariant). Per-phase overrides flow-level. Threaded intoRunOptions.idleTimeoutMson both the imperative and event-kernel paths; included in cache identity/fingerprints automatically via the FlowIR definition hash (so changing it invalidates cross-run cache). - Prompt-size diagnostics. Every agent call's resolved prompt now records durable
PhaseState.promptStats: exact UTF-8 byte count, character count, and a conservative approximate token estimate (ceil(chars/4), not a real tokenizer). A warning is appended when a prompt crosses a conservative threshold (~32K est. tokens).reducephases also recordreduceInputsaggregate stats (count + total bytes/chars/estTokens over thefrom[]inputs). Mirrored on the event-kernel path. - Hierarchical (tree) reduce. Opt-in via
reduceStrategy: "tree"+batchSize(integer ≥ 2) on areducephase. Tree reduction batches the aggregatedfrom[]inputs, runs intermediate reducer calls using the same agent/model/options/timeout/idleTimeout, and reduces round outputs until one remains — useful when the aggregated input would exceed a single prompt. Failures/usage/retry/budget behavior is preserved (each intermediate call reuses the phase'srunOnewrapper), with a hard cap of 256 actual reducer attempts (including retries) even when no budget is declared. Default remains one-shot.reduceStrategy: "tree"forces the imperative runtime (the event kernel falls back viakernelUnsupportedReason); the corrected{previous.output}aggregation always applies regardless of strategy. - Shorthand
cwd(dogfood issue 2). Top-level + per-stepcwdfor shorthand specs ({task}/{tasks}/{chain}). A top-levelcwdis the default working directory for every step; a per-stepcwdoverrides it. Single and chain shorthand propagatecwdto eachPhase.cwd(full workspace-keyword lifecycle —temp/dedicated/worktree). Paralleltaskshonor each branch's own literalcwd, including mixed values across branches (a branch without acwdfalls back to the shared phasecwd); per-branch workspace keywords are rejected at validation time (the workspace lifecycle is per-phase — use the phase-levelcwdfor isolation). - Build/host identity (dogfood issue 4). New exported
getBuildInfo()API (packageVersion,gitCommit,schemaVersion,buildTime). Package version is read from package metadata; the git commit is stamped at build time intodist/build-info.json(gitignored) —gitis never invoked at runtime (deterministic"unknown"fallback in source/dev checkouts;PI_TASKFLOW_BUILD_COMMITenv override).RunStatenow carries optionalpackageVersion/gitCommit/host/schemaVersion/parentRunId; index entries preservehost/packageVersion/parentRunId. Pi runs stamphost=pi; MCP runs stamp the bound host (codex/claude/opencode/grok). New Piaction=version+ MCPtaskflow_versiontool; the MCPinitializeserverInfo name reflects the host (taskflow-codex, …). - Resume overrides + immutable history (dogfood issue 5). Resume now forks a new run (new
runId+parentRunId) — the original run file is never mutated or overwritten. AResumeOverridesAPI (phaseId+ optionaltask/model/timeout/idleTimeout; at least one override required) re-runs exactly one phase with the overrides applied to the child's def only (the parent def is untouched); the target + its transitive downstream are cleared so they re-run, while completed unaffected phases are reused (within-run cache hits). Without overrides, ordinary resume forks a new run and re-runs the non-done phases. Validated, testable helpers (forkRunForResume/applyResumeOverrides/validateResumeOverrides/transitiveDownstream) live intaskflow-core/resume.ts;forkRunForResumefails closed for an invalid stored definition or invalid overrides. Piaction=resumeacceptsphaseId+resumeTask/resumeModel/resumeTimeout/resumeIdleTimeout; new MCPtaskflow_resumetool; resume rendering reports the new run id + parent run id. - Output source attribution (dogfood issue 6).
RuntimeResult/EventKernelResultnow carryoutputSourcePhaseId— the id of the phase whose output actually suppliedfinalOutput(the fallback final phase, or the blocking gate/approval phase for a gate/budget prefix;undefinedwhen no phase output is available). Final-phase selection/fallback and the no-output default are unified between the imperative runtime and the event kernel via a sharedresolveFinalOutputhelper (taskflow-core/final-output.ts). PifinalResultand MCPtaskflow_run/taskflow_resumerendering now label the actual source phase — never the designated skipped/failed final phase.
- First-run trace regression guard. A
FileTraceSinkcreated before the per-flow run directory exists now has integration coverage proving the first run immediately records a readable, non-empty trace. - Timeout closure.
raceis now covered by idle-watchdog validation, and flatctx_spawndescendants inherit the parent phase's idle watchdog, wall timeout, retry, tracing, budget, cwd, and workspace policy through the sharedrunOnepath. - Tree-reduce cache, supervision, and attempt accounting. Tree reductions now consult a compact, stable pre-execution cache key that includes explicit and transitive step outputs, so completed work is reusable without stale hits. Intermediate reducer calls drain
ctx_spawnchildren through the normal timeout/retry/budget/workspace path and fold their reports and usage into the tree. The 256-call ceiling counts actual runner attempts (including retries) without losing the final admitted attempt's usage; prompt diagnostics retain each reducer/child attempt once; budget truncation is explicit, preserves untouched inputs, is never cached, and aborts fail instead of masquerading as completion. - Budgeted fan-out supervision. Budgeted parallel/map phases publish each completed sibling's cumulative usage before draining its spawn intents. One atomic call may cross the ceiling, but no
ctx_spawnchild or sub-flow is admitted after that overshoot; pending intents are still drained deterministically. - Cwd and resume hardening. Every phase cache key now includes the canonical effective invocation cwd, including the default cwd, preventing a resume from mixing outputs produced in different roots. Parallel branch cwd values are resolved/canonicalized before both cache lookup and execution, and
branches[].cwdon race/tournament is rejected instead of silently ignored. Resume deep-clones retained state, preserves workspace provenance, accepts only failed/paused runs, allows an override to re-run an already-completed upstream phase plus its downstream, and uses one host-neutral validation contract so an override can repair an invalid stored definition before execution. Children stamp the current build and host identity. - Event-kernel prompt accounting. Reduce/gate/loop/tournament retries now append one prompt diagnostic per actual runner attempt, matching the imperative runtime.
- Release identity and discovery metadata. Pi's tool description and the Codex/Claude/Grok marketplace manifests now advertise all 12 phase types and all 15 MCP tools, including resume/version.
stamp-build-info --checkis read-only and rejects stale/malformed build stamps instead of rewriting them before validation.
- Typed invocation arguments. Flows may declare
string,relative-path,number,boolean, andenumargs. Defaults and invocation values are validated, and typedrequired: trueis enforced at invocation boundaries. Legacy untypedrequireddeclarations remain advisory for compatibility. - Experimental, default-disabled dynamic-cwd compatibility bridge for #70. An author-written phase
may use an exact
cwd: "{args.package}"whenpackageis declared as a typedrelative-path. Values are portable relative paths, resolved from the invocation root, must name an existing directory, and are checked afterrealpathso..and symlink escapes fail closed at bind time. Concatenation,{steps.*}, undeclared/legacy args, absolute paths, and generated sub-flows remain rejected. Canonical FlowIR records a logical read-write, existing-directorycwdUse, never the machine path. - Partial Workspace Capability control-plane scaffold. Internal host-neutral
authority, path-resolution, lease, journal, mutation-permit, sandbox-policy,
and exact conformance-baseline contracts support the future native backend
without becoming public root-package API in this patch release.
This does not ship a native
WorkspaceExecutionBackendor race-freeFileBroker; 0.2.1 execution remains explicitlyresolve-only.
- The cwd bridge is disabled by default until a sandbox backend passes the
Workspace Capability RFC's host conformance gates. A host operator can opt
into the explicitly weaker resolver-only mode with
TASKFLOW_CWD_BRIDGE_MODE=resolve-only; every affected phase reports that the directory is not a filesystem sandbox. - Argument-selected cwd phases reject
retry.max > 0. A failed resolve-only writer may already have mutated files, so Taskflow recordsdirty-unknownand requires explicit workspace reconciliation instead of replaying side effects automatically. - Pi (
action: "reconcile-workspace",/tf reconcile-workspace --ack) and all MCP hosts (taskflow_reconcile_workspace) expose that deliberate recovery operation. It takes an exclusive whole-root lease, writes a durable reconcile record, and advances generation; it never restores or silently approves files. Model-callable recovery also requires the host-onlyTASKFLOW_WORKSPACE_RECONCILE_MODE=explicitswitch, which is stripped from subagent environments. The Pi slash command is a direct user control-plane action and does not require the switch. - Cwd-bridge flows disable output-only cache/resume reuse across their reachable nested flow tree. This prevents a cache hit from skipping workspace mutations before workspace-state restoration exists.
- Saved-flow definitions are snapshotted once per top-level execution, and bridge runs persist the invocation root's canonical path/device/inode identity. Resume fails closed if the root is rebound or a child gains cwd authority.
- A bridge-selected sub-flow inherits a non-expanding canonical cwd boundary: nested literal cwd and context pre-reads may narrow it but cannot escape it lexically or through symlinks; allocating a workspace provider is rejected.
- Relative literal phase cwd values and phase context files are now anchored to the Taskflow invocation cwd rather than the parent Node process cwd.
- MCP execution now applies declared defaults and invocation validation before
creating
RunState, matching Pi, detached, and direct Core execution. - OpenCode now forwards phase → agent → global
thinkingthrough the native provider-specific--variantargument, normalizingoff → noneandultra → max. Other levels remain best-effort because each provider/model exposes a different variant set. - Runtime-generated sub-flows now reject every
cwdandcontextfile pre-read. Without a sandbox/FileBroker, lexical path checks cannot safely contain symlinks or dynamically produced file paths.
- Pi post-terminal hangs (#73). Pi children now default to
--no-extensions, with Host-onlyisolated/allowlist/inheritresource profiles. A validated final assistant answer plusagent_settled, oragent_endwithwillRetry: false, enters a bounded and revocable terminal- candidate grace period. Legacyagent_endevents without retry metadata remain clean-exit evidence only and are never sufficient for forced reap. Later lifecycle activity revokes the candidate, while a leaked handle is reaped as a successfulterminal-reap. Pi now uses the shared strict-NDJSON, process-group, abort/idle and SIGTERM→SIGKILL supervisor. Completion metadata is recorded in traces, and phase-timeout races are linearized consistently in imperative and event-kernel execution. - Terminal/process supervision closure. Ignored Pi metadata no longer cancels terminal grace, stdout/stderr decoding preserves UTF-8 across pipe chunk boundaries, malformed close tails cannot schedule signals after a run settles, and TERM/INT/HUP synchronously reap both agent and script process groups before preserving native Host signal exit semantics.
- Published Shared Context Tree path. The Pi adapter now resolves the
executing sibling entry (
src/index.tsin development,dist/index.jsin a packed install), so default extension isolation does not removectx_*tools. - Reproducible release tarballs. Release packaging canonicalizes pnpm's publish-ready dependency maps without reordering semantic conditional exports, proves repeat-pack SHA512 stability, and reuses one immutable tarball set for consumer smoke, npm publish, provenance and registry verification.
- Resolve-only fan-out coordination. Potential writers in one invocation are safely serialized before durable lease acquisition; parallel/map/race/ tournament work no longer self-times out while cross-process writers remain protected by persistent leases. Retry warnings are emitted only after a durable mutation intent actually existed.
- Pi CLI typed-argument parsing now uses bounded linear scanners for decimal
coercion and
key=valuetokenization, eliminating worst-case polynomial regular-expression backtracking on user-controlled invocation text. - Native sandbox policy construction is fail-closed behind a checked-in exact Host/OS/binary evidence cell. Evidence uses a strict versioned schema with complete named boolean checks, one no-follow file snapshot for hashing and parsing, an owner decision, and a process-local approval token. The factory also binds the independently observed live target and the canonical digest of the complete backend capabilities. The 0.2.1 native allowlist is empty.
- Resolve-only writers use cross-process leases, durable intents, one-shot
mutation permits, and explicit dirty-state reconciliation. A cancelled
non-cooperative writer's late success is recorded
dirty-unknown, root replacement is rechecked after asynchronous resolution, and subsequent writers remain blocked until deliberate reconciliation. - Persistent mutex and lease cleanup records now carry durable terminal release evidence. A cleanup fault cannot reverse a completed callback, let a live worker steal another worker's lock, or leave a same-PID worker/session blocked solely because the originating JavaScript isolate disappeared.
- README and launch-visual refresh: English and Simplified Chinese landing pages now share one concise, conversion-focused structure built around the 0.2 compiler/runtime story, with validated JSON and TypeScript DSL examples, five-host installation paths, runtime guarantees, and package topology. The hero and social-preview assets now visualize the Author → Verify → Compile → Run → Reuse pipeline and have matching localized artwork.
- S4 TypeScript DSL (
taskflow-dsl): compile-time.tf.tsrunes erase to Taskflow JSON via TypeScript AST (typescriptpackage, not ts-morph); CLInew/check/build/decompile; modularerase/kinds/*registry; parity tests (map +json+ templates). Hosts still run JSON only (no MCP auto-build of.tf.ts). - Horizon B engine kinds (
race,expand):PHASE_TYPESis 12. Imperative runtime + FlowIR 1:1;race= first-success wins (failed settles do not win); cooperative loser usage is aggregated; non-cooperative losers and parent abort are bounded by a cancellation grace (gate is woken on parent abort so the race cannot hang forever);expand= nested or graft-promote (def,expandMode,maxNodes) with template rewrite on graft. DSL runes + skills + website phase docs + examples.cancelLosers(default true) aborts losers via best-effortAbortSignalafter the first success. Event kernel still covers the original 10 kinds (excludesrace/expand); advanced features continue to force imperative fallback; nestedflow{def}/usere-checks kernel admission (fail-closed if child has race/expand or unsupported features). - S5 plan:
docs/internal/s5-kernel-default-on-plan.md— parity harness first, then feature gaps, then default ON + flagship recompute metric. - Claim-vs-impl alignment:
docs/internal/claim-vs-impl-0.2.0.mdledger; S4 RFCs toolchain corrected; architecture S4 ✅ / S5 next; skills + README phase/test/package counts; website homepage 12 phases / 5 hosts; reference page TypeScript DSL (en/zh). - Docs complete for Phase 2 surfaces: website FlowIR docs (
ir:<64-hex>,usedFallbackHash: false); new concepts Deterministic Replay (en/zh) + resume disambiguation; host MCP guides + Grok website +reference/commandslist all 12 tools includingtaskflow_replay; README en/zh Commands tables; skillsadvanced.mdtrace/replay vs recompute;AGENTS.mdexec/trace/replay + kernel flag; architecture RFC status table S0–S5 + internal FlowIR RFC superseded note. - Grok Build host. New
grok-taskflowdelivery package +taskflow-hostsgrokSubagentRunner(grok -p --output-format streaming-json). Plugin scaffold (.grok-plugin/plugin.json+.mcp.json+ skills), repo marketplace index (.grok-plugin/marketplace.json), docs (docs/grok-mcp.md, website en/zh guides). Install:grok plugin install … --trustor local bin for dogfood. - 0.2.0 Phase 2 / S0–S3 foundations (event-sourced kernel path):
flowir/compile.ts(compileTaskflowToFlowIR) — genuine Taskflow→canonical FlowIR compiler;compileTaskflowToIRnow content-addresses withhashFlowIR(ir:<64-hex>) and setsusedFallbackHash: false.exec/fold.ts— purefoldEvents(log) →per-phase snapshot (S1 differential building block).replay.ts— implementsreplayRun(log, overrides)(threshold / budget / model / args knobs; zero tokens; no import of runtime/driver).- S1 decision coverage: runtime emits
gate-score/gate-verdict,when-guard,cache-hit,tournament-winner,budget-hit, plus synthetic phase lifecycle for budget/dep skips. Fold differential tests pin fold ↔ RunState agreement. - S2 event kernel (strangler, default OFF, kernel-eligible kinds = PHASE_TYPES minus
race/expand):exec/step.ts+step-kinds.ts+exec/driver.tswhenRuntimeDeps.eventKernelorPI_TASKFLOW_EVENT_KERNEL=1. Imperative remains default. Hardening: budget enforcement +budget-hitevents; deps/join/optionalparity; gate eval fail-safe;steps.*.jsonpopulation; agent timeout; script stdout cap;flow{def}dynamic validation + nesting cap + recursion stack; feature fall-back for score/retry/expect/reflexion/onBlock/cross-run cache/shareContext. - S1 hard gate + import lint: fold(log) rebuilds phase statuses matching RunState after a captured run (kill-9 oracle);
replay-import-lintkeepsreplay.tsoff the runtime/driver/step import graph. - North-star slogan:
compiled · resumable · incremental · replayable-for-what-if(drops Qwik "not replayable" collision with deterministic replay). - S3 replay surface:
taskflow_replayMCP tool; piaction=replayand/tf replay <runId> [--threshold phase=n] [--budget-usd n]; golden trace fixture undertest/fixtures/.
- Budget wording now matches the runtime contract. User-facing docs, skills,
and FlowIR comments describe
budgetas an observed-usage stop-loss: after a threshold is observed, no new call starts, while calls already in flight may overshoot. Ordinary DAG layers and map/parallel/tournament fan-out use serial admission, limiting new-call overshoot to one call;racenecessarily admits its competing branches together, so its already-active branches may all contribute overshoot. Host limits are explicit: Codex rejectsmaxUSDbut acceptsmaxTokens; Grok rejects every budget because it reports no usage. - DSL erase closes remaining dynamic-field gaps.
mapcallback aliases must match explicitas, tournament task/branch templates contribute DAG edges, and templatedapproval.request/script.inputpreserve placeholders and dependencies. Invalid or malformedtsconfig.jsonnow failscheckinstead of being silently ignored; decompile topology preserves implicit final output. - TypeScript DSL decompile now orders dependencies before consumers. Valid Taskflow JSON may list phases in any order; generated rune bindings now use a stable topological order, so forward-reference gates/reducers rebuild instead of producing unusable source with a successful CLI exit.
- MCP cancellation is now real and end-to-end. The dependency-free stdio
transport dispatches requests concurrently, handles
notifications/cancelled, and propagates a per-requestAbortSignalthroughtaskflow_runinto the runtime and active host subprocess. A cancelled tool call returns JSON-RPC-32800instead of leaving hidden background work. Input disconnect aborts active requests and notifications; duplicate request ids abort the original controller instead of overwriting it. Completed host subprocesses remove their abort listeners, avoiding long-DAG listener leaks. Transport shutdown is grace-bounded and suppresses late writes; explicit cancellation also races non-cooperative handlers and observes any late rejection, so neither a hung promise nor an unhandled rejection can wedge the MCP process. Asynchronous stdioerrorevents (including outputEPIPE) use the same bounded teardown, abort active work, suppress late responses, and remove their transport listeners after settling. - Grok thinking overrides now work. Phase → agent → global thinking is
mapped to
grok --reasoning-effort(off→none). - Grok budgets no longer fail open. Grok 0.2.93 streaming JSON contains no
token/cost usage, so the Grok MCP adapter explicitly rejects flows declaring
budgetrather than silently reporting zero and ignoring the ceiling. The runtime capability check applies at every execution boundary, including inline object/string flows, saved flows, and nested/graftexpandfragments. - Script phases without
inputnow close stdin immediately. Commands that read until EOF (for examplecat) no longer wait for the phase timeout when no input payload was configured. - Codex thinking overrides now reach the host CLI. Phase → agent → global
thinking is mapped to Codex
model_reasoning_effort, including the supported aliases, instead of silently inheriting an unrelated user-level setting. - Thinking configuration is validated instead of silently ignored. Invalid values are rejected at the Taskflow boundary so a typo cannot fall back to a host's global reasoning configuration.
- Host children no longer inherit ambient authority. Codex subagents are
ephemeral, ignore parent user config/rules, and clear unrelated MCP servers;
OpenCode read-only phases use a default-deny policy covering custom/MCP tools;
Codex, OpenCode, and Grok receive filtered provider/runtime environments.
Operators can explicitly pass named task variables with
PI_TASKFLOW_CHILD_ENV_ALLOW. - Published surfaces are verified as consumers receive them. Packed
manifests strip source-only
developmentexports, declaration imports are typechecked, internal dependencies are exact, and the OpenCode tarball now includes its config/skill/assets scaffold. Publish reruns verify all nine registry artifacts after mutation, not only versions that existed before it. - MCP trace responses are bounded. Human and JSON trace views cap event counts and oversized strings; JSON reports total/returned/truncated instead of flooding the host context with an unbounded transcript.
- DSL output writes are contained, no-clobber, and atomic.
build,decompile, andnewreject symlink escapes, preserve existing files unless--forceis explicit, and commit fsynced same-directory temporary files atomically.--emit bothpreflights both destinations before writing either. - Grok read-only phases are kernel-enforced and defence-in-depth. They now
require
PI_TASKFLOW_GROK_READONLY_SANDBOX_PROFILEto name a custom profile extendingread-only, plus a known-good file-read allowlist, independent mutator/MCP deny rules, and disabled subagents.web_search/web_fetchare omitted from the Grok 0.2.93 allowlist because that CLI version can label them unmappable and restore the full toolset. A live executor E2E verifies a write attempt is blocked even when the workspace is under/tmp. - Grok mutating/default phases require a fail-closed custom sandbox. Built-in
profiles can warn and continue unsandboxed when kernel enforcement is
unavailable, which is unsafe with
--always-approve. Mutating phases now requirePI_TASKFLOW_GROK_MUTATING_SANDBOX_PROFILEto name a configured custom profile; built-in names are rejected.max_turns_reachedalso fails the phase instead of accepting a partial answer. - OpenCode subprocess permissions fail closed. Every OpenCode child now
uses
--pureso external plugins cannot bypass the tool permission policy. Mutating/default-capable phases are rejected unless the operator explicitly setsPI_TASKFLOW_OPENCODE_UNSAFE_AUTO=1; only then is--autoadded. - Release reruns no longer blindly trust an existing npm version. Before
skipping, the publish workflow verifies a trusted npm owner, SLSA provenance
from this repository/workflow/tag/commit, and exact locally-packed tarball
integrity. A preclaimed
name@versionnow fails the release. Every third-party action across CI, Pages, and publish/release workflows is pinned to the official major tag's full commit SHA; npm publish has onlycontents: readandid-token: write, while GitHub Release creation is isolated in a dependent job with onlycontents: write. - Claude permission handling no longer defaults to an unsandboxed permission
bypass. Host policy now uses explicit least-privilege execution modes and
fails closed when the requested tool capability cannot be represented
safely. Claude Code >=2.1.169
--safe-modedisables non-managed customizations,--toolsrestricts built-ins, only that same set is pre-approved, and disk settings/non-managed hooks are disabled (managed policy hooks may still run). Explicit lists remain narrow even after the unsafe opt-in; unknown tool names always fail closed. The Claude child also receives a filtered environment that retains platform/proxy/CA and supported provider settings while dropping unrelated application secrets. - Published artifacts are consumer-tested before npm is mutated. CI and
the tag workflow pack all nine packages with pnpm, install the exact local
tarballs into a clean npm project, reject leaked
workspace:*ranges, import every explicit public entry point, and exercise all five shipped bins. The same gate therefore protects first publication as well as release reruns.
cwdno longer accepts interpolation placeholders. A phase'scwdfield is a literal path / reserved workspace keyword (temp/dedicated/worktree), not an interpolated one — but the validator silently accepted values likecwd: "{args.workspace}", which would then resolve to a literal directory named{args.workspace}at run time (or, worse, be exploitable as a path-injection vector).validateTaskflow()now rejects anycwdvalue matching a{placeholder}pattern with a clear error pointing at the reserved keywords. (#65)
- Dependency sweep (no runtime-API changes — these are CI / dev / website
dependencies; the published packages' runtime surface is unchanged):
pnpm/action-setup4 → 6 (#57).- Batched the remaining Dependabot PRs that were stuck behind CI /
workflow-scope gates (#66): GitHub Actions
actions/checkoutv4 → v7,actions/upload-pages-artifactv3 → v5,actions/deploy-pagesv4 → v5; dev depstypescript^6 → ^7,@types/node^22 → ^26,typebox^1.3.3 → ^1.3.6,@biomejs/biome2.5.2 → 2.5.3; website depsfumadocs-core/ui16.10.7 → 16.11.1,fumadocs-mdx15.0.13 → 15.1.0. Local typecheck + the full 1160-test unit suite remain green.
- Deterministic-replay trace foundation. Every run may now record an
append-only event trace (
runs/<flow>/<runId>.trace.jsonl) capturing each subagent call's resolved input + full output and the runtime's own decisions (gate verdicts,unreplayablemarkers for context-sharing / innerflow/ context-file phases). This is the foundation for deterministic replay — re-evaluating a recorded run against changed decision knobs (gate thresholds, budget, model route) without calling the model, zero tokens offline — which lands in 0.2.0. The schema is already complete enough that 0.2.0 replay won't need a breaking migration.- New
trace.ts(TraceEvent,TraceSink, bufferedFileTraceSink, partial-line-tolerantreadTrace) andreplay.ts(ReplayDecisiontype contract) modules intaskflow-core; re-exported from the barrel. - New optional
RuntimeDeps.trace?: TraceSinkhook — fail-open (a missing or throwing sink never crashes a run) and host-agnostic (no host SDK in core; runs with no trace sink behave identically to before). Wired into all fourRuntimeDepsconstruction sites in the pi adapter and the MCP server. - New
traceaction //tf trace <runId> [--json]command (pi) andtaskflow_traceMCP tool: read-only inspection of a run's event timeline. Human form truncates subagent outputs (likepeek);--jsonreturns the complete machine-readable record. - Backfilled the MCP server with
taskflow_why_staleandtaskflow_recompute(dry-run only) — the MCP host previously lacked 5 analysis actions pi had. - Trace files are cleaned up alongside their runs by
cleanupTerminalRuns(no unbounded disk accumulation). - Extracted the pure
parseGateVerdict+ a decoupledoverBudgetintodeterministic.ts, so a futurereplay.tscan import them without dragging in the process-spawning runner. (Design came from a 3-reviewer cross- adversarial plan review: risk-reviewer + critic + reviewer → plan-arbiter, which scoped this to trace-only in 0.1.7 and deferred replay logic to 0.2.0.)
- New
- Release-prep fixes from a deep cross-adversarial release-readiness review
(scout → risk/security/quality reviewers → critic cross-exam → final-arbiter):
- Bumped the stale
@0.1.6plugin pins incodex-taskflow/plugin/.mcp.jsonandclaude-taskflow/plugin/.mcp.jsonto@0.1.7. Without this,codex plugin add taskflow@taskflow/claude plugin installwould install a server lackingtaskflow_trace,taskflow_why_stale,taskflow_recompute,taskflow_save, andtaskflow_search— the 0.1.7 features silently absent. (opencode-taskflow/plugin/opencode.jsonwas already at 0.1.7.) - Taught the
traceaction in the skills (the actions table + Pi/MCP surface in README) so agents can discover and invoke it — an engine feature the skill doesn't teach effectively doesn't exist. - Added trace decision-event tests (gate-verdict, unreplayable marker).
- Bumped the stale
- File loaders now report why a file failed, with the parse position —
instead of a merged "not found or unparseable" message. Four user-facing
loaders (
readDefineFile,readFlowFile/listFlows,tryReadRunFile, and the library sidecarreadMeta/readMetaNextTo) used to collapse two distinct failures — file missing and file malformed — into a singlenull/"… not found or unparseable"result. The underlyingJSON.parseSyntaxError(which carries the offending byte offset and, on Node ≥17, a line/column) was swallowed by the lenientsafeParseand never reached the user. A hand-authoreddefineFilewith a stray bare newline inside a string literal therefore reported "defineFile not found or unparseable" and sent authors chasing a phantom path/cwd problem. The loaders now return a discriminatedLoadResult<T>({ ok: true, value } | { ok: false, reason: "missing" | "unparseable", path, detail });detailcarries the original parse error (e.g.Bad control character in string literal in JSON at position 3979 (line 30 column 801)), surfaced via a shareddescribeLoadFailure(r, what)helper. New strictparseStrict(text, { allowFence })(ininterpolate.ts) preserves theSyntaxError; the lenientsafeParseis unchanged, so all ~25 LLM/subagent output paths keep their fail-open fence/balanced-bracket recovery.listFlowsnowconsole.warns on a corrupt saved flow instead of silently dropping it (sogetFlow(name)no longer reports "not found" for a file that clearly exists); newgetFlowDiagnosed/loadRunDiagnoseddistinguish corrupt-vs-missing for by-name/by-runId resolution. Breaking (pre-1.0):readDefineFile,readMeta, andreadMetaNextToreturnLoadResult<T>instead ofT | null. The opaquely-fail-open paths (index-rebuild scans,cache.tsfile hashing, path-traversal rejections) are intentionally unchanged. - The pi-taskflow "built-in agents upgrade" hint is now truly one-time. It
previously re-printed every session while
settings.jsonlacked ataskflowkey and the project had.pi/agents/*.md. A marker file (~/.pi/agent/.taskflow-upgrade-hint-shown) is now written atomically (wxflag) after the first print, so subsequent sessions skip it. Best-effort: an unwritable agent dir only means the hint may show once more; it never blocks session startup. - Gate verdict parsing hardened — a genuine BLOCK is no longer silently
downgraded to PASS (issue #54). Models routinely wrap decision tokens in
Markdown emphasis (
VERDICT: **BLOCK**,### WINNER: __3__,SCORE:0.8), which the bare-token regexes (`/VERDICT\s*[:=]\s*(…)/`, `/WINNER…(\d+)/`, `/SCORE…([01]…)/`) missed — the match fell through to the default verdict, so a genuine BLOCK was silently recorded as `pass` and a judge's actual pick silently reverted to variant 1. Three layered fixes: (1) a shared `markerRe()` factory in `scorers.ts` now emits emphasis-tolerant regexes for **all three** decision markers — `VERDICT_TOKEN_RE`, `SCORE_TOKEN_RE`, `WINNER_TOKEN_RE` — tolerating `*`/`_`/``/~runs on either side of the captured value (used byparseGateVerdict,parseJudgeOutput, andparseTournamentWinner); (2) **gate *model output* that cannot be parsed now fails closed (BLOCK)** instead of PASS — a gate that cannot reach a verdict cannot be trusted to pass, while *config* slips (unresolvedscore.target, malformedscorers) remain fail-open with a warning (they are authoring errors that degrade, not a judge that couldn't decide); tournament winner stays fail-open (variant 1 — never lose work, since the variants are already computed); (3) a free-text gate whose task omits aVERDICT:instruction now gets the exact format suffix **auto-appended**. For maximum robustness, preferoutput: "json"+expectenum ({ verdict: { enum: ["pass","block"] } }) which machine-validates the verdict. Regression tests added for every Markdown variant and the fail-closed default. **Breaking** (pre-1.0): a gate whose model output contains *no* parseable verdict (noVERDICT:marker and no JSON verdict object) now **blocks** the flow instead of silently passing. Previously such gates rubber-stamped PASS. Migration: any custom gatetaskthat relied on prose-only output should either emitVERDICT: PASS|BLOCK(auto-appended if omitted), adopt theoutput:"json"+expectenum contract, or be markedoptional: truewith a downstream fallback. Note that an *explicit* non-blocking JSON verdict (e.g.{"verdict":"No issues found"}) still resolves to PASS — only truly unparseable model output is affected. *Config* slips (unresolvedscore.target, malformedscorers`) remain fail-open with a warning.
- PostCSS bumped to 8.5.16 (GHSA-qx2m-qp2m-jg93 / CVE-2026-41305, medium).
PostCSS < 8.5.10 did not escape
</style>when stringifying CSS ASTs, an XSS vector when user-submitted CSS is parsed and re-embedded in HTML<style>tags.next@16.2.10(awebsite/transitive dep) pinned the vulnerablepostcss@8.4.31. A rootpnpm.overridesnow forcespostcss@^8.5.10workspace-wide, hoisting the single8.4.31resolution to8.5.16(the version@tailwindcss/postcssalready resolved). The website build (Next.js + Tailwind CSS pipeline) was verified unaffected.
-
Extracted the MCP server into its own
taskflow-mcp-corepackage (sixth package). The stdio JSON-RPC server +taskflow_*tool handlers + DAG SVG/outline renderer moved out oftaskflow-coreintotaskflow-mcp-core, so core is again purely the portable engine (DSL/runtime/cache/verify) and the MCP presentation layer is an independently-publishable unit. The host adapters (codex/claude/opencode) now depend ontaskflow-mcp-coreand import it viataskflow-mcp-core/server/taskflow-mcp-core/jsonrpc.pi-taskflowis unaffected (it never used the MCP server). -
De-duplicated the three host runners. The codex/claude/opencode runners each copy-pasted ~82 lines of identical process-handling boilerplate (spawn / idle watchdog / abort / signal-kill detection / stderr cap / post-exit classification), which had already caused one divergence bug. The shared block — plus
unknownAgentResultand a centralizedactiveChildrenset — is now a singlerunSubagentProcessintaskflow-core'srunner-core.ts, parameterized by a per-hostSubagentAccumulator+foldLine. Each host runner shrank to just its host-specific bits (argv, model-id rule, permission mapping, event parser): codex 366→217, claude 417→266, opencode 397→247 lines. Behavior is identical (1140/1140 tests pass); adding a new host can no longer drift the process/classify contract. -
Renamed the MCP server package from
taskflow-mcptotaskflow-mcp-coreat release time:taskflow-mcp(andtaskflow-mcp-server) had been squatted on npm by an unrelated package, so the host-neutral MCP server ships astaskflow-mcp-core. The directory (packages/taskflow-mcp-core), the host adapters dependency pins, and alltaskflow-mcp/server/taskflow-mcp/jsonrpcimports were updated accordingly. The adapters own bin names (codex-taskflow-mcp/claude-taskflow-mcp/opencode-taskflow-mcp) are unchanged.
-
Library Phase 1: search-before-author + reusable-flow assets. A new reusable-flow asset layer with sidecar
.meta.jsonmetadata (purpose,tags,phaseSignature,generality,agentUsage,reuseCount). Save reusable flows withaction=save(Pi) ortaskflow_save(MCP) and search them before authoring a new flow withaction=search/taskflow_search. Search combines structural similarity (agent→map→reducesignature + phase count) and CJK-aware keyword matching, and degrades gracefully to purely structural/keyword scoring when no embedder is configured (Phase 2). ThereusedFromSearchflag ontaskflow_run/action=runincrementsreuseCount, so high-quality reusable flows surface higher over time. Seedocs/rfc-library-reuse.mdfor the full design andskills-src/taskflow/library.mdfor the agent-facing workflow. -
OpenCode as a fourth host. New
opencode-taskflowpackage: an OpenCode subagent runner (opencode run --format json) plus anopencode.jsonMCP config scaffold, mirroring the Codex/Claude adapters. A flow's subagents can now execute as isolatedopencode runsessions, and taskflow is exposed to OpenCode via the sametaskflow_*MCP tools. Register withopencode mcp add taskflow -- npx -y -p opencode-taskflow opencode-taskflow-mcp-core(or anopencode.jsonmcpentry). Seedocs/opencode-mcp.md.- Read-only phases inject a deny-mutations permission policy via
OPENCODE_CONFIG_CONTENT(genuinely enforced); mutating phases run with--auto. - OpenCode model ids are
provider/model, so the runner uses a different drop rule than codex/claude (drops only{{placeholder}},:thinkingsuffixes, and multi-segment openrouter paths). - Verified end-to-end: a 2-phase flow with real
opencode runsubagents (data flows A→B) on a freeopencode/model.
- Read-only phases inject a deny-mutations permission policy via
-
Claude Code as a third host. New
claude-taskflowpackage: a Claude Code subagent runner (claude -p --output-format stream-json) plus a plug-and-play Claude Code plugin, mirroring the Codex adapter. The engine and DSL are unchanged — a flow's subagents can now execute as isolatedclaude -psessions, and taskflow is exposed to Claude Code users via the sametaskflow_*MCP tools (run/list/show/verify/compile/peek). Install:claude plugin marketplace add heggria/taskflow && claude plugin install claude-taskflow@taskflow. Seedocs/claude-mcp.md.- Read-only phases map to a
--allowedToolswhitelist; mutating phases run under--permission-mode bypassPermissions(the codex workspace-write analogue, no OS sandbox — documented). - The MCP server (JSON-RPC stdio transport,
taskflow_*tool schemas + handlers, DAG SVG/outline renderer) moved into host-neutraltaskflow-core/src/mcp/, parameterized by aSubagentRunner; the codex and claude adapters are now thin bindings. No behavior change for Codex. - Skills are single-sourced for all hosts (
skills-src/taskflow/with comma-list host blocks, e.g.<!-- host:codex,claude,opencode -->).
- Read-only phases map to a
-
defineFile: verify/compile/run a flow from a path on disk.action=run(Pi) andtaskflow_run/taskflow_verify/taskflow_compile(MCP) accept adefineFile(string) or{defineFile, name}in place of an inlinedefine. The engine resolves the path, reads it once, and substitutes it as the flow definition — so a flow can live in a.jsonfile (e.g.examples/review-changes.json) and be invoked by reference without pasting the JSON into the tool call. Pairs naturally with the JSONC support below. Seeskills-src/taskflow/core.md. -
JSONC comments and trailing commas in flow definition files. Flow definitions are hand-authored
.jsonfiles; authors can now annotate them with//and/* */comments and leave trailing commas (JSONC/JSON5 style). A new zero-dependencyparseJsonc()(intaskflow-core'sjsonc.ts) strips comments only outside string literals and tolerates trailing commas before}/], used byreadFlowFile()when loadingdefineFileflows and saved flows from the library.safeParse()for LLM output remains strict. Re-exported from thetaskflow-corebarrel asparseJsonc.
- The pi-taskflow "built-in agents upgrade" hint is now truly one-time. It
previously re-printed every session while
settings.jsonlacked ataskflowkey and the project had.pi/agents/*.md. A marker file (~/.pi/agent/.taskflow-upgrade-hint-shown) is now written atomically (wxflag) after the first print, so subsequent sessions skip it. Best-effort: an unwritable agent dir only means the hint may show once more; it never blocks session startup.
- Scoring gates (
scoreongatephases). Deterministic, composable, auditable quality checks: six pure scorers (exact-match,contains,regex,json-schema,length-range,code-compiles) run against a target string at zero tokens and combine viaall/any/weighted(+threshold). When the deterministic combination passes and the judge cannot veto it, the gate auto-passes with no LLM call (mirrors theevalfast-path). When it fails, an optionaljudge(LLM-as-judge) decides, or the gatetaskruns with the scorer report appended, or — with no fallback — the gate blocks explicitly. The structured result is the gate's.json({steps.<gate>.json.combined},.json.results), so downstream phases can route on quality, not just pass/fail. - Reflexion memory in loops (
reflexion: true). Each iteration after the first receives a structured failure summary of the prior one via the{reflexion}placeholder (auto-appended when absent, capped at 2000 chars):expect-contract diagnostics, the (sanitized) error, or the unmetuntil, plus a truncated output snippet. Body failures become feedback instead of termination — timeout/abort/over-budget still hard-stop, and exhaustingmaxIterationson a failure still fails the phase.PhaseState.loop.failuresrecords every failed iteration for audit. Default off = byte-for-byte the historical behavior. - Side-effect classification (
idempotent: false). Marks a phase with irreversible side effects (webhook POSTs, deploys, DB writes): transient provider errors are not auto-retried (explicitretry{}is still honored) and the result is never cached in any scope (within-run resume, cross-run,incremental). The phase state recordssideEffect: true(rendered as ⚡), and a re-execution on resume surfaces a warning. - Single-source skills. The pi and codex skill docs are now authored once
in
skills-src/taskflow/and compiled per host byscripts/build-skills.mjs(pnpm run build:skills); a drift guard (skills-build.test.ts) fails CI if a generated file is edited directly. Codex reaches feature parity with pi automatically.
- Scoring-gate hardening for LLM-generated dynamic sub-flows.
validateTaskflowrejectscode-compilesscorers (compiler execution — thenpx tscpath could resolve a repo-plantednode_modules/.bin/tsc) andregexscorers (catastrophic-backtracking ReDoS) insideflow{def}definitions produced at runtime — the same hardening class as the existingscript-phase block. Author-written flows keep both (a human reviewed them). code-compilesruns in an isolated temp directory.mkdtempSynccloses the predictable-temp-name symlink/TOCTOU race, and running the compiler with that dir as its cwd stopsnpxresolving a repo-plantedtsceven in author-written flows (defense-in-depth).- Judge-prompt injection guard. A scoring gate's judge embeds the model-produced target in a fenced evidence block; fences in the target are now neutralized so crafted output cannot close the block and inject instructions at prompt level.
- Reflexion prompt-injection surface reduced. Provider error noise is run
through
sanitizeErrorMessagebefore it is injected into the next iteration's prompt (HTML gateway pages no longer leak in verbatim). - ReDoS fixes in
safeParse. The fenced-block extractor and the stray-key diagnostic regexes are now linear-time (removed ambiguous adjacent whitespace quantifiers) — closes twojs/polynomial-redoscode-scanning alerts.
- Detached (background) runs load the host runner correctly. The host now
self-reports its runner module path via
import.meta.urlinstead of resolving a relative.tsspecifier thatrewriteRelativeImportExtensionsleft pointing at a non-existentdist/runner.ts— every detached phase previously failed with "No subagent runner injected" in the published build while dev checkouts worked. The detached runner now also fails fast (exits non-zero, persisting the real import error on a__detach__phase) instead of burying the cause under N no-runner stubs. - Loop
untilself-references no longer error.{steps.<thisId>.json.done}in a loop'suntil(the documented stop-condition pattern) was flagged as a self-reference bug; loop phases are now exempt. - Per-scorer field validation. Fields not applicable to a scorer's type
(e.g.
negateoncontains) are rejected instead of silently ignored. - Loop-only fields on non-loop phases warn.
until/maxIterations/convergenceon a non-loop phase now surface a warning instead of being silently dropped.
- The interpolation reference now documents
{reflexion},{loop.iteration},{loop.lastOutput},{loop.maxIterations}, and thescore.target/score.judge.taskinterpolation sites. examples/quality-pipeline.jsoncomposes all three new features (scoring gate → reflexion loop → non-idempotent notify).
- Dynamic sub-flows can no longer smuggle
scriptphases (RCE guard).validateTaskflowrejectsscriptphases in LLM-authored dynamic flow definitions (flowphases with an inline definition produced at runtime), closing the path where a subagent's output could inject arbitrary shell commands into the host. - Dependency audit clean.
npm audit fixbumps transitiveprotobufjsto 7.6.4 (GHSA schema-derived name shadowing, moderate) — 0 open alerts.
- Peek
--itemnow keys by positional label, not section order. A budget-skipped map item has no section in the merged output, so section-order indexing silently returned the WRONG item's content for every position after the gap.splitItemsnow parses each### [k/N]label and keys byk; a missing item returns "not found (budget-skipped items have no section)" with the available indices. /tf peekrejects non-numeric--item/--limitwith a usage message instead of passingNaNthrough ("Item NaN out of range").- Contract
enumcomparison is now key-order-insensitive for object literals (structuraldeepEqualinstead ofJSON.stringifyequality). - Tournament phases propagate
timedOutwhen all variants fail by phase-timeout (the custom all-failed return path missed the marker). - Codex MCP
taskflow_runpersists terminal run state even if the runtime throws (finally-wrapped saveRun), and bothtaskflow_run/taskflow_peekdescriptions cross-reference the runId so LLM callers chain them. verifyTaskflow's contract pass scans more ref sources —context,input,judge,withvalues, and array-formrun— closing false-negative gaps for{steps.X.json.field}typos.
- Peek — post-hoc inspection of intermediate phase outputs.
/tf peek <runId> [phaseId](pi) and thetaskflow_peekMCP tool (Codex) read one phase's output from a stored run: omitphaseIdfor a phase listing (status + output size),--jsonfor the parsed JSON,--item <n>for one section of a map/parallel fan-out,--limit <chars>to adjust truncation. Output is hard-truncated (default 4000 chars, ceiling 32000) and the operation is read-only + explicitly human/tool-invoked, so the context-isolation contract (only the final output enters the conversation) is preserved — peek is the debugging escape hatch for "phase 4 of 12 produced garbage" without re-running the whole flow. The Codex MCPtaskflow_runnow persists run state (throttled + terminal, same contract as the pi adapter) and reports the runId, so MCP runs are peekable too. - Per-phase
timeoutfor agent-running phases. Previously onlyscriptphases had a time cap; every other phase type could run unboundedly (the idle watchdog only catches silent stalls, not busy-but-never-finishing subagents).timeout(ms, >= 1000) now caps EACH subagent call of an agent/gate/reduce/map/parallel/loop/tournament phase: on expiry the subagent is aborted, the phase fails with atimedOut: truemarker (rendered as ⏱ in the pi TUI), and the failure is deterministic — never retried (neither explicitretrynor the transient fallback), so a capped call can't double-spend. Not valid on approval/flow phases (validation error). Script phases keep their existing child-process semantics and now also recordtimedOuton the phase state. - Output schema contracts (
expect). A JSON-emitting phase (agent/gate/reduce/loop withoutput: "json") can declare the shape its output must satisfy — a small JSON-Schema-like contract (type,properties,required,items,enum, nested). The runtime validates the parsed output the moment the subagent finishes; a violation fails the phase with per-path diagnostics (e.g.$.score: required key is missing) and is retryable under the phase's explicitretrypolicy — turning "phase completed but the shape is wrong and downstream silently mis-parses" into an immediate, precise failure at the source. Statically,validateTaskflowrejects malformed contracts andverifyTaskflowgains acontractpass that warns when a{steps.X.json.field}ref names a field absent from X's declared contract — catching ref typos before a single token is spent. Zero new dependencies (hand-rolled total validator intaskflow-core/src/contract.ts). - Script phase test coverage — validation, execution, security, and
robustness suites for the
scriptphase type (952 tests total).
- Each published package now ships its README (
scripts/copy-readme.mjs), fixing the empty npm package pages. - Docs sync: README/README.zh-CN/AGENTS/CONTRIBUTING/SECURITY refreshed for the script phase, brand/version drift fixed; CI actions bumped (checkout v7, setup-node v6) and dev-dependencies updated.
- Codex MCP
taskflow_compilerenders an inline SVG diagram. It now emits a hand-rendered SVG of the flow DAG so the Codex desktop app shows a real diagram instead of a bare<image content>placeholder; a layered text outline rides along as the caption/fallback for the CLI/TUI and vision-less models. Oversized graphs skip the image and fall back to text. Injection-safe: all rendered text is XML-escaped and the renderer is total (never throws on malformed input). Isolated to thecodex-taskflowadapter — core's Mermaidcompileartifact is unchanged (taskflow_run/taskflow_verifyreturn text only).
- Eval gates could silently auto-PASS on an unresolved ref or parse error. A
containscheck with a missing{steps.*}LHS, or any eval with a parse error, used to skip the LLM gate (evaluateConditionfails open withtrue). Both now fail-safe — a missing ref or unparseable eval falls through to the LLM gate instead of bypassing the safety check. - A
mapphase with a literal-arrayovercrashed the whole run withover.match is not a function. The map runtime assumedoverwas always a string interpolation ref (e.g.{steps.scan.json}) and called.match()on it. Two-layer fix:validateTaskflownow rejects a non-stringoverup front with an actionable message (emit the list from an upstream phase and reference its.json), anddirectRefguards against non-string input so the runtime fails a phase gracefully instead of throwing even if validation is bypassed. - Codex MCP tools threw or false-passed on malformed input.
taskflow_verifycrashed (phases is not iterable) on a missingphases;taskflow_compilefalse-passed an empty flow (✓ PASS) and crashed on a non-stringmap.overor a phase missing itsid. Both tools now validate first and return a structured✗ FAIL(still rendering a diagram for a renderable-but-invalid flow so it can be debugged), and the SVGtruncatehelper coerces non-string fields so the renderer can never throw. Hardening was extended to the full class of JSON-valid-but-malformed inputs: non-stringid/task/agent/when,nullor non-object phase elements, non-string gateevalentries, malformedcache/cache.fingerprint, and non-objectbranchesentries all return a structured validation error instead of throwing, and every diagram renderer (Mermaid + SVG + text outline) is total against a non-arrayphases. - Static verification ignored
reduce.fromedges.verify.tsbuilt its successor / terminal / connectivity graphs fromdependsOnonly, so a phase feeding a reduce solely viafromwas falsely flagged terminal/dead-end and the reduce falsely flagged unreachable — contradicting the runtime and the compile outline, which usedependenciesOf(dependsOn ∪ from). All graph helpers now usedependenciesOf. - Broken install contract on Node 22.0–22.18.
engines.nodewas>=22on all packages, but the locked Pi SDK requires>=22.19.0; withengine-strict=truethat meant a hard install failure once the Pi deps were reached. Bumped all fourengines.nodeto>=22.19.0to match the real floor.
- The Codex plugin pins the MCP package version it launches.
.mcp.jsonnow runsnpx -y -p codex-taskflow@<version>(was unpinned), so the installed plugin version binds the exact code executed. The publish workflow verifies the pin (andplugin.json's version) equals the release tag. - CI matured for the multi-host monorepo. Node
22/24test matrix, cross-platformpnpm install, abuild(dist-emit) job, the network-free Codex MCP e2e suites (stdio handshake + comprehensive rendering/injection), CodeQL, Dependabot, and least-privilege permissions. - Rebrand:
pi-taskflow→ taskflow. The project is now presented as a host-neutral, multi-host orchestration runtime (Pi and Codex), not a Pi-only extension. GitHub repo renamedheggria/pi-taskflow→heggria/taskflow; all docs, badges, hero/social images, and the 6 i18n READMEs updated. npm package names are unchanged (taskflow-core,pi-taskflow,codex-taskflow) and thepi-taskflowpackage keeps itspi-*keywords +pimanifest field, so Pi package indexing andpi install npm:pi-taskfloware unaffected. - MCP
serverInfonow reportstaskflow/0.1.3(waspi-taskflow/0.0.28).
- Codex plugin (
packages/codex-taskflow/plugin/) for zero-config, plug-and-play install:codex plugin marketplace add heggria/taskflowthencodex plugin add taskflow@taskflow. Ships a.codex-plugin/plugin.jsonmanifest, a.mcp.jsonthat launches the MCP server vianpx -y -p codex-taskflow@<version> codex-taskflow-mcp-core(no separate global install), and a routingSKILL.mdso Codex reaches for thetaskflow_*tools on multi-phase / fan-out work automatically. A repo-root.claude-plugin/marketplace.jsonmakes the plugin discoverable. tool_timeout_sec: 1800in the Codex plugin's.mcp.json. Codex applies a per-server MCP tool-call timeout; when unset the plugin inherited Codex's (short) default, so a long multi-phasetaskflow_run— which returns only after the whole DAG finishes — could be abandoned client-side while the run kept executing server-side. The plugin now ships a 30-minute default so large flows aren't cut off. Override per machine in~/.codex/config.tomlunder[mcp_servers.taskflow]if you need more or less.
- codex-taskflow MCP server failed every phase with
Model metadata for {{fast}} not found(same refactor-omission class as issue #3). The MCP server calleddiscoverAgents(cwd, "both")without the model-roles map, so the built-in agents'{{fast}}/{{strong}}/… placeholders were never resolved. Fixed: it now callsreadSubagentSettings()and passesmodelRolesthrough, exactly like the pi adapter. - codex-runner passed pi-provider model ids to
codex exec, which rejected them. Once the placeholders resolved, the resulting ids (openrouter/deepseek/...,anthropic/glm-5.2:xhigh) are pi's provider namespacing — Codex model ids are flat (gpt-5.5,claude-sonnet-4-6) and it errors withModel metadata for <id> not found. Fixed:runCodexAgentTasknow drops a model that still looks like a pi-provider path (contains/) or an unresolved{{placeholder}}, socodex execfalls back to its own configured default model. A user who sets a real Codex model id (no/) still gets it passed through. Verified end-to-end: a single-agent flow now runs to completion on Codex and returns the model's output.
- Foreground taskflow runs silently executed no phase (issue #3, broadest
impact — found during the regression sweep). The same default-runner change
that broke detached runs also broke
runFlow(every foreground run) and bothrecomputeTaskflowpaths inpi-taskflow/src/index.ts: they constructedRuntimeDepswithout arunTask, so every phase hit thenoRunnerInjectedstub. Pre-refactor this worked only because the engine's defaultrunTaskwasrunAgentTask(same package). Fixed by explicitly injectingpiSubagentRunner.runTaskat all three call sites. Added a structural regression test that scans the production source and fails if anyexecuteTaskflow/recomputeTaskflowdeps omitsrunTask. - Detached (background) runs crashed on launch (issue #3). The detached
runner specifier resolved to
dist/detached-runner.js.js(double.js) under taskflow-core's"./*"export rewrite, so the spawned child died at import withCannot find module. Now resolved with a suffix-less specifier (taskflow-core/detached-runner) →dist/detached-runner.js. The stale--experimental-strip-typesflag (the runner ships compiled) is dropped. - Detached runs could never execute any phase (issue #3, deeper). The
detached-runner called
executeTaskflowwith norunTask(see the default above), so every detached phase failed with "No subagent runner injected" even after the module loaded. Fixed: the host serializes arunnerModule/runnerExport(resolved from its own package, works under both workspaces and npm installs) into the detached context file, and the detached-runner dynamically imports it and injectsrunTask. - A crashed detached runner no longer leaves the run stuck at
runningforever (issue #3, secondary). The host now pipes stderr and attachesexit/errorhandlers that, when the child dies before reaching a terminal state, persiststatus: "failed"with the captured stderr recorded in a pollable synthetic phase (__detach__). Race-safe: guarded by pid + status so a genuine terminal state the runner persisted is never clobbered.
Monorepo split + first multi-host release. pi-taskflow is now three independently published packages built on a host-neutral engine, and the taskflow engine runs on both Pi and Codex.
taskflow-core— the host-neutral engine (DSL, runtime, cache, verify, FlowIR, shared context tree, agent discovery, persistence, theSubagentRunnercontract). Zero host-SDK dependency (depends only on typebox). Vendors the three small pi helpers it used (StringEnum,parseFrontmatter,getAgentDir) so it is fully standalone.codex-taskflow— run taskflow on OpenAI Codex: acodex exec-backed subagent runner, plus a dependency-free MCP server (codex-taskflow-mcp-core) that exposestaskflow_run/list/show/verify/compileto Codex users. Register withcodex mcp add taskflow -- codex-taskflow-mcp-core.- Host-neutral
SubagentRunnerseam — the engine drives any host via an injectedrunTask;piSubagentRunnerandcodexSubagentRunnerare the two implementations.
pi-taskflowis now the Pi adapter package (same published name — existingpi install npm:pi-taskflowusers are unaffected). It depends ontaskflow-core.- Repo restructured to npm workspaces under
packages/*. Each package now builds todist/(tsc→.js+.d.ts) and publishes the compiled output — required because Node refuses to type-strip.tsundernode_modules. Dev still runs the TypeScript sources directly (adevelopmentexport condition +--conditions=developmentresolvestaskflow-coretosrc, so a fresh clone runstypecheck/testwith no build step). CI builds and publishes the three packages in dependency order (core → pi → codex) on av*tag.
- All 864 tests pass across the three packages (713 core + 135 pi + 16 codex).
- See
RELEASE.mdfor the publish flow.
Granular-reuse release: incremental recompute goes from whole-flow to per-phase and per-item. v0.0.27 proved the recompute cost win; this release makes that win far larger and easier to opt into. Editing one phase now invalidates only that phase and its transitive dependents (a sibling keeps its cache hit), a
mapphase re-executes only the items that actually changed, and a singleincrementalflag flips a whole flow into cross-run reuse without annotating every phase.
- Per-phase structural sub-fingerprint (
v3:phasefp). The cache key now folds a per-phase fingerprint — the phase plus its transitivedependsOn ∪ fromclosure — instead of the whole-flowv2:flowdefhash. Editing phase B invalidates only B and its dependents; an independent sibling A keeps its hit.cacheKeysemits a 4-tier read ladder (v3:phasefpwrite →v2:flowdef→ bare flowdef → legacy, all read-only) so the upgrade is additive — no miss-storm for unchanged flows. Fail-open: any per-phase error degrades that phase to the whole-flow hash. Soundness fallback to whole-flow when per-phase invalidation can't be statically guaranteed (flow-widecontextSharing, anyshareContextphase in the closure,join: "any", or sub-flow inner phases). (extensions/flowir/phasefp.ts,test/cache-phasefp.test.ts— 11 tests.) - Per-item cross-run caching for
mapphases. When one of N items changes between runs, only that item re-executes (N−1 cache hits) while the whole-map fast path and every soundness fallback stay intact. Per-item keys omit the structural fingerprint (which hashes the wholeoversource) so changing one item no longer moves every key at once; they fold[phase.id, it.agent, model, it.task]+ the world-state tail, so task/agent/upstream/world changes still invalidate the right items. Disabled (whole-map only) under run-only/off scope,shareContext/flow-widecontextSharing, or inside a runtime-generated sub-flow. (test/cache-peritem.test.ts— 11 tests.) incrementalflag — flow-level (TaskflowSchema.incremental) and invocation-level (runtool arg). Defaults every phase toscope:"cross-run"so re-running a flow reuses unchanged phases across runs/sessions, without annotating each phase. The invocation arg wins over the flow field; per-phase cache settings and the cross-run-blocked types (gate/approval/loop/tournament) still take precedence; default remains the saferun-only(fresh each run). (resolveCacheScopeinextensions/index.ts,test/incremental-flag.test.ts.)- Reuse reporting. The end-of-run cache report and
/tf recomputenow show reused-vs-executed counts and a per-phase "Why" trace (the explainable- reactivity view:▲ rerun / ✂ cutoff / ✓ reused / ✗ failed, with← causedBy). Dollar figures are reported only for within-run reuse, where the prior usage is preserved; cross-run hits are counted but never attributed an invented saving. (summarizeReuse/RecomputeDecisioninextensions/runtime.ts,test/reuse-summary.test.ts.) - Tests: 804 → 846 (+42).
phaseFingerprintstrips more policy fields (cache,retry,concurrency,final): none changes a phase's subagent output, so a no-op config tweak no longer causes false cache invalidation.- README test count and feature line refreshed (804 → 846 across 46 files);
per-item map cachingadded to the headline capabilities.
Evidence release: the incremental-recompute cost win is now proven, not asserted. v0.0.25 made
/tf recomputetrustworthy and v0.0.26 made the dependency contract under it real — but the only cascade test re-ran every phase, so "rerun only what changed" had no regression proof. This release pins the two ways recompute actually saves money, closing the flagship's open acceptance criterion (the prerequisite for ever flipping recompute on by default).
- Flagship cost-win tests (
test/recompute.test.ts):- Partial cascade —
rerun < full. A diamond where one branch shares no edge with the changed seed proves the unrelated phase is reused (0 tokens), never re-run, and the rerun set is strictly smaller than the full flow. - Early-cutoff propagation. Re-seeding a phase whose output is unchanged cuts off its entire transitive downstream — only the seed spends a token, every descendant hits its cache. This is the "changed a file that didn't actually affect the result ⇒ near-zero rerun" guarantee.
- Partial cascade —
- Tests: 802 → 804 (+2).
- README test count and feature line refreshed (was stale at 702/34 files):
now 804 tests across 42 files, with
incremental recomputeandFlowIR compile seamlisted among the headline capabilities.
- Scope held deliberately. Two further H2 ideas — flipping
runto auto-recompute by default, and preciseir-changed/ map item-level reuse — are not in this release. The first changes every user'srunbehavior (a kernel-level, post-M5 decision); the latter two are scoped-out in the roadmap (§6) as later RFCs. Shipping the proof first keeps each step independently releasable.
Foundation release: the convergence roadmap's H1 lands — a real FlowIR compile seam (M1), a declared dependency plane (M2), and a backward-compatible cache-key migration. v0.0.25 made incremental recompute trustworthy; this release makes the contract underneath it real: the recompute frontier now reasons over observed ∪ declared dependencies, the flow definition compiles through a typed IR surface instead of an inlined hash, and folding the definition into the cache key no longer evicts every pre-existing cross-run entry.
- FlowIR compile seam (M1). New
extensions/flowir/{index,translate,meta}.tsexposescompileTaskflowToIR(def) → { ir, meta, hash, usedFallbackHash, warnings, errors }— a typed, never-throwing projection of a desugared flow into a content-addressed IR. The runtime now routesflowDefHashthrough this seam instead of inlining it.translateis currently a 1:1 stub projection (sousedFallbackHashistrueand the hash equals the vendoredflowDefHash); it becomes the genuine overstory compiler once that kernel is vendored, at which point the cache-key version advancesv2: → v3:. /tf ir <flow>command +irtool action. Renders the compiled IR plus its hash and any structuredCompileError[]— zero tokens, no LLM.- Declared dependency plane (M2).
compileTaskflowToIRsynthesizes per-phaseDeclaredDeps { reads, writes }from interpolation refs (task/over/when/until/eval/branches/with/context) anddependsOn, attaches them toir.meta.declaredDeps, and persists them toRunState./tf recomputenow computes its stale frontier over union(observed ∪ declared) rather than observed-only — a dependency that was declared but never interpolated at runtime is no longer missed. - Tests: 753 → 802 (+49) across new suites:
flowir.test.ts,flowir-declared.test.ts,stale-union.test.ts(incl. a 500-iteration property test proving the union frontier is never narrower than observed-only),recompute-union.test.ts,cache-migration.test.ts, pluse2e-flowir.mtsande2e-cache-migration.mts.
- Cache-key migration no longer evicts existing cross-run entries. Folding
flowdef:into the key previously invalidated every pre-existing cross-run cache entry on upgrade (a one-time miss-storm).cacheKeyis now versioned (v2:flowdef:) with a 3-tier lookup: new key → bareflowdef:key → legacy (no-flowdef) key. Old entries still hit for one release cycle; there is no write-through on a fallback hit (legacy entries age out naturally), and every tier still includesflow:${name}so two different flows can never collide. - Declared plane and recompute guard now see
loop.untilandgate.eval.collectRefsskippeduntil(loop convergence) andeval[](gate zero-token checks), so a dependency expressed only in those fields was absent from the declared plane and from thedryRun:falseunobserved-dependency guard. Both are now scanned. (Closes the two MEDIUM findings from the H1 risk review.)
- Backward compatible.
RunState.flowDefHashandRunState.declaredDepsare optional — pre-0.0.26 run states load unchanged. A compile/hash failure fails open:usedFallbackHashstays set, cross-run cache is disabled for that run, and the key degrades to a flow-scoped (collision-free) form. The one observable change on upgrade is a single re-execution of in-flight phases whose storedinputHashpredates thev2:prefix.
Correctness release: incremental recompute is now trustworthy.
/tf recomputeshipped in the prior line as a promising idea — force-rerun a seed, walk its stale frontier, let the cache cut off untouched downstreams. But the dependency graph it walked was a half-truth: reads observed only inside awhenguard or anevalgate were never recorded, a loop that read its own output deadlocked the scheduler, and a{previous.output}chain could be silently skipped — each one a path where "only rerun what changed" quietly reused stale upstream state and returned a wrong answer that looked incrementally correct. This release closes all of them: the observed readSet is now complete, the recompute order unions declared and observed edges, and real (dryRun:false) recomputation refuses to run when it cannot prove the frontier is sound. The headline feature finally earns its safety claim — the difference between looks incremental and provably incremental.
- Safety guard for real recomputation.
recomputeTaskflowwithdryRun:falsenow refuses to run flows whose dependencies cannot be fully observed through the captured readSet: Shared Context Tree (shareContext/contextSharing),flowphases,context:file pre-reads, and interpolation placeholders such as{previous.output},{args.X}, or{item.X}. This prevents silently reusing stale upstream state. - Regression tests in
test/recompute.test.ts:- observed-read edges still order recomputation even without an explicit
dependsOndeclaration; {previous.output}chains are rejected for real recomputation;recomputeTaskflowreturns a freshRunStateand does not mutate the caller's state.
- observed-read edges still order recomputation even without an explicit
- Loop self-read no longer deadlocks recompute. A loop whose
untilcondition references its own prior output (e.g.{steps.refine.output}) produced a self-edge in the observed-dependency graph, causingtopoLayersto schedule the phase with a permanently non-zero indegree.observedDeps()now filters self-references so scheduling remains sound. whencondition upstream reads are captured. Conditions are now evaluated insideexecutePhaseInnerwith the sameonReadhook used by the phase task, so upstream refs observed only in awhenguard are recorded inPhaseState.reads.- Gate
evalupstream reads are captured. The machine-checkevalbranch now receives the sharedonReadhook, and the resulting readSet is persisted when an eval-only gate skips the LLM call. - Recompute topo-order now unions declared and observed edges. Previously
the recompute order only respected declared
dependsOn, which could place a downstream phase before its observed-but-not-declared upstream refreshed and cause false early-cutoff. The scheduling graph now merges both edge sets. - Recompute no longer mutates the caller's RunState.
recomputeTaskflowclones the input state viastructuredClonebefore modifying it. - Help text accuracy.
/tfcommand and tool-action descriptions updated to match the newrecomputeand provenance behavior.
Feature release:
/tf compile— turn the declared DAG into a Mermaid diagram plus a verification overlay for 0 tokens. A picture of the plan, a structural audit of the plan, and a GitHub-pastable artifact — all from the same JSON.
compileaction for thetaskflowtool and the/tf compile <name>command. Renders the flow as a Mermaidflowchart, overlays verification issues onto the nodes (red = error, amber = warning, green border = final), and emits a markdown document suitable for READMEs / issues / PRs.- Distinct shapes for every phase kind: agent ▭, parallel/map/flow ⊐, reduce ▽,
gate ◇, approval ⏸, loop ↻, tournament ⬡. Guards become edge labels;
join: "any"becomes dotted edges. - Reuses the existing
verifyTaskflowgraph analysis, so every dead-end, unreachable node, gate-exhaustion, budget overflow, concurrency warning, and guard contradiction is painted directly on the diagram. - Zero runtime dependencies; the compiler is a pure function with no LLM calls.
- Tests: 670 → 702 (+32) in
test/compile.test.ts— structural assertions on the emitted Mermaid tokens (no third-party parser dependency; render- correctness is validated by shape/edge/class assertions).
- Id collisions no longer merge nodes. Two distinct phase ids that
sanitize to the same Mermaid token (e.g.
audit-eachandaudit_each) are now disambiguated with a_2suffix instead of collapsing into one node with an accidental self-loop. - Markdown-injection hardening. Free-form strings (flow name, description, verification messages) are neutralized before interpolation, so a multi-line / bracket-laden name can no longer break out of the H1 heading or spawn a second blockquote.
/tf compile <name>now schema-validates first, matching the tool action — a malformed saved flow yields a clean error instead of a half-rendered diagram. An optionallr/tdsuffix selects diagram direction.- Backslashes are now escaped inside Mermaid labels.
Feature release: the Shared Context Tree — an opt-in mechanism that gives subagents a horizontal blackboard and a vertical supervision tree, so fan-out items can reuse expensive context instead of re-reading it, and a node can delegate work at runtime and have its children report back. Validated with six real end-to-end runs (real
pi, real models) including a recursive org tree and a large 5-way audit that converges through a loop + gate.
- Shared Context Tree (opt-in). Set
shareContext: trueon a phase (orcontextSharing: trueat the flow level) to give its subagent four extra tools backed by a per-run, file-based blackboard:ctx_write(key, value)/ctx_read(key?)— a horizontal blackboard: a node publishes a finding; siblings/descendants reuse it (own > ancestors > completed-others on key conflict; a running sibling's half-written findings stay hidden). Stops fan-out items from re-reading the same files.ctx_report(summary, structured?)/ctx_spawn(assignments[])— a vertical supervision tree: a node reports up, and delegates child work at runtime; the runtime runs each child (isolated) after the node finishes and folds their reports into the phase output.- New module
extensions/context-store.tsreuses the run store's atomic-write- file-lock primitives (per-node findings files — no global lock contention).
- All bookkeeping is fail-open (it can never sink a phase); the blackboard is size-bounded (256 KB/value, 256 keys/node), depth-capped (5), and cleaned up with the run. Fully backward-compatible: flows that don't opt in are byte-for-byte unaffected.
ctx_spawnaccepts a sub-graph, not just flat tasks. An assignment is now either{task, agent?}or{subflow, defaultAgent?}wheresubflowis an inline Taskflow (a dependency-bearing DAG withmap/gate/reduce). The spawned subflow reuses the samevalidateTaskflow+verifyTaskflow+ nested-executeTaskflowmachinery asflow{def}; spawn-subflows andflow{def}share oneMAX_DYNAMIC_NESTINGcounter (adef:spawn-*_stackframe), and spawned child token/cost usage is folded into the parent phase for honest budget accounting. A bad subflow fails open with a diagnostic.- Tests: 608 → 670 (+62) across 33 files, incl.
context-store,context-tree,spawn-xor,spawn-subflow,spawn-subflow-nesting,workspace,workspace-isolation. - Workspace isolation (
cwdkeywords). A phase'scwdnow accepts three reserved keywords that make the runtime allocate an isolated working directory for the phase's subagent and tear it down afterwards:"temp"— an ephemeral dir under the OS tmpdir, removed when the phase ends."dedicated"— a persistent dir under the run state (runs/ws/<runId>/<phaseId>), kept for inspection and deterministic per phase so a resume reuses the same dir."worktree"— a realgit worktreeon a throwaway branch offHEAD, removed (git worktree remove --force+ branch delete) when the phase ends; for changes you want to diff / commit / discard in isolation.- New module
extensions/workspace.ts(zero deps:fs.mkdtemp+gitviachild_process). Fail-open: a failed allocation degrades to the base cwd (worktree→tempwhen not a git repo) and records awarningsdiagnostic — a phase never fails to run because of isolation. Security: the keywords are rejected at validation in LLM-authored sub-flows (flow{def}/ctx_spawnsubflow) so generated plans cannot allocate worktrees or temp dirs that mutate the repo. A literal path is passed through unchanged (fully backward-compatible).
map/parallelfan-out items that callctx_spawnwere silently orphaned. The post-run spawn-drain only covered single-agent/gate/reducephases (keyed on the base phase id), but fan-out items run with suffixed node ids (audit-0…audit-4) and were never drained — their queued children never ran (5 orphaned intents, 0 children, in a real e2e). Each fan-out item now drains its own node and runs + folds its spawned children (reports + usage), fail-open. Regression test added.- Workspace override no longer leaks across isolation boundaries (found by
the pre-release adversarial review).
runInlineSubflowand the gateonBlock:retryupstream re-execution both spread...depswithout clearing the parent's_cwdOverride, so a spawned subflow / re-run upstream dep could be force-pinned to the parent phase's isolated dir. Both now strip the override (a spawned subflow still inherits the parent's dir as its base cwd, consistent withflow{def}, but no longer ignores an inner phase's own cwd). The triplicatedeffCwdformula was extracted into oneresolveEffCwd()helper (the divergence was the root cause).runs/ws/dedicated-workspace dirs are now reclaimed by the terminal-run cleanup, andrmrf()gained a path-containment guard (defense-in-depth).
Dogfooding release. The
dogfood-fullself-audit taskflow (which itself exercises all 9 phase types + when/join/retry/budget/cache/eval/flow-def/ loop/tournament/approval) ran against the codebase and surfaced these fixes.
- Live auto-refresh for the
/tf runspanel. The run-history panel was a static snapshot taken when opened, so a background (detached) run's progress never updated while watching. It now polls run state on a 1s interval and re-renders only when a run's status/updatedAtactually changes — phase progress (includingmap/parallelsubProgresslike24/24) updates live. The user's selection follows the samerunIdacross refreshes, a green● livetag shows while any run is running, and the refresh timer is cleared on close (dispose()) andunref'd so it never keeps the event loop alive. Fully backward-compatible: without live hooks the panel renders statically as before.- 5 new tests (
test/runs-view.test.ts): refresh-on-change, no-render-when-unchanged, dispose-stops-timer, selection-follows-runId, back-compat-no-hooks.
- 5 new tests (
safeParsenow prefers ajson-tagged fence in multi-fence output. When an LLM phase emitted an evidence block (e.g.```typescript) before the```jsonpayload, the old single-match regex grabbed the first fence, failed to parse, and the balanced-bracket fallback was misled by braces in the prose —safeParsereturnedundefinedand any downstreammapphase failed with'over' did not resolve to an array. It now scans every fenced block and triesjson-tagged ones first, then untagged. (3 new multi-fence tests.)- Unresolved interpolation refs are surfaced as phase warnings.
interpolate()returnsmissing[](placeholders with no source), but the runtime discarded it on the main task path — so{args.typo}or a{steps.x.output}withoutdependsOnwas silently left intact in the dispatched task. Theinterpolate.tsdoc comment promised "a recorded warning" that no code produced. The runtime now logs[taskflow] phase X: unresolved refs ...and attaches the message toPhaseState.warnings(persisted in the run record, visible in/tf runs). Doc comment corrected to match.
- Per-step context pre-read in shorthand modes. Single, chain, and tasks shorthand steps now accept
context(file paths) andcontextLimit, desugared directly onto the generated phases. This eliminatesO(N²)file exploration without writing the full DSL. In paralleltasksmode all branches share the deduped union of step contexts; chain steps each carry their own context. A top-levelcontextin chain mode produces a warning (no unsupported flow-level default). Context-file changes automatically invalidate phase caches.
- Headless approval safety. Approval phases now auto-reject (not auto-approve) when running in detached/background/CI mode, preventing silent bypass of human gates.
- Step-reference validator accepts transitive ancestors. The step-reference checker previously raised false positives on valid DAGs where dependencies span multiple levels of ancestry. Ancestor transitive closure is now fully resolved.
- Background (detached) execution —
detach: true. Run a taskflow in a detached child process without blocking the current session. Passdetach: trueand get arunIdback immediately; the flow executes in the background, persisting state to the store. Status polled via/tf runsandresumeworks as normal.extensions/detached-runner.ts(new): lightweight child-process entry script — reads serialized context, callsexecuteTaskflow, persists terminal state.extensions/index.ts:detach: Booleanparameter on the taskflow tool + child-process spawn logic (records PID inRunState).extensions/store.ts:RunStategainspid?: number+detached?: booleanfields;isProcessAlive(pid)stale-PID helper.- Design: entry-point spawn wrapper — zero changes to the 1340-line
runtime.tscore, no new phase type, no DSL version bump, fully backward-compatible. - Approval phases auto-reject in background mode. Idle watchdog kills stalled children. Stale PID detection via signal-0 probe.
- 8 new tests (
test/detached.test.ts): process-alive, PID persistence, end-to-end detached, crash→failed, resume after failure, stale PID, backward compat.
approvalViewinitialization robustness: throws a clear error when the approval view module is unavailable, preventing silent failures in detached/background mode.
- Closed the SKILL coverage gap — the LLM can now author every shipped feature. A schema-vs-SKILL.md audit (
docs/internal/skill-coverage-audit.md, machine-checked + cross-adversarial reviewed) found several implemented + tested features that were undocumented in the LLM-facing skill, so the model never generated them. All ~46 user-facing schema fields are now documented across SKILL.md + configuration.md.- SKILL.md: phase-type table now lists all 9 types (added
loop,tournament) with a “details” column pointing each to its section; new Loop phases (until/maxIterations/convergence) and Tournament phases (variants/judge/mode/judgeAgent) sections;eval(zero-token machine gate) andonBlock: "retry"(self-healing rework loop) folded into the Gate section; cross-runcachepointer +optional+ staticbranchesnotes. - SKILL.md: new Operating a run section — run lifecycle (
running → completed/blocked/failed/paused), cache-aware resume, when to resume vs. re-run, budget-mid-run behavior, and run inspection. Clarified action semantics (definevsname, save scope/collision,verify/agentsactions). - configuration.md: new §2.1 Context pre-reading (
context/contextLimit— resolution order, per-file 8000-char cap, 200k total cap) and §8 Cross-run caching (cache.scope,ttl, fullfingerprintprefix table for git/glob/glob!/file/env). Fixed a stale “5 phase types” → 9 cross-file drift.
- SKILL.md: phase-type table now lists all 9 types (added
- Every documented JSON example validates against the live schema; all run-status/resume claims verified against the runtime (
blockedis terminal;paused/failedare resumable). 560 tests pass, zero regression.
- GitHub Packages publish is now best-effort (
continue-on-error) so an unscoped-package 404 there can never block the npm publish or the GitHub Release.
- Runtime dynamic sub-flows —
flow { def }. Aflowphase may now carry an inlinedef(mutually exclusive withuse) that is resolved at runtime — typically from an upstream phase's JSON output ("def": "{steps.plan.json}") — validated, verified, and executed as a nested sub-flow. This is the declarative answer to code-modefor/if: a planner decides at runtime what work to spawn, and every generated plan is structurally checked (cycles / dangling refs / duplicate ids / dead-ends) before it spends a token.- Accepts a full Taskflow
{name,phases}, a barephasesarray, or{phases:[...]}(markdown ```json fences tolerated). Pure data — noeval. - Iterative replanning: pair with
loopso round N's plan depends on round N-1's result (not a one-shot fan-out). - Fail-open: a malformed/invalid/unverifiable def never aborts the run — the phase resolves as a no-op with a
defErrordiagnostic and upstream output is preserved. An emptyphasesarray is a valid no-op. - New examples:
examples/dynamic-plan-execute.json,examples/iterative-replan.json.
- Accepts a full Taskflow
- Hardening for runtime-generated (untrusted) sub-flows, enforced only when content is LLM-authored:
- Breadth caps:
MAX_DYNAMIC_PHASES(100),MAX_DYNAMIC_CONCURRENCY(16, flow- and phase-level),MAX_DYNAMIC_MAP_ITEMS(200, fan-out truncated not blocked). cwdcontainment: a generated phase cannot escape the run directory.- Budget clamp: a generated def's budget is clamped to
min(child, parent)per dimension — it can only ever be tighter, never looser. - Nesting cap:
MAX_DYNAMIC_NESTING(5) bounds inline self-spawning depth. - Prototype-pollution defense: inline defs are deep-cloned and
__proto__/constructor/prototypeown-keys are stripped.
- Breadth caps:
- Authored/saved flows (
use) are unchanged and not subject to these dynamic caps.
- 25 new tests (
test/flow-def.test.ts); 560 total, zero regression. Design + two-round cross-adversarial review (engineering-risk / design-critic / architecture / security) recorded underdocs/internal/.
- 28 fixes from 3-round adversarial dogfooding across 11 files.
- store.ts: validateRunId path-traversal guard in saveRun, cleanupTerminalRuns race condition mtime guard, saveFlow file locking (prevents concurrent write loss), saveFlow unified sanitization via safeFlowDirName, SharedArrayBuffer hoisted to module scope, empty flow name rejection, conditional .pi/ creation hint.
- runner.ts: signal kill detection (killedBySignal), idle timeout excluded from transient error retry, message cap (500) with truncation notice, stderr cap (64KB) with truncation notice.
- runtime.ts: loop abort semantics (stop: "aborted"), failed phase interpolation (sensible placeholder instead of raw template), tournament judge budget/abort guard, retry factor asymmetry documentation.
- interpolate.ts: tokenizer escaped quote handling (character-by-character loop), graceful dig() trailing path segment resolution.
- index.ts: /tf save and /tf verify tab completion, JSON string define parsing in renderCall label, escaped quote handling in parseArgsString.
- agents.ts: YAML tools type validation (reject non-string/array), atomic writeFileAtomic in syncBuiltinAgentsToProject.
- cache.ts: 30s timeout on execFileSync git calls.
- verify.ts: budget maxUSD overflow detection.
- render.ts: consistent numerator/denominator in summarizeRun.
- runs-view.ts: timeAgo negative timestamp guard, blocked status removed from isResumable.
- Built-in agents configurable via
/tf init— customize model role, thinking level, and tools per agent. - Community PR support:
feat/configurable-builtin-agents(thanks @yolonir). - Multi-language READMEs: 简体中文, हिन्दी, Español, العربية, বাংলা, Português, Русский.
AGENTS.mdproject guide — agent pipeline rules, review routing, executor selection, escalation paths.- GitHub issue templates (bug report + feature request) and PR template.
- Social preview OG image for npm/ GitHub card.
- Internal docs reorganized under
docs/internal/for clean project root. - Run cleanup made configurable with
.pi/creation notification. - npm tarball slimmed — only essential files shipped.
- 10 previously uncovered critical code paths covered (runtime branches, interpolate edge cases, transient error heuristics, store concurrency).
- Total: 524 tests (was 394).
- Built-in agent auto-sync to project
.pi/agents/— first-class community collaboration. - Tool description updated:
taskflownow replacessubagentas the recommended delegation API.
- Multi-language READMEs completed with >2% native-speaker coverage (7 languages).
- Static DAG verification (
verify.ts) — dead-end detection, gate exhaustion, ref integrity, concurrency warnings, guard contradictions — all computed at 0 tokens before a single agent runs. onBlock: "retry"— retry upstream phases when a gate blocks, instead of halting the run.- Declarative eval gates — machine-checkable criteria that run before the LLM gate.
- Budget and idle-watchdog guards on
onBlock:retryloops + nested recursion depth cap.
loopphase — iterate a task until a condition, convergence, or cap.tournamentphase — best-of-N with a judge (or aggregate mode).- Cross-run memoization (
cache: { scope: "cross-run" }) with git/file/glob/env fingerprints, TTL, and LRU eviction. - Interactive
/tf initwith action menu, role-aware model pickers, diff preview, and atomic merge-write. - 18 built-in agents with 6 model roles (
{{fast}},{{strong}},{{thinker}},{{arbiter}},{{vision}},{{reasoner}}).
- P0 cache-key correctness after adversarial cross-review.
/tf initcompile error and custom model registry validation.- Multi-agent review must-fixes (F1 label parse, F5 missing modelRoles).
- Model role system with
/tf initinteractive setup. - Per-phase
model,thinking,toolsoverrides.
- Full control-flow & reliability layer:
whenguards,join: anyOR-joins,retrywith backoff,approvalhuman-in-the-loop,flowsub-flow composition,budgetcaps. - Idle watchdog (kills wedged subagents after 5 minutes of silence).
- Transient error auto-retry (rate-limit / 5xx / timeout).
- README rewritten as flagship landing page with hero flow diagram and competitive comparison.
- Live DAG render with timing, cost, and sub-task progress in the TUI.
approvalphase type (approve / reject / edit).- Cross-session resume with per-phase input-hash caching.
mapphase dynamic fan-out over JSON arrays.reducephase aggregation.gatephase withVERDICT: PASS / BLOCKparsing./tf:<name>command shortcuts for saved flows.
- 13 dogfooding fixes + 6 meta-bug hardening.
- Run state storage: per-flow subdirectories, index, file lock, TTL cleanup.
- Agent availability query command + unknown-agent runtime degradation.
- Stalled subagent kill and negative-timer freeze.
- Index concurrency lock + stale-lock atomic preemption + flowName path-escape hardening.
- 11 critical defects from adversarial review batch fix.
- Structural refactor of control flow and reliability features.
- Self-audit and repair loop.
- Shorthand modes (
task,tasks,chain) — same shape as the built-in subagent tool. /tf save,/tf list,/tf showcommands.
- Initial DSL:
agent,parallelphases. {args.X},{steps.ID.output},{previous.output}interpolation.- DAG validation: cycle detection, reference soundness.
- Inline flow execution via
taskflowtool. - Run state persistence for resume.
- Extension scaffolding: tool registration, command registration, agent discovery.
- Initial release. Declarative DAG orchestration for Pi subagents.