Workflows
Use defineWorkflow for work that must survive retries, time, or an external continuation.
Authoring a workflow
Preflight. Apply the system guidance's closed-world preflight before calling
defineWorkflow. The workflow authoring declarations are already present in the system
reference; read the provider declarations selected by that gate.
Complete when the closed-world preflight passes. When it cannot pass, complete this branch
by reporting the blocking requirement.
Author. Define the workflow directly at the top level. Inline definitions automatically
start; retain the returned instanceId. Save a workflow file only when the user asks for
persistent automation behavior; use the Building Automations skill for that branch.
defineWorkflow({ name: "approval-workflow" }, async (event, step) => {
const request = await step.do("prepare-request", async () => {
return { requestId: crypto.randomUUID() };
});
const approval = await step.waitForEvent("approval", {
type: "approval",
timeout: "15 minutes",
});
return { request, approval: approval.payload };
});
Put side effects, provider calls, and expensive work inside step.do. Keep pure deterministic
calculations outside steps. Use stable, descriptive step names because history and retries
address those names. Use step.sleep or step.sleepUntil for time and step.waitForEvent for
external continuation.
Complete when every non-deterministic operation has a durable step and every continuation
has an exact event type.
Observe. Copy the returned instanceId; code-mode calls do not share in-memory variables.
Read the run with workflow.getInstance({ instanceId }). Inspect output for a completed run,
confirm the authored event type for a waiting run, and read
workflow.getHistory({ instanceId }) when diagnosing an errored run. If instance details are
temporarily unavailable, call workflow.listInstances({}) once as a status fallback and report
the backend failure beside the observed summary.
Complete when the instance is complete with observed output, intentionally waiting with
its exact continuation, or errored with the failed step and error identified.
Prompting an agent
When a workflow needs model work, use step.agent.prompt(name, input). One workflow instance owns
one continuing agent session, so later prompts include the earlier prompt, tool-call, and
tool-result history. Give every prompt a stable name.
Workflow agents inherit no tools from the authoring Pi session. Define each capability locally with
defineTool and pass the complete tool set to that prompt. Prefer a tool result when the workflow
needs structured data:
const classify = defineTool({
name: "classify",
description: "Return the harmfulness classification.",
parameters: {
type: "object",
additionalProperties: false,
required: ["classification", "confidence", "reason"],
properties: {
classification: { enum: ["harmful", "not harmful", "uncertain"] },
confidence: { enum: ["low", "medium", "high"] },
reason: { type: "string", maxLength: 1000 },
},
},
execute: async (_toolCallId, result) => result,
});
const response = await step.agent.prompt("classify-text", {
text: `Classify this text:\n\n${text}`,
tools: [classify],
});
const classificationResults = response.toolResults.filter(
({ toolName }) => toolName === "classify",
);
if (classificationResults.length !== 1) {
throw new Error("Expected exactly one classify tool result.");
}
const classification = classificationResults[0].result;
Tool execution is part of the prompt step and can repeat when an attempt fails before commit. Keep
tools pure and replay-safe. Put external effects in separate step.do calls with stable idempotency
keys. Before branching, confirm that the expected tool ran. When execute returns the validated
tool arguments, use the result properties directly instead of repeating the JSON Schema validation.
Operating an existing workflow
Read "/static/codemode/providers/workflow.d.ts" for exact inputs:
workflow.createInstance({ path, instanceId, payload }) starts a saved .workflow.js file.
Supply a stable instanceId and copy it for later calls. Inline defineWorkflow runs do not need
this call.
workflow.listInstances({ status, pageSize, cursor }) lists codemode workflow instances.
workflow.getInstance({ instanceId }) reads status, output, error, and source path.
workflow.getHistory({ instanceId }) exposes steps, events, and emissions for diagnosis.
workflow.sendEvent({ instanceId, type, payload }) resumes a waiting instance.
workflow.retryFailedStep({ instanceId, delayMs }) retries the latest failed top-level step.
For a waiting instance, send the exact event type expected by step.waitForEvent. Failed-step
retry requires the latest top-level step to be the only failed top-level step. Retrying a do step
reruns all of its nested steps, including completed steps, so their effects and mutations must be
repeatable. Retrying a failed event wait starts a fresh timeout window and considers pending events
before the new deadline.
1---2name: workflows3description: Orchestrate durable multi-step work with defineWorkflow and operate workflow instances. Use when a current task needs retries, sleeps, waiting for an external event, or the user asks to inspect, signal, or retry a workflow instance.4---56# Workflows78Use `defineWorkflow` for work that must survive retries, time, or an external continuation.910## Authoring a workflow11121. **Preflight.** Apply the system guidance's closed-world preflight before calling13 `defineWorkflow`. The workflow authoring declarations are already present in the system14 reference; read the provider declarations selected by that gate.1516 **Complete when** the closed-world preflight passes. When it cannot pass, complete this branch17 by reporting the blocking requirement.18192. **Author.** Define the workflow directly at the top level. Inline definitions automatically20 start; retain the returned `instanceId`. Save a workflow file only when the user asks for21 persistent automation behavior; use the Building Automations skill for that branch.2223 ```js24 defineWorkflow({ name: "approval-workflow" }, async (event, step) => {25 const request = await step.do("prepare-request", async () => {26 return { requestId: crypto.randomUUID() };27 });2829 const approval = await step.waitForEvent("approval", {30 type: "approval",31 timeout: "15 minutes",32 });3334 return { request, approval: approval.payload };35 });36 ```3738 Put side effects, provider calls, and expensive work inside `step.do`. Keep pure deterministic39 calculations outside steps. Use stable, descriptive step names because history and retries40 address those names. Use `step.sleep` or `step.sleepUntil` for time and `step.waitForEvent` for41 external continuation.4243 **Complete when** every non-deterministic operation has a durable step and every continuation44 has an exact event type.45463. **Observe.** Copy the returned `instanceId`; code-mode calls do not share in-memory variables.47 Read the run with `workflow.getInstance({ instanceId })`. Inspect output for a completed run,48 confirm the authored event type for a waiting run, and read49 `workflow.getHistory({ instanceId })` when diagnosing an errored run. If instance details are50 temporarily unavailable, call `workflow.listInstances({})` once as a status fallback and report51 the backend failure beside the observed summary.5253 **Complete when** the instance is `complete` with observed output, intentionally `waiting` with54 its exact continuation, or `errored` with the failed step and error identified.5556### Prompting an agent5758When a workflow needs model work, use `step.agent.prompt(name, input)`. One workflow instance owns59one continuing agent session, so later prompts include the earlier prompt, tool-call, and60tool-result history. Give every prompt a stable name.6162Workflow agents inherit no tools from the authoring Pi session. Define each capability locally with63`defineTool` and pass the complete tool set to that prompt. Prefer a tool result when the workflow64needs structured data:6566```js67const classify = defineTool({68 name: "classify",69 description: "Return the harmfulness classification.",70 parameters: {71 type: "object",72 additionalProperties: false,73 required: ["classification", "confidence", "reason"],74 properties: {75 classification: { enum: ["harmful", "not harmful", "uncertain"] },76 confidence: { enum: ["low", "medium", "high"] },77 reason: { type: "string", maxLength: 1000 },78 },79 },80 execute: async (_toolCallId, result) => result,81});8283const response = await step.agent.prompt("classify-text", {84 text: `Classify this text:\n\n${text}`,85 tools: [classify],86});87const classificationResults = response.toolResults.filter(88 ({ toolName }) => toolName === "classify",89);90if (classificationResults.length !== 1) {91 throw new Error("Expected exactly one classify tool result.");92}93const classification = classificationResults[0].result;94```9596Tool execution is part of the prompt step and can repeat when an attempt fails before commit. Keep97tools pure and replay-safe. Put external effects in separate `step.do` calls with stable idempotency98keys. Before branching, confirm that the expected tool ran. When `execute` returns the validated99tool arguments, use the result properties directly instead of repeating the JSON Schema validation.100101## Operating an existing workflow102103Read "/static/codemode/providers/workflow.d.ts" for exact inputs:104105- `workflow.createInstance({ path, instanceId, payload })` starts a saved `.workflow.js` file.106 Supply a stable `instanceId` and copy it for later calls. Inline `defineWorkflow` runs do not need107 this call.108- `workflow.listInstances({ status, pageSize, cursor })` lists codemode workflow instances.109- `workflow.getInstance({ instanceId })` reads status, output, error, and source path.110- `workflow.getHistory({ instanceId })` exposes steps, events, and emissions for diagnosis.111- `workflow.sendEvent({ instanceId, type, payload })` resumes a waiting instance.112- `workflow.retryFailedStep({ instanceId, delayMs })` retries the latest failed top-level step.113114For a waiting instance, send the exact event `type` expected by `step.waitForEvent`. Failed-step115retry requires the latest top-level step to be the only failed top-level step. Retrying a `do` step116reruns all of its nested steps, including completed steps, so their effects and mutations must be117repeatable. Retrying a failed event wait starts a fresh timeout window and considers pending events118before the new deadline.