add persisted pi workflow extension

This commit is contained in:
jb
2026-08-25 16:07:44 -04:00
parent bfb07282d6
commit c5c3316e49
46 changed files with 20642 additions and 94 deletions
@@ -0,0 +1,19 @@
---
status: accepted
---
# Build pi-workflow around persisted policy gates
`pi-workflow` will be a new extension rather than an activation or rename of `_workflows`. It will expose one command-style tool backed by atomic revisioned snapshots, Markdown Agent Profiles, deterministic eligibility followed by planner selection, a mandatory feature pipeline, and restricted feature or read-only analysis scripts. Every feature run receives a distinct exclusive worktree. Concurrency defaults to 8 under an immutable process-wide maximum of 32. Non-trivial UI work requires at least three structurally distinct prototypes and explicit user acceptance. Before implementation, the user chooses a fresh plan-seeded session, compaction of the current session that prefers `smart_compact` and falls back to normal Pi compaction when unavailable, or unchanged current context. Review uses a fresh session and excludes every Agent Profile that mutated the reviewed diff.
This keeps the caller interface small while making approval, prototype, recovery, and reviewer-independence rules enforceable in one workflow engine. We rejected unrestricted custom graphs because they could bypass policy, a general registry architecture because it adds speculative extension points, and event-sourced persistence because revisioned snapshots already provide the required recovery and audit data with less machinery.
## Consequences
- Child agents inherit the existing nono sandbox; the restricted script process remains defense in depth for model-authored JavaScript.
- Project Agent Profiles and project configuration are loaded only for trusted projects.
- Potentially mutating interrupted Assignments require explicit recovery and are never replayed automatically.
- A versioned `plan.md` is the implementation handoff source for fresh-session and compacted-context transitions. Durable Gate-bound intents and callbacks keep implementation blocked until session replacement or compaction completes; normal Pi compaction is permitted only after the host confirms `smart_compact` is inactive, while an actual execution failure remains visible.
- Fresh-session replacement rotates the run lease to the new active parent session inside `withSession`; historical parent sessions may inspect but cannot mutate the run.
- The extension creates one exclusive worktree per feature run on `feature/<feature-name>`, where the validated feature name describes the feature without a numeric or run-ID suffix. Any existing branch rejects a new start and, when persisted state maps it to a Workflow Run, directs the caller to continue through `inspect` or `decide`; otherwise it directs the caller to choose another descriptive name. The extension does not merge, commit, reset, or remove the worktree automatically in the first version.
- `_workflows` remains inactive until migration or removal is approved separately.
+68
View File
@@ -0,0 +1,68 @@
# pi-workflow
`pi-workflow` plans and implements feature work through persisted human Gates, isolated Git worktrees, focused verification, and independent review.
## Start a feature
Ask the assistant to use `pi_workflow` with:
- `action: "start"`
- `workflow: { mode: "feature" }` (or omit `workflow`)
- A descriptive lowercase kebab-case `featureName`
- The feature request
- A unique idempotency key
The extension creates `feature/<feature-name>` in an exclusive worktree. Existing branch names are rejected rather than silently renamed.
The managed flow is:
1. Discover repository facts and ask unresolved Questions.
2. Design the implementation and explicitly approve the plan and UI-impact classification.
3. For non-trivial UI, review three browser-validated candidates and accept one revision or request refinement.
4. Choose a fresh plan-seeded session, compacted current session, or unchanged current context.
5. Implement with guarded file ownership.
6. Remove prototype-only behavior.
7. Run plan-approved verification commands and focused tests.
8. Complete independent specification and standards review.
The assistant must inspect the current run before each state-changing call and obtain the user's explicit Gate Decision. It must not infer approval.
## Inspect and continue
Use `/workflow` for the focused-card dashboard. It lists runs attached to the current session and shows persisted status, progress, Gate, plan, report, branch, and worktree information.
Use:
```text
/workflow continue <run-id>
```
only when the dashboard or `pi_workflow` reports a pending fresh-session handoff or normal-compaction continuation.
## Script modes
A feature run may include one restricted read-only script at a closed insertion point:
- `before-design`
- `after-design`
- `before-review`
An analysis run uses `action: "start"` with `workflow: { mode: "analysis-script", source, args? }` and does not create a worktree or feature Gates. Scripts expose only `stage`, `assign`, `parallel`, and immutable `args`. Their Assignment tools are the intersection of the configured analysis allowlist, the selected Markdown profile, and active parent tools.
## Recovery
Interrupted work opens a Recovery Gate with retry, inspect, and abort choices. Completed Assignments and Decisions remain persisted. Potentially mutating interrupted Assignments are preserved and are not replayed automatically; an explicit retry creates newly fenced work where policy permits it.
## Artifacts
Run state and bounded artifacts are stored under:
```text
~/.pi/agent/pi-workflows/runs/<run-id>/
```
Important files include immutable plan revisions, Assignment transcripts, retained prototype reports and screenshots, `result.json`, and the terminal Markdown report. Completed worktrees remain available for manual inspection and integration.
## Reload after exposure
Repository-managed extension links are controlled through `config/pi-links.json`. Run `/reload` after the managed link is synchronized so Pi discovers `pi-workflow`.
+141
View File
@@ -0,0 +1,141 @@
# pi-workflow dashboard mocks
These are static TUI direction mocks for `/workflow`. They are not implementation screenshots.
## A. Split workspace
Best for comparing Stage progress and Assignment detail without navigation.
```text
pi-workflow 118 cols
Runs feature/search-page · waiting
> search-page waiting plan-approval · revision 12
auth-cleanup running Worktree .worktrees/pi-workflow-search-page-r1
cache-audit done Branch feature/search-page
Plan artifacts/plan-11-a31f.md
Stages Report pending
✓ Discover
✓ Design Open Gate · Plan Approval
· Prototype UI impact: non-trivial
· Implement Review the plan and UI evidence before deciding.
· Verify
· Review [a] approve [r] revise [o] open plan
↑↓ run tab pane enter action esc close
```
Narrow terminal:
```text
pi-workflow · search-page
waiting · plan-approval · r12
✓ Discover ✓ Design · Prototype
· Implement · Verify · Review
Plan Approval
UI impact: non-trivial
[o] plan [enter] actions
```
## B. Focused run cards
Minimal chrome. The list stays compact; selecting a run opens one vertically ordered detail card.
```text
pi-workflow
> search-page WAITING Plan Approval r12
auth-cleanup RUNNING Implement r31
cache-audit DONE report available r18
────────────────────────────────────────────────────────────
search-page
feature/search-page
Progress Discover ✓ Design ✓ Prototype · Implement ·
Verify · Review ·
Gate Plan Approval
non-trivial UI · 3 prototype candidates required
Artifacts plan-11-a31f.md
Worktree .worktrees/pi-workflow-search-page-r1
[enter] actions [o] artifact [esc] close
```
Action sheet:
```text
search-page · Plan Approval
> Inspect plan
Record approval
Request revision
Copy worktree path
Close
```
Prototype Gate:
```text
search-page · Prototype Acceptance
1 Sidebar navigation /prototype?variant=1
mobile ✓ desktop ✓ browser evidence retained
2 Command palette /prototype?variant=2
mobile ✓ desktop ✓ browser evidence retained
3 Inline workspace /prototype?variant=3
mobile ✓ desktop ✓ browser evidence retained
> Open candidate 1
Accept candidate 1
Request refinement
Abort run
```
Narrow terminal uses the same vertical card and hides paths until `o`.
## C. Event timeline
Best for recovery and auditability. The primary view is an immutable history; actions belong to the current event.
```text
pi-workflow · search-page WAITING
12:04 ✓ Discover 2 assignments
12:09 ✓ Questions decision recorded
12:15 ✓ Design plan revision 11
12:16 ● Plan Approval waiting for Decision
Current
UI impact non-trivial
Plan plan-11-a31f.md
Worktree feature/search-page
[enter] current actions [p] prototypes [r] report [esc]
```
Run list:
```text
Recent workflow events
> search-page Plan Approval 2m
auth-cleanup Implement 8m
cache-audit Completed 31m
```
Narrow terminals preserve the same timeline and omit timestamps first.
## Direction criteria
The implementation should:
- Start dark-mode friendly through Pi theme tokens.
- Keep the default view compact and reveal paths, evidence, and transcripts on demand.
- Show only valid actions for the current persisted Gate or continuation intent.
- Keep run status, revision, branch, worktree, plan, report, and prototype evidence discoverable.
- Remain usable from narrow terminals through 4K-width terminals without continuous animation.
+679
View File
@@ -0,0 +1,679 @@
# pi-workflow Design
**Status:** Accepted
**Date:** 24AUG2026
## Purpose
`pi-workflow` coordinates feature planning and implementation through selected Markdown-defined agents, persisted human gates, isolated Pi child sessions, and mandatory independent review.
The extension is based on `extensions/_workflows`, but it is a new extension rather than a rename. The existing extension remains an inactive reference until `pi-workflow` is verified and explicitly exposed.
## Goals
- Load bundled Agent Profiles and trusted project overrides from `.pi/agents/*.md`.
- Select agents automatically using deterministic eligibility followed by planner selection with recorded rationale.
- Pause after discovery for unresolved questions and after design for user approval.
- Require at least three prototype candidates and explicit user acceptance for non-trivial UI or UX work.
- Prevent an implementation agent from reviewing its own work.
- Support configurable concurrency with a user-controlled safety ceiling.
- Retain a restricted JavaScript DSL for custom feature and read-only analysis workflows.
- Run every feature in one dedicated Git worktree.
- Let the user choose a fresh plan-seeded session, smart compaction with normal-compaction fallback, or unchanged current context before implementation.
- Recover safely after process interruption without replaying potentially mutating work automatically.
## Non-goals
- Replacing the outer nono sandbox or granting capabilities beyond it.
- Allowing child agents to ask the user directly.
- Supporting arbitrary workflow graphs that can bypass required gates.
- Automatically merging a completed worktree into its source branch.
- Adding a general plugin framework for custom stages, policies, or persistence backends.
- Supporting global Agent Profiles in the first version.
## Language
**Agent Profile:** A versioned Markdown definition containing an agent's purpose, instructions, model preferences, tools, roles, and Eligibility Criteria.
**Eligibility Criteria:** Machine-readable conditions that determine whether an Agent Profile may be considered for an Assignment.
**Assignment:** One stage-specific task executed by one Agent Profile in a fresh child session.
**Workflow Run:** The persisted execution of one feature or analysis request.
**Stage:** A named unit of workflow progress controlled by the engine.
**Gate:** A persisted point where the Workflow Run waits for a valid Decision.
**Decision:** A revision-checked user or policy response that satisfies or rejects a Gate.
**Prototype Candidate:** One throwaway design option produced for user comparison.
**Review Assignment:** A fresh-session Assignment whose Agent Profile differs from every implementation Assignment it reviews.
**Implementation Context Choice:** The user's persisted selection of a fresh plan-seeded session, compacted current session, or unchanged current session before implementation starts. The compacted strategy prefers `smart_compact` and falls back to normal Pi compaction only when that tool is unavailable.
## External Interface
The extension exposes one state-changing tool and one workflow command.
```ts
type PiWorkflowInput =
| {
action: "start";
request: string;
featureName?: string;
workflow?:
| { mode: "feature" }
| {
mode: "feature-script";
insertionPoint: "before-design" | "after-design" | "before-review";
source: string;
args?: string;
}
| { mode: "analysis-script"; source: string; args?: string };
concurrency?: number;
background?: boolean;
idempotencyKey: string;
}
| {
action: "decide";
runId: string;
gateId: string;
expectedRevision: number;
decision: GateDecision;
idempotencyKey: string;
}
| { action: "inspect"; runId: string }
| {
action: "abort";
runId: string;
expectedRevision: number;
idempotencyKey: string;
};
```
Gate Decisions use closed, gate-specific schemas:
```ts
type GateDecision =
| {
kind: "answer-questions";
answers: Record<string, string>;
}
| {
kind: "approve-plan";
approved: true;
acceptedUiImpact: "none" | "minor" | "non-trivial";
comment?: string;
}
| {
kind: "revise-plan";
feedback: string;
}
| {
kind: "accept-prototype";
candidateId: string;
candidateRevision: number;
browserReviewed: true;
feedback?: string;
}
| {
kind: "refine-prototypes";
feedback: string;
}
| {
kind: "select-implementation-context";
strategy: "fresh-session" | "smart-compact" | "current-session";
}
| {
kind: "report-context-preparation";
preparationId: string;
planRevision: number;
method: "smart_compact";
outcome: "unavailable" | "succeeded" | "no-op";
}
| {
kind: "report-context-preparation";
preparationId: string;
planRevision: number;
method: "smart_compact";
outcome: "failed";
error: string;
}
| {
kind: "recover";
resolution: "retry" | "inspect" | "abort";
};
```
Feature and feature-script starts require `featureName` as a concise lowercase kebab-case name that describes what the feature does, such as `searchable-workflow-dashboard`. Analysis starts omit it. The host validates the name before persisting launch intent.
The `pi_workflow` tool calls a single internal `execute(command)` interface. `/workflow` opens the dashboard and may accept a run ID for direct inspection. `/workflow continue <run-id>` performs a pending fresh-session handoff or normal-compaction fallback after the run is safely waiting; it cannot approve plans, select prototypes, or make other Decisions.
When a run reaches a Gate, the tool returns a structured waiting view. The parent agent asks the user through its normal interaction tools, then calls `pi_workflow` with `action: "decide"`. Child agents never receive `ask_user`.
Background runs return a run ID immediately. When they reach a Gate or settle, the extension sends one custom follow-up message with a stable delivery key to the active parent session.
After `start` creates the run lease, every subsequent state-changing parent operation is authenticated against its active parent-session ID. Session IDs retained in `parentSessionIds` are historical discovery links and permit inspection only.
## Agent Profiles
### Discovery and precedence
Profiles are loaded in this order:
1. Bundled profiles from `extensions/pi-workflow/agents/*.md`.
2. Project profiles from the nearest `.pi/agents/*.md`, only when `ctx.isProjectTrusted()` is true.
Files are read in lexical order. The first duplicate within one source wins and emits a diagnostic. A project profile replaces a bundled profile with the same normalized name. Provenance and a content digest are retained. Regular files and symlinks are accepted only when their resolved paths remain inside the selected profile root.
Profile contents are pinned into the Workflow Run before selection. Editing a Markdown file does not change an active run.
### Markdown schema
```md
---
name: frontend-prototyper
description: Produces structurally distinct browser-reviewable UI prototypes.
roles: [prototype]
model: anthropic/claude-opus-4-6
effort: high
tools: [read, grep, find, ls, edit, write]
selection:
stages: [prototype]
anyTags: [frontend, ui, ux]
allTags: []
pathGlobs: ["src/**/*.svelte", "src/**/*.tsx"]
keywords: [dashboard, page, component, responsive]
---
Agent instructions go here.
```
Required fields:
- `name`: lowercase kebab case, unique after precedence is applied.
- `description`: concise purpose used by selection and the planner.
- `roles`: one or more of `discover`, `design`, `prototype`, `implement`, `verify`, or `review`.
- `selection`: structured Eligibility Criteria with at least one stage; additional filters may be empty for a deliberate fallback profile.
- Markdown body: non-empty agent instructions.
Optional fields:
- `model`: bare model ID or `provider/model-id`.
- `provider`: accepted only with a bare `model` field.
- `effort`: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max`.
- `tools`: CSV or YAML string array.
Unknown fields produce diagnostics and are ignored. Malformed files are skipped independently so one profile cannot break the catalog.
### Eligibility and planner selection
The engine normalizes request and repository evidence into facts:
```ts
interface WorkflowFacts {
stage: StageKind;
taskKind: "feature" | "analysis";
requestTerms: string[];
candidatePaths: string[];
languages: string[];
tags: string[];
riskTags: string[];
}
```
Eligibility is deterministic:
- The profile must support the requested role and stage.
- Every `allTags` entry must be present.
- At least one `anyTags` entry must match when the list is non-empty.
- At least one `pathGlobs` entry must match when the list is non-empty.
- At least one normalized `keywords` entry must match when the list is non-empty.
Pre-discovery selection uses only facts available without a model call: workflow mode, requested stage, normalized request terms, repository language markers, and paths named by the request. Criteria that require unavailable discovery facts do not match. A bundled general discovery profile provides an explicit fallback.
The engine selects the initial discovery profile deterministically from eligible profiles using specificity and profile name as the stable tie-breaker. Empty and ambiguous selections fail clearly. After discovery, the selected planner receives only eligible profiles and chooses Assignments with a structured rationale. The host validates every choice and persists the eligible set, rejected profiles with reasons, selected profiles, and rationale.
## Configuration
Configuration sources are:
1. Bundled defaults.
2. `~/.pi/agent/pi-workflow.json`.
3. Trusted `.pi/pi-workflow.json`.
4. Per-run invocation values.
Invocation values may change requested concurrency but never the safety ceiling. A project may lower the global ceiling but cannot raise it.
```json
{
"concurrency": {
"default": 8,
"ceiling": 32
},
"maxAssignmentsPerRun": 128,
"maxReviewCycles": 3,
"analysisTools": ["read", "grep", "find", "ls"],
"worktrees": {
"root": ".worktrees"
}
}
```
`32` is an immutable process-wide maximum, not merely a default. Global and trusted project configuration may lower it but cannot raise it. The scheduler uses one extension-wide semaphore so concurrent Workflow Runs cannot exceed the effective process ceiling in aggregate. Each run also has its requested or configured limit.
The first-version analysis and review tool sets are audited built-in allowlists. Configuration may remove tools but cannot add arbitrary extension tools by name.
Effective per-run concurrency is:
```text
min(requested or configured default, global ceiling, trusted project ceiling, 32)
```
Invalid, zero, non-integral, or out-of-range values fail before the run starts.
## Workflow State
```ts
type RunStatus =
| "running"
| "waiting"
| "completed"
| "failed"
| "aborted";
type AssignmentStatus =
| "queued"
| "running"
| "succeeded"
| "failed"
| "interrupted";
interface WorkflowRun {
runId: string;
revision: number;
mode: "feature" | "analysis";
status: RunStatus;
request: string;
featureName?: string;
parentSessionIds: string[];
planArtifact?: PlanArtifact;
implementationContext?: ImplementationContextState;
lease?: RunLease;
workspace: WorkflowWorkspace;
effectiveConfig: EffectiveWorkflowConfig;
pinnedProfiles: PinnedAgentProfile[];
facts: WorkflowFacts;
stages: Stage[];
gates: Gate[];
decisions: Decision[];
assignments: Assignment[];
prototypeCandidates: PrototypeCandidate[];
deliveries: DeliveryRecord[];
uiImpact?: UIImpact;
error?: WorkflowError;
}
```
Only the workflow engine may transition Stage, Gate, Assignment, context-preparation, or run status. Each accepted state-changing command increments `revision` and atomically replaces the persisted snapshot. Read-only inspection does not increment the revision. `ImplementationContextState` persists the Gate ID, plan revision, preparation ID, selected strategy, observed method/outcome, active parent-session ID, and any fresh-session or normal-compaction intent.
## Built-in Feature Pipeline
```text
Initialize
-> Discover
-> Questions Gate, when unresolved questions exist
-> Design
-> Plan Approval Gate
-> Prototype, only for non-trivial UI impact
-> Prototype Acceptance Gate
-> Implementation Context Gate
-> Implement
-> Verify
-> Review
-> Remediate -> Verify -> Review, when changes are requested
-> Complete
```
### 1. Initialize
- Resolve effective configuration, validate the feature name, and validate the repository without mutation.
- Load, validate, and pin Agent Profiles from the source checkout.
- Verify that eligible discovery, design, implementation, verification, and independent review profiles exist before mutating the repository.
- For feature mode, verify that the configured worktree root is ignored by Git and that the source checkout is clean.
- Persist a run snapshot with workspace state `creating` before invoking Git.
- Create a distinct worktree transactionally, then persist workspace state `ready`.
- On creation failure, roll back what can be removed safely, persist the failure, and leave enough intent for startup reconciliation.
- Persist Assignment intent before starting every child.
### 2. Discover
Discovery Assignments inspect the repository and return structured facts, relevant files, risks, precedents, unresolved questions, and preliminary UI impact evidence.
If unresolved questions exist, the engine persists a Questions Gate and waits. Answers become a Decision and are included in every later planning prompt.
### 3. Design
The planner selects eligible Agent Profiles with recorded rationale and produces:
- Scope and acceptance criteria.
- Stage Assignments.
- Explicit file ownership for every mutating Assignment.
- Verification commands and expected outcomes.
- A structured UI impact classification: `none`, `minor`, or `non-trivial`, with affected surfaces and evidence.
- The independent Review Assignment target.
The engine combines planner output with objective UI signals from request terms, affected paths, routes, layout/navigation changes, accessibility, responsiveness, and user-flow changes. Objective signals of non-trivial UI impact cannot be downgraded by the planner. The approved design is written atomically to a bounded, versioned `plan.md` containing requirements, Decisions, selected Assignments, file ownership, verification commands, worktree details, and UI impact evidence. The Plan Approval view shows the classification and evidence, and the Decision explicitly accepts that classification. Implementation and prototyping cannot begin before approval.
### 4. Prototype
This Stage is required when UI impact is `non-trivial`.
- Produce at least three structurally distinct Prototype Candidates.
- Use separate fresh Assignments and candidate-specific paths. Profiles may repeat when only one prototyper is eligible.
- Prefer an existing host page and a `?variant=` switcher.
- Use realistic data and responsive layouts.
- Keep prototypes out of production behavior and mark them as throwaway.
- Return launch instructions, routes, candidate summaries, and artifact paths.
The engine opens a Prototype Acceptance Gate only after at least three candidates have unique IDs, revisions, paths, routes, structural rationales, responsive checks, and browser-launch evidence. The user reviews them in the browser or application and may accept one successful candidate revision, request refinement, or abort. The accepted candidate and feedback produce a new `plan.md` revision before implementation-context selection. Accepted prototype code is evidence, not production code; implementation rewrites the accepted direction to production standards. A post-acceptance cleanup Assignment removes prototype-only routes, losing candidates, and switchers before verification while retaining bounded artifacts outside the production tree.
### 5. Choose Implementation Context
Immediately before implementation, the engine persists an Implementation Context Gate referencing the final approved `plan.md` revision. For work without non-trivial UI impact this follows Plan Approval; for non-trivial UI work it follows Prototype Acceptance and the resulting plan revision.
The user chooses one strategy:
- `fresh-session`: the engine atomically persists a handoff intent bound to the Gate ID, plan revision, run revision, source parent-session ID, and unique preparation ID before `/workflow continue <run-id>` calls `ExtensionCommandContext.newSession()`. During the expected `session_shutdown`, the matching source session and `reason: "new"` atomically claim the event's `targetSessionFile` onto that intent and enter `claimed-target-awaiting-withSession`. The target's immediate `session_start` with `reason: "new"` and matching `previousSessionFile` suppresses recovery for that startup lifecycle only. Only the following replacement `withSession` callback for the claimed target may consume the intent: it attaches the new active parent-session ID, seeds the bounded `plan.md`, run ID, revision, worktree path, and continuation instruction, and then satisfies the Gate. Cancellation or failure before consumption keeps the Gate waiting with the original session active when possible.
- `smart-compact`: the engine persists a preparation ID bound to the Gate and final plan revision. When `pi.getActiveTools()` contains `smart_compact`, the parent invokes it with `plan.md` as the focus and reports `succeeded`, `no-op`, or `failed` through a `report-context-preparation` Decision carrying that preparation ID and plan revision. When the parent reports `unavailable`, the host accepts it only if `smart_compact` is absent from the active tool inventory, persists a normal-compaction intent, and directs `/workflow continue <run-id>` to invoke `ctx.compact({ customInstructions, onComplete, onError })` with equivalent plan-focused instructions. The invocation's intent-bound `onComplete` or `onError` callback consumes that exact intent and records the terminal method and outcome. A successful or no-op preparation satisfies the Gate; a failure remains visible and waiting. An available-but-failed `smart_compact` attempt never silently falls back.
- `current-session`: the Gate is satisfied immediately and the current context is retained unchanged.
Session replacement or compaction occurs only while the Workflow Run is waiting with no active child Assignments. Every callback, command, and event must match the active parent-session ID, Gate ID, preparation ID, and plan revision. A stale value, failed transition, or mismatched strategy leaves the Gate waiting and cannot start implementation.
### 6. Implement
Mutating Assignments run in the Workflow Run's worktree.
- Every Assignment has declared file ownership.
- All mutating Assignments are serialized in the first version. Concurrency remains available for read-only Assignments.
- Mutating child sessions do not receive `bash` or other process-execution tools. Assignment-scoped file operations reject symlink traversal and require canonical containment beneath both the worktree root and the Assignment's owned path roots.
- Plan-approved verification and generation commands run separately through a host executor using fixed executable/argument arrays, never agent-authored shell strings.
- The engine records branch, HEAD, worktree identity, and Git diff state before and after every mutating Assignment and host command.
- Out-of-scope changes, branch movement, commits, or worktree metadata changes invalidate the Assignment and open a Recovery Gate before more mutation starts.
- The scheduler reserves capacity synchronously before launching children.
- Assignment intent is persisted before child execution.
- The extension does not commit, merge, reset, or remove work automatically in the first version.
### 7. Verify
Verification runs the focused tests, type checks, linting, builds, and browser validation required by the approved plan. Changed behavior must have focused tests. UI changes must be exercised in the application or browser with realistic data.
A failed verification returns to remediation or opens a Recovery Gate when automatic remediation is unsafe.
### 8. Review
The engine maintains a cumulative set of every Agent Profile that mutated the reviewed diff, including implementation, prototype cleanup, and remediation. Review selection excludes that complete set.
Each Review Assignment:
- Uses a fresh child session.
- Receives only an audited read-only tool allowlist plus an internal read-only diff tool.
- Records a zero-diff postcondition; any repository change invalidates the verdict.
- Receives the approved plan, accepted prototype Decision when applicable, verification evidence, and complete diff scope.
- Reports specification conformance and repository standards as distinct structured axes.
- Returns a structured verdict and findings.
`changes_requested` creates remediation Assignments whose profiles join the cumulative mutator set, then repeats Verify and Review in another fresh session. A reviewer profile may never remediate the work it reviews. If exclusions leave no eligible reviewer, the run fails closed before further mutation. After three unsuccessful review cycles by default, the engine opens a Recovery Gate for user direction. Completion requires an approved review verdict.
## Custom Script Modes
The existing restricted JavaScript runtime remains as defense in depth inside nono.
### Feature script
A feature script is bound by its closed `insertionPoint` input to run before Design, after Design, or before Review. It may request only custom read-only analysis Assignments at that point. Every pre-implementation custom Assignment uses the audited analysis tool allowlist. The host still injects and enforces Questions, Plan Approval, Prototype Acceptance, implementation verification, and independent review policy. Scripts cannot choose another insertion point at runtime, create mutating Assignments before approval, satisfy Gates, reorder mandatory Stages, or create child sessions directly.
### Analysis script
An analysis script may orchestrate read-only Assignments without feature Gates. Its effective tool set is the intersection of:
- The configured `analysisTools` allowlist.
- The selected Agent Profile's tools.
- Tools active in the parent session.
The fixed first-version allowlist excludes `bash`, mutation tools, recursive orchestration, user interaction, process execution, and unaudited extension tools. Configuration may only remove entries from that allowlist.
### Script-visible interface
```js
stage("Audit");
const results = await parallel([
() => assign("review", "Inspect persistence behavior"),
() => assign("review", "Inspect cancellation behavior"),
]);
return results;
```
`stage`, `assign`, `parallel`, and immutable `args` communicate through authenticated, bounded IPC. The script has no filesystem, network, process, import, timer, eval, or dynamic code-generation access.
## Worktree Policy
Every feature Workflow Run owns a distinct, exclusive worktree under:
```text
<repository>/.worktrees/pi-workflow-<feature-name>-<run-id>/
```
The corresponding branch is:
```text
feature/<feature-name>
```
`<feature-name>` comes from the validated `featureName` start input and describes the feature rather than the workflow mechanism. Any existing branch with that name rejects a new start. If persisted state associates the branch with a Workflow Run, the error returns its run ID and directs the caller to continue it through `inspect` or `decide`; otherwise it directs the caller to choose another descriptive name. The extension never adds a numeric or run-ID suffix silently.
The configured worktree root must already be ignored by Git. The extension verifies this with Git before persisting creation intent and fails with setup guidance rather than modifying `.gitignore` automatically. A dirty source checkout blocks creation so uncommitted work is never silently omitted.
The engine records an ownership lease for the run and refuses to adopt or share another run's worktree. The completed worktree remains available for inspection and manual integration. Removal is explicit and never part of normal completion.
Read-only analysis runs use the parent cwd and do not create worktrees. All child sessions inherit the parent nono sandbox. The extension neither invokes nono nor widens filesystem or network permissions.
## Persistence and Recovery
Artifacts live under:
```text
~/.pi/agent/pi-workflows/<run-id>/
```
Files include:
```text
workflow.json
request.md
plan.md
config.json
profiles/
handoffs/
transcripts.json
result.json
artifacts/
```
Writes are atomic and mode `0600`. Serialization, transcript, source, argument, result, and IPC bounds from `_workflows` are retained.
The snapshot contains Decisions and prior Assignment outcomes, so a separate event store is unnecessary. Every `start`, `decide`, and `abort` command requires an idempotency key. The launch key and initial result are persisted before workspace creation. State-changing commands use revisions and idempotency keys to reject stale or duplicate transitions. Repeating a completed key returns its recorded result without another transition.
An active run owns a persisted lease containing process identity, active parent-session identity, heartbeat time, and worktree identity. The run retains every parent session ID attached through a handoff so `/workflow` can discover it from either session, but only the active ID may mutate the run. Startup and session recovery reconcile stale leases, child processes, workspace-creation intent, Git identity, context handoffs, compaction intents, and assignments left `running`.
Fresh-session handoff intent is durable before `newSession()`. A `session_shutdown` with reason `new` bypasses ordinary interruption recovery only when its source session matches the pending intent; that handler atomically claims the event's `targetSessionFile` and marks the intent `claimed-target-awaiting-withSession`. The claimed target's immediately following `session_start` is exempt from recovery only when its reason is `new` and `previousSessionFile` matches the source. The replacement `withSession` callback must then match the claimed target before it atomically consumes the intent, rotates the active parent-session ID, records both session IDs, and resumes delivery. Any later `session_start` caused by `startup`, `reload`, `resume`, or `fork`, or a bounded claim expiry before consumption, follows normal recovery and leaves implementation blocked.
Every Gate and terminal background notification has a deterministic delivery key and persisted pending/sent state. Delivery uses a custom parent-session message containing that key in structured details. The parent session is the receiver-side receipt: recovery scans durable session entries for the key before retrying, then marks the outbox item sent. This closes the crash window between message persistence and sender-side state updates.
On process or session interruption:
- Active Assignments become `interrupted` idempotently.
- Potentially mutating Assignments are never replayed automatically.
- The run opens a Recovery Gate offering retry, inspect, or abort.
- Completed Assignments and accepted Decisions remain intact.
## Child Execution
Every Assignment creates a fresh in-memory Pi session using the parent model registry, Assignment cwd, resolved trust decision, settings, skills, extensions, and `AGENTS.md` context. Worktree trust is resolved with `resolveStandaloneChildProjectTrust`; an alternate cwd never inherits a session-only trust decision implicitly.
The effective model is resolved in this order:
1. Assignment override allowed by the workflow definition.
2. Agent Profile model/provider/effort.
3. Parent session model and thinking level.
Unknown or ambiguous models fail the Assignment before execution. The effective provider, model, effort, profile digest, child session ID, and context capacity are persisted.
Children cannot access recursive workflow or subagent tools or `ask_user`. `pi_workflow` is added to both the shared child-tool exclusion policy and the separate Pi-subagent child exclusion list alongside the legacy `workflow` name; implementation should consolidate the duplicate list onto the shared policy when possible. Each child tool call retains the shared timeout guard, including tools registered after startup. The first-response watchdog and bounded shutdown behavior from `_workflows` are retained.
## Internal Modules
`pi-workflow` should remain a small set of deep modules:
- `index.ts`: Pi tool, command, lifecycle, and composition root.
- `engine.ts`: command handling, state transitions, gates, policies, and scheduling.
- `profiles.ts`: Markdown discovery, validation, eligibility, pinning, and diagnostics.
- `execution.ts`: fresh child sessions, assignment-scoped file operations, approved host commands, model resolution, structured output, transcripts, and timeouts.
- `script-runtime.ts` plus `sandbox-child.cjs`: restricted custom DSL and IPC.
- `store.ts`: atomic revisioned snapshots and bounded artifacts.
- `workspace.ts`: worktree creation, ownership conflict checks, and lifecycle.
- `context-transition.ts`: plan handoffs, new-session replacement, smart/default compaction callbacks, and session attachment.
- `dashboard.ts`: run inspection, continuation handoff, and prototype/report links.
- `agents/*.md`: bundled default profiles.
Pure transition and eligibility logic stay inside the engine and profile modules rather than behind speculative adapter interfaces. Tests may inject filesystem, executor, decision, and script-runtime adapters at the composition root.
## Exposure and Migration
- Do not rename or activate `_workflows` during initial development.
- Add `extensions/pi-workflow` tests to `package.json` explicitly.
- Typecheck through the existing `extensions/**/*.ts` configuration.
- Add `pi-workflow` to `config/pi-links.json` only after focused tests, full tests, independent review, and manual TUI validation pass.
- Run the link reconciler in check mode before sync.
- Preserve unrelated managed links and require `/reload` after exposure.
## Implementation Phases
### Phase 1: Domain and profile catalog
Implement the Workflow Run model, configuration resolution, Markdown profile loader, deterministic Eligibility Criteria, and bundled profiles.
#### Phase 1 Success Criteria
- Trusted project profiles override bundled profiles deterministically, while untrusted project profiles are never read.
- Profile files and symlinks resolving outside the selected bundled or trusted `.pi/agents` root are rejected.
- Malformed profiles produce isolated diagnostics and cannot suppress valid profiles.
- Profile contents, provenance, and effective model settings are pinned by digest.
- Every profile has stage Eligibility Criteria, pre-discovery fallback is deterministic, and irrelevant profiles are excluded with recorded reasons.
- Configuration resolves default concurrency 8 and an immutable process-wide maximum of 32; project and run settings cannot raise that maximum.
### Phase 2: Engine, gates, and persistence
Implement the command interface, legal transitions, closed Gate Decisions, atomic revisioned snapshots, idempotency, leases, delivery state, recovery, transactional worktree creation scaffold, and the built-in feature state machine through Plan Approval using an injected scripted Assignment executor for tests.
#### Phase 2 Success Criteria
- An exhaustive legal and illegal Stage/Gate transition matrix passes through `execute(command)`; invalid transitions fail closed.
- `start`, `decide`, and `abort` require idempotency keys; launch intent is persisted before worktree creation, and a repeated completed key returns its prior result without a second transition.
- Stale revisions are rejected for `decide` and `abort`.
- Using the injected scripted Assignment executor, Questions and Plan Approval pause and resume across process restarts with the accepted UI classification intact and no dependency on live Pi child execution.
- Startup reconciliation detects stale leases and transitions abandoned `running` Assignments to `interrupted` exactly once.
- Potentially mutating interrupted Assignments require an explicit Recovery Decision; completed Decisions and Assignments are not replayed.
- Feature names are validated before launch persistence; worktree branches use exactly `feature/<feature-name>`, and every existing branch collision rejects new start with recorded-run `inspect`/`decide` guidance or descriptive-name rename guidance, never a silent suffix.
- Workspace creation intent and lease are persisted before Git mutation; creation succeeds transactionally or leaves a reconciled failure that startup can recover.
- Interrupted atomic writes leave a complete prior or next snapshot, never partial JSON.
### Phase 3: Child execution and selection
Implement the production Pi Assignment executor and eligible planner-selection path, then integrate child sessions, structured output, model selection, process-wide scheduling, transcript capture, trust resolution, and timeout behavior with the Phase 2 engine.
#### Phase 3 Success Criteria
- Live Discover and Design Assignments drive the built-in pipeline through Questions and Plan Approval using the production executor.
- Every Assignment uses a fresh child session and resolves worktree trust through `resolveStandaloneChildProjectTrust`.
- Planner choices are restricted to eligible profiles and persist selected, rejected, and rationale data.
- Aggregate running Assignments across all Workflow Runs never exceed 32 or a lower configured ceiling.
- Capacity is reserved synchronously; queued cancellation and bounded session shutdown release capacity exactly once.
- `pi_workflow`, legacy workflow/subagent tools, and user interaction remain unavailable through both shared workflow-child and Pi-subagent child policies.
- A tool registered after child startup is still guarded by `extensions/shared/tool-call-timeout.ts`.
### Phase 4: Worktrees, implementation, verification, and review
Extend the Phase 2 worktree scaffold with the Implementation Context Gate, `/workflow continue`, and the production command/callback context-transition adapter, then add serialized mutation, canonical ownership and Git-state enforcement, verification evidence, independent review, and remediation cycles.
#### Phase 4 Success Criteria
- A dirty source state or unignored worktree root blocks creation before Git mutation.
- Workspace creation intent is persisted first; creation failure rolls back safely or remains reconcilable after restart.
- Every feature run receives a distinct leased worktree and worktrees are retained until explicitly removed.
- A versioned `plan.md` is persisted before the Implementation Context Gate and revised with an accepted prototype when applicable.
- Phase 4 registers `/workflow continue`; its `ExtensionCommandContext` performs both fresh-session replacement and approved normal-compaction fallback rather than deferring either path to dashboard integration.
- `fresh-session` persists a Gate-, revision-, plan-, preparation-, and source-session-bound handoff intent before `newSession`; only the replacement `withSession` callback consumes it, attaches the new active parent session, seeds the bounded plan handoff, and resumes.
- `smart-compact` uses a Gate-bound `report-context-preparation` transition with distinct `unavailable`, `succeeded`, `no-op`, and `failed` outcomes; `failed` requires a non-empty actionable error. The host validates `unavailable` against `pi.getActiveTools()`; only that outcome permits plan-focused `ctx.compact({ onComplete, onError })`, whose intent-bound callback persists success or failure.
- An available-but-failed `smart_compact` call does not silently fall back and keeps the Implementation Context Gate waiting with an actionable error; `current-session` preserves context unchanged.
- Context transitions run only while no child Assignment is active. Every state-changing parent call must match the lease's active parent-session ID; historical IDs are inspection-only, and stale Gate, preparation, plan, or run revisions keep implementation blocked.
- The pending fresh-session intent survives its matching old-session `reason: "new"` shutdown and atomically enters `claimed-target-awaiting-withSession`; only the target's immediate matching `reason: "new"` `session_start` suppresses recovery before `withSession` consumes it. Later startup, reload, resume, fork, or expiry recovers an unconsumed claim without starting implementation.
- Mutating Assignments are serialized, receive no process-execution tools, reject symlink traversal, and require canonical write containment beneath both the worktree and owned path roots.
- Plan-approved host commands use fixed executable/argument arrays; out-of-ownership writes or branch, HEAD, commit, reset, clean, or worktree changes fail before another mutation begins.
- Review excludes the cumulative set of profiles that mutated the diff, including remediation and prototype cleanup, and fails closed when no independent reviewer remains.
- Review uses a fresh session, an audited read-only tool set, and a zero-diff postcondition.
- Review reports specification conformance and repository standards separately.
- Completion is impossible without focused behavioral tests, successful verification, and approved independent review.
### Phase 5: Prototype policy and custom scripts
Add objective UI-impact validation, three-candidate prototype handling, post-acceptance cleanup, Prototype Acceptance, and restricted feature/analysis script modes.
#### Phase 5 Success Criteria
- Objective signals of non-trivial UI impact cannot be downgraded, and Plan Approval explicitly accepts the displayed classification.
- Non-trivial UI work cannot reach implementation without three successful, structurally distinct candidate revisions and explicit acceptance of one.
- Prototype candidates use realistic host context, are browser-reviewable and responsive, and produce launch and validation evidence.
- Prototype-only routes, losing variants, and switchers are removed or isolated before verification while bounded artifacts remain inspectable.
- Analysis and pre-implementation feature scripts receive only the audited read-only tool allowlist.
- Feature scripts are bound to a validated closed insertion point and cannot reorder or bypass mandatory Gates, prototype policy, verification, or review.
- Script IPC tests reject malformed, duplicate, oversized, unauthenticated, and over-budget requests and prove cancellation terminates active work.
### Phase 6: Dashboard, integration, and exposure
Add the dashboard integration around the Phase 4 `/workflow continue` command, persisted notification outbox, reports, complete verification, documentation, and managed exposure.
#### Phase 6 Success Criteria
- Dashboard state is derived from persisted snapshots and active runs, prior supported snapshot versions remain readable, and the existing `/workflow continue` command is presented only for a valid pending fresh-session or normal-compaction intent.
- Stable delivery keys, durable custom parent-session message receipts, and outbox reconciliation ensure each background Gate or terminal result is delivered once across reload and recovery.
- Focused tests, typecheck, full test suite, and manual Pi TUI validation of Gates, dashboard navigation, background delivery, and prototype handoff pass.
- Exposure is performed only through `config/pi-links.json`; unmanaged extension links are rejected and link reconciliation check passes before sync.
- `_workflows` remains available as an inactive reference until removal is separately approved.
## Verification Notes
- Test profile trust, precedence, malformed YAML isolation, and containment of regular files and symlinks.
- Test the exhaustive legal and illegal Stage/Gate transition matrix through the engine interface.
- Test stale revision, duplicate idempotency, lease reconciliation, and notification outbox handling.
- Test process-wide semaphore reservation races, queued cancellation, bounded session shutdown, and interrupted recovery.
- Test all three Implementation Context choices; Gate-bound `smart_compact` unavailable/succeeded/no-op/failed reports; required actionable failure errors; active-tool validation; intent-bound normal-compaction `onComplete`/`onError` callbacks; available-tool failure without fallback; and stale Gate, preparation, plan, run, and active-session identities.
- Test fresh-session intent persistence before `newSession`, exact shutdown claim, immediate target `session_start` exemption, `withSession` consumption, cancelled replacement, active-session rotation, old-session mutation rejection, and recovery of unconsumed claims on later startup/reload/resume/fork or expiry, plus the no-active-child transition precondition.
- Test cumulative reviewer profile/session exclusion, read-only postconditions, and failure when no independent reviewer exists.
- Test non-trivial UI classification cannot bypass structural candidate evidence, user acceptance, or prototype cleanup.
- Test analysis and pre-approval feature modes cannot activate mutation, recursive orchestration, interaction, process, or unaudited extension tools.
- Test malformed, duplicate, oversized, unauthenticated, and over-budget script IPC plus cancellation escalation.
- Test atomic persistence and backward-tolerant dashboard reads.
- Manually validate Gate dialogs, background delivery, dashboard navigation, browser prototype handoff, and responsive candidates in Pi TUI and the application.
## Precedents &amp; Lessons
- `_workflows` already proves bounded sandbox IPC, atomic artifacts, child-session execution, transcript capture, and dashboard rendering.
- `extensions/shared/child-session.ts` is the source of truth for project trust inheritance, recursive-tool exclusion, and bounded child teardown.
- `extensions/shared/tool-call-timeout.ts` must continue guarding tools registered after child startup.
- `extensions/subagents` demonstrates synchronous capacity reservation, normalized lifecycle state, and duplicate-delivery hazards.
- `skills/design/prototype/UI.md` requires structurally different variants, realistic host context, browser review, and throwaway cleanup.
- `skills/develop/code-review/SKILL.md` establishes independent standards and specification review as separate concerns.
- `config/pi-links.json` is the only approved exposure mechanism for repository-managed global extensions.