Skip to content

Repository files navigation

pi-workflows

An experimental attempt to re-implement Claude Code's workflows feature on top of pi. Workflow scripts fan out agents through pi-subagents, enforce concurrency and output-token limits, and persist enough state for deterministic replay.

Unreleased. 0.1.0-rc.0 is packaging metadata for an unreleased hardening snapshot, not a supported or published release. The runtime milestones and automated build/package gates are complete and verified; pnpm check and the packed-package CI matrix pass. Do not publish or tag it until the minimum/latest released-host matrix and manual TUI QA are complete.

Release gates and compatibility targets

Surface Target Current verification state
Node.js >=22.19.0 Source and packed-package tests pass on Node 22.19.0 and latest Node 22 in CI
pi @earendil-works/pi-coding-agent 0.80.9 minimum and latest compatible release Minimum/latest side-by-side host runs are unverified
pi-subagents Same-process host protocol version: 1 GitHub fork adrianorocha-dev/pi-subagents#main at 1321b3d (containing the required host work from 6462f03); the minimum/latest released-host matrix is unverified
Build/package dist/index.js plus dist/runtime/worker-entry.js, packed production-only install Build, artifact validation, tarball install, and installed-package smoke tests pass in CI
Interactive behavior TUI, FleetView, cancellation, resume, narrow/wide terminals, theme changes Manual TUI gate unverified

pnpm check and the packed-package CI matrix pass. The remaining release gates are the minimum/latest released-host matrix and manual TUI QA.

Verified runtime milestones

The source suite verifies the completed remediations:

  • Nonterminal crash resume preserves the valid cached prefix and durable budget, including interrupted child runs.
  • Child workflow calls replay positionally and render nested phases, logs, and agents in journal-backed UI groups.
  • Metadata-validated steering is durably captured and invalidates the affected replay suffix.
  • Shutdown is bounded, settles/stops active work, terminalizes once, removes listeners, and releases runtime ownership.

pi-workflows discovers the host at Symbol.for("pi-subagents:host"). It deliberately has no production, peer, optional, or bundled dependency on @tintinweb/pi-subagents: pi packages have separate module roots, and a bundled copy would create a second manager. Development and contract tests use the fork directly through the github:adrianorocha-dev/pi-subagents#main dev dependency; the lockfile resolves it to an exact commit so CI tests the same host implementation.

Installation compatibility

Install the compatible host first, then Workflows, and restart pi.

While the host work is pending upstream, the supported distribution is the GitHub fork's main branch:

git clone https://github.com/adrianorocha-dev/pi-subagents.git
cd pi-subagents
git checkout main # currently 1321b3d
npm ci
pi install /absolute/path/to/pi-subagents

For local package testing only (not a release endorsement):

cd /absolute/path/to/pi-workflows
pnpm install --frozen-lockfile
pnpm build
pi install /absolute/path/to/pi-workflows

The production build and packed-install flow pass in CI. The commands above reproduce the package locally. After all remaining gates are cleared, a future published package can be installed with pi install npm:pi-workflows@<released-version>.

Host-version diagnostic

/workflows remains available for diagnostics. The Workflow LLM tool is registered only after a compatible root host is found. A missing host leaves the tool absent and reports:

No pi-subagents host was found. pi-workflows requires a compatible pi-subagents host at Symbol.for("pi-subagents:host"). Install github.com/adrianorocha-dev/pi-subagents commit 6462f03 or a future release containing it and exposing SubagentHostV1 (host version 1), then restart pi.

An old/incompatible host reports its discovered value:

Unsupported pi-subagents host version <found>; pi-workflows requires host version 1. Install github.com/adrianorocha-dev/pi-subagents commit 6462f03 or a future release containing it and exposing SubagentHostV1 (host version 1), then restart pi.

