Custom workflows

Define durable multi-step Pi harness workflows.

Pi harness workflows are normal @fragno-dev/workflows workflows. Use them when a Pi session needs more than the stock interactive chat loop: multiple agent steps, tool-result branching, parallel reviewers, races against user input, or explicit approvals.

Register workflow entries directly with createPiWorkflows(...) and with the Workflows fragment. There is intentionally no createPi() or definePiWorkflow(...) builder in @fragno-dev/pi-harness.

import { createPiHarness, createPiWorkflows } from "@fragno-dev/pi-harness/factory";
import { createAgentLoop } from "@fragno-dev/pi-harness/harness/commands";
import { createWorkflowsFragment } from "@fragno-dev/workflows";
import { defineWorkflow } from "@fragno-dev/workflows/workflow";
import { z } from "zod";

const supportWorkflow = defineWorkflow(
  { name: "support", schema: z.object({ topic: z.string() }) },
  async (event, step) => {
    const loop = createAgentLoop(step, {
      env,
      workflowName: "support",
      sessionId: event.instanceId,
      agentName: "support",
      model,
      systemPrompt: "You are a helpful support agent.",
      tools: [searchTool],
    });

    await loop.runStep("initial", { kind: "prompt", args: [`Help with ${event.payload.topic}`] });
    while (true) {
      await loop.waitForCommandAndRunStep();
    }
  },
);

const piConfig = { workflows: [supportWorkflow] };
const workflows = createPiWorkflows(piConfig);

Create sessions by selecting the workflow and passing schema-validated input.

{
  "name": "Customer issue",
  "input": { "topic": "durable LLM workflows" }
}

Lower-level harness steps

Use runPiHarnessStep(...) directly when the workflow wants full control over operation names, committed entries, active tools, or non-prompt operations such as compaction and tree navigation.

import {
  runPiHarnessStep,
  createPiHarnessSessionState,
} from "@fragno-dev/pi-harness/harness/run-pi-harness-step";

const state = createPiHarnessSessionState();
const result = await runPiHarnessStep(step, "classify", {
  env,
  workflowName: "triage",
  sessionId: event.instanceId,
  agentName: "triage",
  model,
  tools: [classifyRequest],
  activeToolNames: ["classify_request"],
  committedEntries: state.entries,
  operation: {
    kind: "prompt",
    args: [event.payload.request],
    stopOnTools: ["classify_request"],
  },
});

Replay rules

Keep workflow structure deterministic

Workflow replay depends on stable step structure. Do not build step names from random IDs, current time, or partial streamed LLM output.

  • Step names should be string literals or derived from already completed durable data.
  • Keep runtime-only capabilities (env, streamFn, tool execute functions) in workflow closures.
  • Persist only serializable params and step results.
  • Use activeToolNames as a per-step policy when a turn should expose only a subset of registered tools.