Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
"plugins": [
{
"name": "taskflow",
"description": "Multi-phase subagent orchestration for Codex: 16 taskflow_* MCP tools (run/runs/resume/version/list/show/verify/compile/peek/trace/replay/why_stale/recompute/reconcile_workspace/save/search) plus a routing skill. Background runs support status/wait/cancel; compile returns SVG + text outline. The server runs via npx (codex-taskflow).",
"description": "Multi-phase subagent orchestration for Codex: 17 taskflow_* MCP tools (run/runs/resume/version/list/show/verify/compile/lint/peek/trace/replay/why_stale/recompute/reconcile_workspace/save/search) plus a routing skill. Background runs support status/wait/cancel; compile returns SVG + text outline. The server runs via npx (codex-taskflow).",
"source": "./packages/codex-taskflow/plugin",
"category": "developer-tools",
"homepage": "https://github.com/heggria/taskflow"
},
{
"name": "claude-taskflow",
"description": "Multi-phase subagent orchestration for Claude Code: 16 taskflow_* MCP tools (run/runs/resume/version/list/show/verify/compile/peek/trace/replay/why_stale/recompute/reconcile_workspace/save/search) plus a routing skill. Background runs support status/wait/cancel; compile returns SVG + text outline. The server runs via npx (claude-taskflow).",
"description": "Multi-phase subagent orchestration for Claude Code: 17 taskflow_* MCP tools (run/runs/resume/version/list/show/verify/compile/lint/peek/trace/replay/why_stale/recompute/reconcile_workspace/save/search) plus a routing skill. Background runs support status/wait/cancel; compile returns SVG + text outline. The server runs via npx (claude-taskflow).",
"source": "./packages/claude-taskflow/plugin",
"category": "developer-tools",
"homepage": "https://github.com/heggria/taskflow"
Expand Down
2 changes: 1 addition & 1 deletion .grok-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"plugins": [
{
"name": "taskflow",
"description": "Multi-phase subagent orchestration for Grok Build: 16 taskflow_* MCP tools (run/runs/resume/version/list/show/verify/compile/peek/trace/replay/why_stale/recompute/reconcile_workspace/save/search) plus a routing skill. Background runs support status/wait/cancel; compile returns SVG + text outline. The server runs via npx (grok-taskflow).",
"description": "Multi-phase subagent orchestration for Grok Build: 17 taskflow_* MCP tools (run/runs/resume/version/list/show/verify/compile/lint/peek/trace/replay/why_stale/recompute/reconcile_workspace/save/search) plus a routing skill. Background runs support status/wait/cancel; compile returns SVG + text outline. The server runs via npx (grok-taskflow).",
"source": {
"type": "local",
"path": "./packages/grok-taskflow/plugin"
Expand Down
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,29 @@

All notable changes to taskflow are documented here. This project follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format.

## [0.2.4] β€” 2026-07-20

### Added

- **Event kernel concurrent DAG layers.** The event kernel now executes phases within a topological layer concurrently via `Promise.all` with 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. Both `runOneAgent` (step.ts) and `runAgentCall` (step-kinds.ts) implement the full retry curve with budget-aware admission.
- **Kernel context pre-read.** `phase.context` files are now read and prepended to the prompt as `<context file="...">` blocks on the event kernel path, matching the imperative runtime's `resolvePhaseContext` behavior.
- **Kernel per-phase literal cwd.** Literal string `phase.cwd` values (absolute or relative to the flow cwd) are now resolved and passed to `runTask` on 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 from `taskflow-core/verifiers`) statically analyzes `script` phase `run` commands for common shell mistakes: `grep` patterns starting with `-` without a `--` separator, unbalanced regex brackets/parens in `grep`/`sed`, and pipelines ending with a filter (`grep`/`awk`/`head`/`tail`/`wc`/`sort`) without `set -o pipefail` or `PIPESTATUS`. Auto-included by `compileTaskflow` (opt out with `lint: 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`/`.mjs` file is dynamically imported; default, `verifier`, and `verifiers` exports are collected. Fail-open: broken modules are skipped with a warning. Convention over configuration β€” no config file needed.
- **MCP `taskflow_lint` tool.** 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). Provide `name`, `define`, or `defineFile`.
- **Compile subgraph visualization.** Inline flow definitions (`phase.def`) now render as Mermaid `subgraph` blocks 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).

### Changed

- **MCP tool count is now 17.** `taskflow_lint` joins the existing 16 tools. Marketplace metadata, host adapter tool-roster tests, and skill discovery assertions are synchronized.
- **Kernel admission expanded.** `canUseEventKernel` now admits flows with: concurrent DAG layers, explicit `retry`, `context` pre-read, and literal per-phase `cwd`. Remaining imperative-only features: `score` gates, `expect` contracts, `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.

### Fixed

- **Gate parity normalization.** The kernel parity harness normalizes gate objects to strip `reason: undefined` vs absent-key serialization differences between the two engines.

## [0.2.3] β€” 2026-07-18

### Added
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pi-taskflow-monorepo",
"version": "0.2.3",
"version": "0.2.4",
"private": true,
"description": "Monorepo for taskflow-core, taskflow-mcp-core, taskflow-hosts, taskflow-dsl, pi-taskflow, codex-taskflow, claude-taskflow, opencode-taskflow, grok-taskflow, and the documentation website.",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion packages/claude-taskflow/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "claude-taskflow",
"version": "0.2.3",
"version": "0.2.4",
"description": "Run taskflow on Claude Code: a Claude subagent runner plus an MCP server (and a plug-and-play Claude Code plugin) that exposes the taskflow_* tools to Claude Code users.",
"keywords": [
"claude",
Expand Down
1 change: 1 addition & 0 deletions packages/claude-taskflow/plugin/skills/taskflow/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ For a non-trivial flow you'll iterate on, **write the definition to a file**
// 2. verify, iterate, run β€” all reference the SAME file by path:
{ "name": "taskflow_verify", "arguments": { "defineFile": "/tmp/audit.json" } } // zero tokens
{ "name": "taskflow_compile", "arguments": { "defineFile": "/tmp/audit.json" } } // diagram
{ "name": "taskflow_lint", "arguments": { "defineFile": "/tmp/audit.json" } } // script-lint + custom verifiers
{ "name": "taskflow_run", "arguments": { "defineFile": "/tmp/audit.json" } }
```

Expand Down
50 changes: 50 additions & 0 deletions packages/claude-taskflow/plugin/skills/taskflow/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,56 @@ overrides, ordinary resume re-runs the non-done phases.

---

## Pluggable verifiers β€” zero-token custom static checks

Beyond the built-in structural detectors (dead-ends, unreachable, gate-exhaustion,
budget-overflow, concurrency, ref-integrity, guard-contradictions, contracts),
Taskflow supports **pluggable verifiers**: pure functions that lint a flow's
declarations at compile time, before any model is spawned.

### Built-in: script-lint

`compileTaskflow` auto-includes the **script-lint** verifier (opt out with
`lint: false`). It catches common shell mistakes in `script` phase `run`
commands:

- `grep` pattern starting with `-` without a `--` separator (exit 2, false RED)
- Unbalanced `[` or `(` in `grep`/`sed` regex (exit 2)
- Pipeline ending with a filter (`grep`/`awk`/`head`/`tail`/`wc`/`sort`)
without `set -o pipefail` or `PIPESTATUS` (failing upstream masked)

### Custom verifiers (convention dir)

Drop a `.ts`/`.js`/`.mjs` file in `.pi/taskflows/verifiers/` (project) or
`~/.pi/taskflows/verifiers/` (user). Export a `TaskflowVerifier`:

```ts
export default {
name: "my-check",
verify(flow) {
// flow.phases, flow.budget, flow.name are available.
// Return VerifierIssue[]: { phaseId?, message, severity: "error"|"warning" }.
return [];
},
};
```

Project-scope verifiers shadow user-scope by `name`. Broken modules are
skipped with a warning (fail-open). Use `taskflow_lint` (MCP) or
`verifyTaskflow(flow, { verifiers })` (programmatic) to run them.

### MCP: `taskflow_lint`

```
taskflow_lint { "defineFile": "/tmp/flow.json" }
```

Runs built-in + discovered verifiers. Plugin issues are stamped
`category: "plugin"` with `source: <verifier-name>`. Structural issues
are covered by `taskflow_verify`; `taskflow_lint` reports only plugin findings.

---

## `taskflow_version` β€” build/host identity

`taskflow_version` reports the engine package version, the git commit the dist
Expand Down
6 changes: 3 additions & 3 deletions packages/claude-taskflow/test/mcp-server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ test("claude mcp: initialize returns the protocol version + serverInfo", async (
assert.equal(res.result.protocolVersion, "2025-06-18");
assert.ok(res.result.capabilities.tools, "advertises tools capability");
assert.equal(res.result.serverInfo.name, "taskflow-claude");
assert.equal(res.result.serverInfo.version, "0.2.3");
assert.equal(res.result.serverInfo.version, "0.2.4");
});

test("claude mcp: tools/list exposes the same taskflow tools as codex", async () => {
const [res] = await rpcRoundtrip([{ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} }]);
const names = res.result.tools.map((t: any) => t.name);
assert.deepEqual(
names.sort(),
["taskflow_compile", "taskflow_list", "taskflow_peek", "taskflow_recompute", "taskflow_reconcile_workspace", "taskflow_replay", "taskflow_resume", "taskflow_run", "taskflow_runs", "taskflow_save", "taskflow_search", "taskflow_show", "taskflow_trace", "taskflow_verify", "taskflow_version", "taskflow_why_stale"],
["taskflow_compile", "taskflow_lint", "taskflow_list", "taskflow_peek", "taskflow_recompute", "taskflow_reconcile_workspace", "taskflow_replay", "taskflow_resume", "taskflow_run", "taskflow_runs", "taskflow_save", "taskflow_search", "taskflow_show", "taskflow_trace", "taskflow_verify", "taskflow_version", "taskflow_why_stale"],
);
for (const t of res.result.tools) {
assert.equal(typeof t.description, "string");
Expand Down Expand Up @@ -83,6 +83,6 @@ test("claude mcp: makeToolHandlers exposes the tools", () => {
const tools = makeToolHandlers(process.cwd());
assert.deepEqual(
Object.keys(tools).sort(),
["taskflow_compile", "taskflow_list", "taskflow_peek", "taskflow_recompute", "taskflow_reconcile_workspace", "taskflow_replay", "taskflow_resume", "taskflow_run", "taskflow_runs", "taskflow_save", "taskflow_search", "taskflow_show", "taskflow_trace", "taskflow_verify", "taskflow_version", "taskflow_why_stale"],
["taskflow_compile", "taskflow_lint", "taskflow_list", "taskflow_peek", "taskflow_recompute", "taskflow_reconcile_workspace", "taskflow_replay", "taskflow_resume", "taskflow_run", "taskflow_runs", "taskflow_save", "taskflow_search", "taskflow_show", "taskflow_trace", "taskflow_verify", "taskflow_version", "taskflow_why_stale"],
);
});
2 changes: 1 addition & 1 deletion packages/codex-taskflow/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codex-taskflow",
"version": "0.2.3",
"version": "0.2.4",
"description": "Run taskflow on OpenAI Codex: a Codex subagent runner plus an MCP server (and a plug-and-play Codex plugin) that exposes the taskflow_* tools to Codex users.",
"keywords": [
"codex",
Expand Down
1 change: 1 addition & 0 deletions packages/codex-taskflow/plugin/skills/taskflow/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ For a non-trivial flow you'll iterate on, **write the definition to a file**
// 2. verify, iterate, run β€” all reference the SAME file by path:
{ "name": "taskflow_verify", "arguments": { "defineFile": "/tmp/audit.json" } } // zero tokens
{ "name": "taskflow_compile", "arguments": { "defineFile": "/tmp/audit.json" } } // diagram
{ "name": "taskflow_lint", "arguments": { "defineFile": "/tmp/audit.json" } } // script-lint + custom verifiers
{ "name": "taskflow_run", "arguments": { "defineFile": "/tmp/audit.json" } }
```

Expand Down
50 changes: 50 additions & 0 deletions packages/codex-taskflow/plugin/skills/taskflow/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,56 @@ overrides, ordinary resume re-runs the non-done phases.

---

## Pluggable verifiers β€” zero-token custom static checks

Beyond the built-in structural detectors (dead-ends, unreachable, gate-exhaustion,
budget-overflow, concurrency, ref-integrity, guard-contradictions, contracts),
Taskflow supports **pluggable verifiers**: pure functions that lint a flow's
declarations at compile time, before any model is spawned.

### Built-in: script-lint

`compileTaskflow` auto-includes the **script-lint** verifier (opt out with
`lint: false`). It catches common shell mistakes in `script` phase `run`
commands:

- `grep` pattern starting with `-` without a `--` separator (exit 2, false RED)
- Unbalanced `[` or `(` in `grep`/`sed` regex (exit 2)
- Pipeline ending with a filter (`grep`/`awk`/`head`/`tail`/`wc`/`sort`)
without `set -o pipefail` or `PIPESTATUS` (failing upstream masked)

### Custom verifiers (convention dir)

Drop a `.ts`/`.js`/`.mjs` file in `.pi/taskflows/verifiers/` (project) or
`~/.pi/taskflows/verifiers/` (user). Export a `TaskflowVerifier`:

```ts
export default {
name: "my-check",
verify(flow) {
// flow.phases, flow.budget, flow.name are available.
// Return VerifierIssue[]: { phaseId?, message, severity: "error"|"warning" }.
return [];
},
};
```

Project-scope verifiers shadow user-scope by `name`. Broken modules are
skipped with a warning (fail-open). Use `taskflow_lint` (MCP) or
`verifyTaskflow(flow, { verifiers })` (programmatic) to run them.

### MCP: `taskflow_lint`

```
taskflow_lint { "defineFile": "/tmp/flow.json" }
```

Runs built-in + discovered verifiers. Plugin issues are stamped
`category: "plugin"` with `source: <verifier-name>`. Structural issues
are covered by `taskflow_verify`; `taskflow_lint` reports only plugin findings.

---

## `taskflow_version` β€” build/host identity

`taskflow_version` reports the engine package version, the git commit the dist
Expand Down
4 changes: 2 additions & 2 deletions packages/codex-taskflow/test/e2e-mcp-comprehensive.mts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ send({ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "
const init = await waitFor(1, "initialize");
assert.equal(init.result.protocolVersion, "2025-06-18");
assert.equal(init.result.serverInfo.name, "taskflow-codex");
assert.equal(init.result.serverInfo.version, "0.2.3");
assert.equal(init.result.serverInfo.version, "0.2.4");
ok(`initialize β†’ ${JSON.stringify(init.result.serverInfo)}`);

// notification must NOT produce a response
Expand All @@ -88,7 +88,7 @@ send({ jsonrpc: "2.0", method: "notifications/initialized" });
send({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} });
const list = await waitFor(2, "tools/list");
const toolNames = list.result.tools.map((t: any) => t.name).sort();
assert.deepEqual(toolNames, ["taskflow_compile", "taskflow_list", "taskflow_peek", "taskflow_recompute", "taskflow_reconcile_workspace", "taskflow_replay", "taskflow_resume", "taskflow_run", "taskflow_runs", "taskflow_save", "taskflow_search", "taskflow_show", "taskflow_trace", "taskflow_verify", "taskflow_version", "taskflow_why_stale"]);
assert.deepEqual(toolNames, ["taskflow_compile", "taskflow_lint", "taskflow_list", "taskflow_peek", "taskflow_recompute", "taskflow_reconcile_workspace", "taskflow_replay", "taskflow_resume", "taskflow_run", "taskflow_runs", "taskflow_save", "taskflow_search", "taskflow_show", "taskflow_trace", "taskflow_verify", "taskflow_version", "taskflow_why_stale"]);
for (const t of list.result.tools) {
assert.equal(t.inputSchema.type, "object", `${t.name} has object schema`);
assert.equal(typeof t.description, "string");
Expand Down
6 changes: 3 additions & 3 deletions packages/codex-taskflow/test/mcp-server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,15 @@ test("mcp: initialize returns the protocol version + serverInfo codex expects",
assert.equal(res.result.protocolVersion, "2025-06-18");
assert.ok(res.result.capabilities.tools, "advertises tools capability");
assert.equal(res.result.serverInfo.name, "taskflow-codex");
assert.equal(res.result.serverInfo.version, "0.2.3");
assert.equal(res.result.serverInfo.version, "0.2.4");
});

test("mcp: tools/list exposes the taskflow tools with schemas", async () => {
const [res] = await rpcRoundtrip([{ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} }]);
const names = res.result.tools.map((t: any) => t.name);
assert.deepEqual(
names.sort(),
["taskflow_compile", "taskflow_list", "taskflow_peek", "taskflow_recompute", "taskflow_reconcile_workspace", "taskflow_replay", "taskflow_resume", "taskflow_run", "taskflow_runs", "taskflow_save", "taskflow_search", "taskflow_show", "taskflow_trace", "taskflow_verify", "taskflow_version", "taskflow_why_stale"],
["taskflow_compile", "taskflow_lint", "taskflow_list", "taskflow_peek", "taskflow_recompute", "taskflow_reconcile_workspace", "taskflow_replay", "taskflow_resume", "taskflow_run", "taskflow_runs", "taskflow_save", "taskflow_search", "taskflow_show", "taskflow_trace", "taskflow_verify", "taskflow_version", "taskflow_why_stale"],
);
for (const t of res.result.tools) {
assert.equal(typeof t.description, "string");
Expand Down Expand Up @@ -245,7 +245,7 @@ test("mcp: makeToolHandlers exposes the tools", () => {
const tools = makeToolHandlers(process.cwd());
assert.deepEqual(
Object.keys(tools).sort(),
["taskflow_compile", "taskflow_list", "taskflow_peek", "taskflow_recompute", "taskflow_reconcile_workspace", "taskflow_replay", "taskflow_resume", "taskflow_run", "taskflow_runs", "taskflow_save", "taskflow_search", "taskflow_show", "taskflow_trace", "taskflow_verify", "taskflow_version", "taskflow_why_stale"],
["taskflow_compile", "taskflow_lint", "taskflow_list", "taskflow_peek", "taskflow_recompute", "taskflow_reconcile_workspace", "taskflow_replay", "taskflow_resume", "taskflow_run", "taskflow_runs", "taskflow_save", "taskflow_search", "taskflow_show", "taskflow_trace", "taskflow_verify", "taskflow_version", "taskflow_why_stale"],
);
});

Expand Down
2 changes: 1 addition & 1 deletion packages/grok-taskflow/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "grok-taskflow",
"version": "0.2.3",
"version": "0.2.4",
"description": "Run taskflow on Grok Build: a Grok subagent runner plus an MCP server (and a plug-and-play Grok plugin) that exposes the taskflow_* tools to Grok Build users.",
"keywords": [
"grok",
Expand Down
1 change: 1 addition & 0 deletions packages/grok-taskflow/plugin/skills/taskflow/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ For a non-trivial flow you'll iterate on, **write the definition to a file**
// 2. verify, iterate, run β€” all reference the SAME file by path:
{ "name": "taskflow_verify", "arguments": { "defineFile": "/tmp/audit.json" } } // zero tokens
{ "name": "taskflow_compile", "arguments": { "defineFile": "/tmp/audit.json" } } // diagram
{ "name": "taskflow_lint", "arguments": { "defineFile": "/tmp/audit.json" } } // script-lint + custom verifiers
{ "name": "taskflow_run", "arguments": { "defineFile": "/tmp/audit.json" } }
```

Expand Down
Loading