Files
harness-tools/docs/workflow/design.md
T

50 KiB

workflow Design

Historical reference, superseded for new development: This design describes the legacy workflow extension and custom engine. New feature work uses the model-invoked develop-workflow skill under ADR 0006. Retain this document only as pilot history.

Status: Superseded Date: 24AUG2026

Purpose

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 workflow is verified and explicitly exposed.

Goals

  • Load bundled Workflow Agent Profiles plus configured standard subagent definitions from the active pi-interactive-subagents package, the global agent directory, and trusted project overrides.
  • Adapt standard definitions through explicit workflow role overlays, then 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.
  • Inferring workflow roles or mutability from arbitrary standard-agent names or prompt prose.

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.

type WorkflowInput =
  | {
      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;
      allowDirtySource?: 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:

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.

Human-facing dashboard, selector, notification, handoff, compaction, tool, and report prose uses a shared presentation label. It prefers featureName; otherwise it normalizes the request to one line and bounds it to 48 characters, preserving whole words where possible and adding … when truncated; only a missing request falls back to runId. When derived labels collide within an available run list, only those labels receive · <prefix>, where the run-ID prefix is the shortest unique prefix with a minimum of eight characters. This label is never persisted as identity or accepted as a lookup alias. It requires no schema migration and does not change persisted paths. The complete runId remains canonical for snapshots, structured fields, command arguments, paths, receipts, leases, and idempotency. Copyable /workflow continue <run-id> and /workflow stop <run-id> commands retain it, and reports include it as Run ID metadata beneath the readable heading.

The workflow tool calls a single internal execute(command) interface. /workflow opens a drill-down TUI dashboard and may accept a run ID for direct inspection. Run selection, actions, Gates, confirmations, questions, and feedback use bounded popup overlays. Verified plans, reports, prototype evidence, and Assignment transcripts use a sanitized full-screen reader with H1-H3 outline navigation at widths of 80 columns or more and a single document pane from 60 through 79 columns. Below 48x12 it renders only a truthful minimum-size state. RPC, print, and JSON paths remain linear and do not call ctx.ui.custom().

/workflow start <message> sends the initial request through the assistant so the normal workflow tool path owns feature naming and idempotency; in interactive TUI mode, omitting the message opens an input overlay. /workflow continue presents eligible pending context transitions for selection, while /workflow continue <run-id> continues one directly; /workflow resume is an alias for both forms. Selecting a fresh-session continuation from the dashboard routes directly through the same handler and calls newSession() without a second command. /workflow stop [run-id] aborts a non-terminal run owned by the active parent session and presents a selector when multiple owned runs are active. The misspelled /worlflow alias is intentionally unsupported. Continue and stop selectors require interactive TUI mode and provide direct-run-ID guidance otherwise.

Continuation performs only 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. Stop treats running and waiting runs as active and excludes terminal runs and runs owned only by historical parent sessions.

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 workflow with action: "decide". Child agents never receive ask_user.

Background runs return a structured 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 and a readable run label in its prose.

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 workflow profiles from extensions/workflow/agents/*.md.
  2. Configured standard definitions from ${PI_CODING_AGENT_DIR:-~/.pi/agent}/extensions/pi-interactive-subagents/agents/.
  3. Configured standard definitions from ${PI_CODING_AGENT_DIR:-~/.pi/agent}/agents/.
  4. Workflow profiles or configured standard definitions from the nearest .pi/agents/*.md, only when ctx.isProjectTrusted() is true.

Standard definitions become Workflow Agent Profiles only when standardAgents.roles explicitly maps their name to one or more workflow roles. Their prompt, description, model, thinking level, and declared tools remain the standard definition's values; workflow policy supplies stages and still intersects tools with the audited stage allowlist. Unmapped standard definitions are ignored rather than assigned a role from their name or prose.

Files are read in lexical order. The first duplicate within one source wins and emits a diagnostic. Later trusted sources replace earlier definitions with the same normalized name. Provenance and a digest over both definition content and role overlay are retained. Regular files and symlinks are accepted only when their resolved paths remain inside the selected profile root. When a configured standard profile is eligible for a role, bundled profiles for that role become explicit fallbacks and are ineligible; bundled profiles continue to supply roles such as Design, Verify, and Review when no standard mapping exists.

Profile contents are pinned into the Workflow Run before selection. Editing a Markdown file does not change an active run.

Markdown schema

---
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:

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/workflow.json.
  3. Trusted .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.

{
  "concurrency": {
    "default": 8,
    "ceiling": 32
  },
  "maxAssignmentsPerRun": 128,
  "maxReviewCycles": 3,
  "analysisTools": ["read", "grep", "find", "ls"],
  "standardAgents": {
    "enabled": true,
    "roles": {
      "scout": ["discover"],
      "worker": ["implement"]
    }
  },
  "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:

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

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

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. Capture at most 20 sorted path/status records plus an omitted count for staged, unstaged, or untracked source changes. Unmerged paths always block creation. Ordinary changes require explicit TUI confirmation or allowDirtySource: true from a noninteractive caller.
  • 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 only accepted wrapper form is env PI_OFFLINE=1 <executable> ...; the executor unwraps it, sets that single variable, and applies the normal executable safety checks.
  • The host executor sets PYTHONDONTWRITEBYTECODE=1 so Python-backed checks do not create out-of-scope __pycache__ artifacts.
  • Every host command has a three-minute execution timeout and reports exit code 124 when terminated. Internal Git probes and worktree commands have a 30-second timeout. Process-tree termination, including Windows taskkill, has its own deadline and reports exit code 125 when cleanup cannot be completed.
  • Every child Assignment has a five-minute execution deadline. Timeout aborts the child signal and persists the Assignment as interrupted with assignment-timeout evidence rather than reporting success.
  • 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

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:

<repository>/.worktrees/workflow-<feature-name>-<run-id>/

The corresponding branch is:

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 worktree is created from the pinned source HEAD; source-only staged, unstaged, and untracked changes are not copied. The engine persists bounded path/status evidence and whether omission was explicitly allowed. TUI callers must confirm a warning that states those changes are omitted. Noninteractive callers must set allowDirtySource: true. Conflicts and unmerged paths always block creation.

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:

~/.pi/agent/workflows/runs/<run-id>/

Files include:

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 or explicit Recovery may adopt a continuable run only when its recorded owner process is dead or its lease is absent. Adoption atomically rotates both lease.activeParentSessionId and implementationContext.activeParentSessionId, appends the adopting session to parentSessionIds, and preserves revision fencing. A live process or live Assignment blocks adoption even when its heartbeat is old. Startup and session recovery reconcile 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.
  • Explicit retry resets failed verification commands to pending and clears their prior output before host execution.
  • An interrupted mutating Assignment routes to remediation only while the Implement stage is incomplete; after remediation completes that stage, later retries continue into verification.
  • 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. Standard definitions are execution identities, not permission grants: worker may declare bash and nested subagents, but mutating workflow Assignments still receive only audited reads plus guarded edit/write, and read-only Assignments receive only audited reads. workflow is added to the shared child-tool exclusion policy used by Pi-subagent children so recursive orchestration remains unavailable. 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

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: drill-down run actions, Gate orchestration, continuation handoff, and artifact selection.
  • tui.ts: reusable popup inputs/selections and the full-screen verified-document reader.
  • progress.ts: newest-attached-run projection and the bounded editor widget.
  • 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/workflow tests to package.json explicitly.
  • Typecheck through the existing extensions/**/*.ts configuration.
  • Add 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

  • Configured standard package/global/trusted-project definitions override bundled fallbacks deterministically, while untrusted project profiles are never read.
  • Standard definitions require explicit role overlays, preserve their model/prompt/tool identity, and cannot grant tools beyond the workflow stage allowlist.
  • Profile files and symlinks resolving outside the selected bundled or trusted agent roots 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 absent or dead ownership 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.
  • 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

  • An unignored worktree root or unmerged source state blocks creation before Git mutation. Ordinary source changes require bounded evidence and explicit authorization, then create from the persisted HEAD without copying those changes.
  • 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 start, /workflow stop, /workflow continue, and its /workflow resume alias. Start routes through the assistant's normal workflow tool path, stop is scoped to active runs owned by the current parent session, and continuation without a run ID selects from valid candidates. 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 continuation is presented only for a valid pending fresh-session or normal-compaction intent. Selecting a fresh-session continuation enters newSession() immediately.
  • Bounded TUI interactions use responsive popup overlays. Verified documents use the dedicated sanitized reader with H1-H3 navigation, cell-width-safe viewports, and the locked 80-column, 60-column, and 48x12 behavior. RPC, print, and JSON remain linear.
  • The widget above Pi's editor projects only the newest attached active run and clears after terminal persistence or session shutdown.
  • 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 workflow and standard-profile trust, package/global/project precedence, explicit role overlays, malformed YAML isolation, unmapped-definition omission, 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 & 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.