There is no fallback runner and no private pi-subagents/dist/* import.

Upgrading and migration

Upgrade/install pi-subagents first, restart pi, and confirm /workflows can see host version 1 before testing Workflows. A pre-host or host-version-0 setup is not migrated to a private runner; it stays tool-disabled. Journal format version 1 is self-contained, so retain the complete workflow-<runId> directory when moving a project. Unsupported future host and journal versions fail explicitly rather than being guessed. Reload/restart after adding saved workflows because discovery is snapshot at session start.

Workflow invocation

The Workflow tool accepts:

Field Type Meaning
script string Inline workflow source
scriptPath string JavaScript file resolved from session cwd; a leading @ is accepted
name string Saved workflow from the session-start registry snapshot
args JSON value Cloned/frozen value exposed as global args
resumeFromRunId string Prior run with a terminal or interrupted valid journal, used for positional replay
budget positive integer Hard aggregate output-token ceiling

Source precedence is scriptPath > script > name. With only resumeFromRunId, the validated prior run's persisted workflow.js is used. Both terminal journals and valid interrupted nonterminal journals can be resumed. TUI and RPC starts request confirmation; print and JSON modes are non-interactive.

Every script starts with a pure metadata export followed by an async body:

export const meta = {
  name: "review",
  description: "Review files and verify findings",
  whenToUse: "Before merging a substantial change",
  phases: [
    { title: "Review", detail: "Independent reviewers" },
    { title: "Verify", detail: "Adversarial verification" },
  ],
}

phase("Review")
const values = await pipeline(
  args.files,
  (file) => agent(`Review ${file}`, { label: `review:${file}`, phase: "Review" }),
  (review, file) => review === null
    ? null
    : agent(`Verify this review of ${file}:\n${review}`, { phase: "Verify" }),
)
return values.filter(Boolean)

meta.name and meta.description are required. whenToUse and phases are optional. Metadata must be a pure object literal so discovery and permission UI never execute the body. Scripts are JavaScript, not TypeScript, and the final value must be JSON-serializable.

Script API

agent(prompt, options?)

Returns Promise<string | object | null>. A terminal agent failure, stop, skip, or missing structured result returns null. Invalid policy, exhausted budget, and workflow/runtime errors throw and may be caught in the script.

Option Type Meaning
label string Progress label; otherwise derived from the prompt
phase string Explicit group; use inside concurrent stages to avoid a shared phase-cursor race
schema JSON Schema object Injects StructuredOutput and returns its validated object
agentType string pi-subagents registry type; omitted uses embedded general-purpose
model string Fuzzy host model override, subject to model-scope policy
thinking offmax Thinking override, clamped by the host
maxTurns positive integer Per-agent turn ceiling
isolation "worktree" Fresh git worktree
cwd string Host-validated working directory

Agent-type frontmatter remains authoritative. Workflow requests use the host's external queue, suppress individual nudges and parent bookkeeping, carry workflow metadata, and exclude pi-workflows from managed children. The source contract suite verifies this host binding and metadata-validated steering capture against the required host contract.

Combinators and globals

  • parallel(thunks) runs thunks concurrently as a barrier, preserves order, and maps a thrown thunk to null.
  • pipeline(items, ...stages) advances each item independently without a stage barrier. A failed stage maps that item to null and skips its remaining stages. Stages receive (previous, originalItem, index).
  • phase(title) changes the current progress phase for later non-concurrent calls.
  • log(message) appends a narrator event to journal and UI. Log deliberate sampling or dropped coverage.
  • args is the invocation's cloned, frozen JSON value.
  • budget is { total, spent(), remaining() }; without a ceiling total is null and remaining is Infinity.
  • workflow(nameOrRef, args?) invokes one saved child name or { scriptPath }. Children share the parent's budget, concurrency, agent count, and abort signal; grandchildren are rejected. Child calls participate in positional replay, and their phases, logs, and agents appear in nested journal/UI groups.

Guard budget loops with both a nullable-total check and a finite iteration bound:

for (let round = 0; budget.total !== null && budget.remaining() > 4_000 && round < 5; round += 1) {
  await agent(`Round ${round}`, { phase: "Review" })
}

The runtime rejects more than 1,000 lifetime agent calls or more than 4,096 items in one combinator. Date.now(), Math.random(), and argumentless new Date() throw to protect replay determinism.

Authoring examples

Pattern File
Non-barrier pipeline examples/pipeline-smoke.js
Barrier fan-out examples/barrier-smoke.js
Budget-aware loop examples/budget-loop.js
JSON Schema result examples/structured-output.js
Resume-safe deterministic calls examples/resume-safe.js
Parent/child composition examples/child-parent.js and examples/child-review.js
Review/verify pipeline examples/review-changes.js

Run a file by asking pi to call Workflow with { "scriptPath": "examples/pipeline-smoke.js" }, or copy it to a saved-workflow root.

Saved workflows, security, and trust

Discovery happens once at session_start, with first match winning:

  1. <project>/.pi/workflows/<name>.js
  2. <project>/.agents/workflows/<name>.js
  3. $PI_CODING_AGENT_DIR/workflows/<name>.js

Project roots are ignored unless trusted. meta.name must match the filename. Canonical duplicate names, symlinks/root escapes, non-regular or changing files, malformed source, and metadata mismatches are diagnosed and ignored.

Pi packages execute with the user's full OS permissions, so review this package before installation. Workflow script bodies run in a worker-backed VM without process, filesystem, network, timers, eval, Function, require, or dynamic import. The worker is terminated on timeout/cancellation and has a memory limit. This is capability reduction, not an OS sandbox. Spawned agents retain the tools permitted by their type; project workflows and project agent definitions are trusted content. Use worktree isolation when agents may edit in parallel.

Journal and resume

Each run is stored under .pi/output/workflow-<runId>/ with workflow.js, run.json, journal.jsonl, per-agent JSONL transcripts, and hashed sidecars for values over 256 KiB. Replay re-executes from the top and matches calls positionally by prompt/options hash. It returns the longest eligible cached prefix in recorded completion order and runs the first mismatched suffix live. Logical output includes replayed usage; physical output is newly spent usage.

resumeFromRunId supports terminal or interrupted valid journals. An interrupted journal must be valid and inactive. Nonterminal crash resume, including interrupted child workflows and durable budget carry-forward, is covered by a real subprocess-kill test. When an active run is selected, approval stops and settles it before replay planning; denial leaves it running. Metadata-validated steering is journaled before completion and makes that invocation and its suffix cache-ineligible.

Settings

Global settings are in $PI_CODING_AGENT_DIR/workflows.json (normally ~/.pi/agent/workflows.json). Trusted projects may override fields in <project>/.pi/workflows.json.

Field Default Meaning
enabled true Allow a compatible root session to register Workflow
maxConcurrent min(16, max(1, cores - 2)) Per-run active-agent ceiling
globalMaxConcurrent null Optional session-wide ceiling
timeoutMinutes 60 Positive whole-minute wall-clock limit
retentionDays 14 Age limit for terminal, unreferenced run directories

Malformed files warn and fall back per field/file. Project settings override global settings. Writes are atomic and project writes are refused when untrusted. TUI users can use /workflowsSettings.

Events and RPC

Public event channels and payloads:

Channel Payload
workflows:started { runId, name, scriptPath }
workflows:phase { runId, title }
workflows:agent { runId, callKey, phase, status, agentId }
workflows:log { runId, message }
workflows:completed { runId, status: "completed", returnValue, tokens, agentCount }
workflows:failed { runId, status, returnValue, tokens, agentCount, error }

tokens contains input, output, cacheRead, cacheWrite, logicalOutput, and physicalOutput. Existing subagents:* events receive workflow metadata from the managed host request.

RPC protocol version 1 uses replies on <channel>:reply:<requestId>:

Request channel Request after requestId Success data
workflows:rpc:ping none { version: 1, methods: ["ping", "run", "status", "stop"] }
workflows:rpc:run Workflow fields { runId }
workflows:rpc:status { runId } Full RunSnapshot
workflows:rpc:stop { runId } { runId, stopped }

Replies are { success: true, data } or { success: false, error }. Handlers bind only in the root session.

TUI, print, JSON, and RPC behavior

  • TUI: returns a background handle and one follow-up completion; /workflows manages runs, saved scripts, journals, cancellation, settings, and nested child workflow groups. Automated UI contracts pass; the manual TUI gate remains unverified.
  • Print (pi -p): waits for terminal completion and returns one terminal result before exit.
  • JSON: waits and emits one inline tool result inside pi's JSONL stream; Workflows must not print human text to stdout, preserving valid JSONL.
  • RPC: starts in the background after correlated UI confirmation and uses request-scoped replies.

Automated source contracts cover these mode branches with fakes, and packed-package mode tests pass in CI. Real minimum/latest host runs remain unverified until their explicit matrix executes.

Troubleshooting: journal first

  1. Open .pi/output/workflow-<runId>/journal.jsonl; inspect run_end, then the last agent/log/phase event. Cached values are journal truth too.
  2. Inspect run.json for status, source hash, resume source, and terminal metadata.
  3. Inspect the relevant per-agent JSONL transcript for provider/tool failures.
  4. If Workflow is absent, run /workflows, follow the exact host diagnostic, and restart pi.
  5. If a saved name is absent, verify project trust, filename/meta.name, diagnostics, and session snapshot timing.
  6. For replay divergence, compare source, args-derived prompts, options, steering, and budget observations at the first diverged call. A steered invocation intentionally invalidates itself and the following suffix.
  7. For timeout/cancellation, inspect the terminal journal event. Shutdown stops active agents, waits for settlement within a bounded grace period, terminalizes runs exactly once, and cleans up listeners and runtime ownership.

Development and honest release verification

Source-only checks (permitted without producing artifacts):

pnpm install --frozen-lockfile
pnpm check

pnpm check runs the real-host contract against the forked @tintinweb/pi-subagents dev dependency pinned by the lockfile. During local host development, PI_SUBAGENTS_REPO=/absolute/path/to/pi-subagents pnpm test:real-host overrides that dependency with a checkout. Neither path creates a production dependency. Use pnpm update @tintinweb/pi-subagents after pushing a new fork commit to refresh the lockfile, and run the harness separately against any proposed compatibility target.

The CI-verified build and package flow can be reproduced locally with:

pnpm build
pnpm check:built
pnpm pack
PI_WORKFLOWS_PACKAGE_ROOT=/absolute/path/to/clean/node_modules/pi-workflows pnpm test:package

CI runs frozen-lockfile source checks plus a packed production-only install on Node 22.19.0 and latest Node 22. The installed-package harness covers TUI/RPC/print/JSON mode contracts and verifies that the built worker graph has no Effect dependency and remains at or below 256 KiB. These gates passed in CI run 29601027330. Manual TUI QA and the minimum/latest released-host matrix remain unverified; until those gates pass, this snapshot must not be published or described as release-ready.

About

An experimental attempt to re-implement Claude Code's workflows feature on top of pi

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages