---
name: pi-loops
description: Design, build, or modify supervised Pi loop workflows. Use when the user asks for loop nodes, loop runtime behavior, loop UI/progress, gates, room nodes, model/tool policy, loop_report flow, or how to separate generic extension features from workflow-specific loop logic.
---

# Pi Loops

Pi Loops turns Pi into a supervised workflow runner. A **loop** is an ordered graph of bounded nodes. The **extension** owns orchestration; the loop owns domain logic.

Leading rule: keep the runtime generic and keep the workflow specific.

## First steps

1. Identify whether the request is about the **extension** or a **loop**.
   - Extension: runtime, node types, transitions, retry mechanics, progress UI, commands, persistence, shared metrics, `loop_report`, generic handler registration.
   - Loop: node list, prompts, deterministic handlers, gates, domain artifacts, model choices, tool policies, workflow-specific reports.
2. Inspect the relevant files before editing.
   - Extension core: `src/index.ts`, `src/runtime.ts`, `src/types.ts`, `src/nodes.ts`, `src/state.ts`, `src/tools.ts`, `src/ui.ts`.
   - Loop registry: `src/loops/index.ts`.
   - Loop implementation: `src/loops/<loop-id>/definition.ts` and optional `handlers.ts`.
3. Preserve the boundary. Do not put one user's workflow labels, tender fields, issue names, or business rules into extension core.
4. Validate with a harmless loop or small workflow before suggesting publish.

Completion criterion: every change is classified as extension-level or loop-level, and no loop-specific behavior leaked into extension core unless it is intentionally exposed as generic configuration.

## Boundary

### Extension owns

These belong in `src/` core:

- Node lifecycle: pending, running, completed, failed, skipped.
- Runtime progression, transitions, retries, pause/resume/cancel.
- Generic node kinds: `code`, `llm`, `room`, `gate`, `approval`, `finalizer`.
- Handler registries such as `registerCodeHandler` and `registerRoomHandler`.
- Tool policy enforcement and evidence collection.
- `loop_report` mechanics and report acceptance.
- Generic metrics: duration, attempts, token usage, error counts.
- Generic UI: current step, progress widget, loader animation, status line, generic final summary.
- Context hygiene rules such as not auto-injecting all artifacts into LLM prompts.

### Loop owns

These belong in `src/loops/<loop-id>/`:

- Node IDs, labels, goals, and order.
- Domain handlers and domain artifacts.
- Prompts and schemas.
- Gate criteria and validation details.
- Model/provider/thinking choices per node or worker.
- Allowed tools per LLM node.
- Domain-specific metrics and reports.
- Product/business logic.

### Smell test

Before editing extension core, ask: "Would this make sense for a GitHub issue loop, a PDF tender loop, a release loop, and a data-cleaning loop?" If not, put it in the loop.

## Generic UI rules

The extension should render a standard progress view for every loop automatically. It may use node labels from the loop definition, but must not hardcode workflow-specific labels.

Default widget shape:

```text
Pi Loop: <loop-id> · <status> · <completed>/<total>

 1 ✓ Prepare                         2 ●●●· Analyze
 3 ○ Validate                        4 ○ Finish
```

Loader rule:

- The running node uses 4 dot positions.
- 3 dots are highlighted.
- The empty dot moves across the 4 positions:

```text
●●●·
●●·●
●·●●
·●●●
```

Keep this in extension UI code, not in individual loops. If a user wants a different global UI, change `src/ui.ts` or add generic UI options to `LoopDefinition`; do not customize one loop by branching inside the renderer.

## Creating a loop

Create `src/loops/my-loop/definition.ts`:

```ts
import type { LoopDefinition, NodeDefinition, TransitionRule } from "../../types.ts";

export function createMyLoopDefinition(): LoopDefinition {
  const nodes: NodeDefinition[] = [
    {
      id: "prepare",
      kind: "code",
      label: "Prepare",
      goal: "Prepare input data.",
      handler: "myPrepare",
      successCriteria: ["Artifact contains 'context'"],
    },
    {
      id: "analyze",
      kind: "llm",
      label: "Analyze",
      goal: "Analyze the prepared context and report findings.",
      allowedTools: ["read", "bash", "loop_report"],
      timeout: 300_000,
      instruction: `Use only this context:\n{artifact.context}\n\nCall loop_report when done.`,
    },
    {
      id: "finish",
      kind: "finalizer",
      label: "Finish",
      goal: "Complete the loop.",
    },
  ];

  return {
    id: "my-loop",
    version: "1.0.0",
    label: "My Loop",
    description: "What this loop does.",
    trigger: { type: "command", commandName: "my-loop" },
    nodes,
    transitions: [passNext("analyze"), passNext("finish"), complete()],
  };
}

function passNext(nodeId: string): TransitionRule[] {
  return [
    { fromGateVerdict: "pass", target: { type: "node", nodeId } },
    { fromGateVerdict: "correctable", target: { type: "retry" } },
    { fromGateVerdict: "terminal", target: { type: "fail" } },
  ];
}

function complete(): TransitionRule[] {
  return [{ fromGateVerdict: "pass", target: { type: "complete" } }];
}
```

Create `src/loops/my-loop/handlers.ts`:

```ts
import { registerCodeHandler } from "../../nodes.ts";

export function registerMyLoopHandlers(): void {
  registerCodeHandler("myPrepare", async (_run, _node, input) => ({
    artifacts: { context: { input } },
    summary: "Prepared context.",
  }));
}
```

Register in `src/loops/index.ts`:

```ts
import type { LoopDefinition } from "../types.ts";
import { createMyLoopDefinition } from "./my-loop/definition.ts";
import { registerMyLoopHandlers } from "./my-loop/handlers.ts";

export function registerLoopPackages(registerDefinition: (definition: LoopDefinition) => void): void {
  registerDefinition(createMyLoopDefinition());
  registerMyLoopHandlers();
}
```

Run:

```text
/reload
/loop start my-loop key=value
```

Completion criterion: `/loop list` shows the new loop, and `/loop start <id>` reaches the first node without missing handler errors.

## Node guidance

- `code`: deterministic work. Use for parsing, validation, setup, cleanup, file writes, API calls, tests, and report generation.
- `llm`: bounded agent work. Must have a narrow instruction, allowed tools, timeout, and a clear `loop_report` completion condition.
- `room`: first-class multi-worker phase. Use when the node internally coordinates packets/workers/reduction. The extension owns lifecycle; the room handler owns strategy.
- `gate`: deterministic validation over artifacts/evidence. Gates decide pass/correctable/terminal.
- `approval`: human decision point.
- `finalizer`: completes the loop and writes/announces final output.

Prefer `code` over `llm` whenever the operation is deterministic.

## Context rule

Do not rely on implicit artifact injection. LLM instructions must explicitly interpolate only the artifacts they need:

```text
{artifact.cleanContext}
{artifact.currentPacket}
```

A loop should create clean artifacts between LLM nodes. This prevents parser output, queues, previous tool logs, and stale reports from leaking into later prompts.

## loop_report

LLM nodes finish by calling `loop_report`:

```json
{
  "summary": "What was accomplished",
  "status": "ready_for_validation",
  "confidence": "high",
  "artifacts": { "key": "value" },
  "changedFiles": ["path/to/file.ts"],
  "commandsRun": ["npm test"],
  "risks": ["Potential regression"]
}
```

Treat the report as a proposal. Gates and runtime transitions decide whether the node actually passes.

## Modifying extension core

Use this path for global behavior shared by all loops:

1. Locate the generic owner:
   - UI/progress: `src/ui.ts` and render calls in `src/runtime.ts` / `src/index.ts`.
   - Runtime transitions: `src/runtime.ts`.
   - Node model/types: `src/types.ts`.
   - Handler execution: `src/nodes.ts`.
   - Commands and registration: `src/index.ts`.
2. Remove loop-specific names before committing.
3. Expose variation through generic options instead of conditionals for a specific loop.
4. Test with a dummy/no-op loop in an installed or local test copy, not in the starter repo if the package should ship with zero loops.

Completion criterion: the starter repo still has no private loops, generic examples remain generic, and the installed/test copy can demonstrate the behavior.

## Modifying a loop

Use this path for workflow behavior:

1. Edit only `src/loops/<loop-id>/definition.ts`, `handlers.ts`, prompts, schemas, or local helper files.
2. Keep prompts narrow and evidence/artifacts explicit.
3. Keep domain metrics in loop artifacts or loop outputs.
4. Register handlers in that loop's registration path.
5. Run the smallest realistic input first.

Completion criterion: the loop behavior changes without changing how unrelated loops render, transition, or report.

## Publish checklist

Before pushing a new extension version:

- No private/customer loops are included in the starter package unless intentionally shipped as examples.
- `src/loops/index.ts` is empty or contains only public examples.
- Extension core contains no hardcoded domain labels such as tender fields, PR names, or one workflow's step IDs.
- Generic UI works from node labels and statuses.
- Runtime changes are backward-compatible with existing loop definitions.
- README and this skill agree on node types and commands.
- A dummy installed/test loop was used if UI or lifecycle behavior changed.

## Commands

| Command | Description |
|---------|-------------|
| `/loop start <definition> [input...]` | Start a loop |
| `/loop status` | Show current run state |
| `/loop list` | List registered loop definitions |
| `/loop cancel` | Cancel active run |
| `/loop pause` | Pause active run |
| `/loop resume` | Resume paused/idle loop |

## Reference

- `src/types.ts` — loop, node, transition, and metric types.
- `src/nodes.ts` — handler registration and node execution.
- `src/runtime.ts` — orchestration, transitions, retries, metrics, persistence.
- `src/ui.ts` — generic progress widget and loader.
- `src/loops/index.ts` — loop registry for the package/user install.
- `references/github-issue.md` — non-runnable reference walkthrough.
