Agentic Plan Execution Runtime for Pi Coding Agent
pi-plan-runtime is a source-level PlanSpec execution runtime exclusively built for [@mariozechner/pi-coding-agent](https://www.npmjs.com/package/@mariozechner/pi-coding-agent).
When should you use this?
If your AI project already implements its own product UI, user confirmation flows, and business logic, and you only need a pure, reusable, topology-based execution engine for multi-step Agent tasks, then pi-plan-runtime is exactly what you need. It strips away business state and focuses entirely on reliable task execution.
- 🔄 Flexible Scheduling: Supports complex task topologies with both
serialandparallelexecution modes. - 🧩 Seamless Integration: Designed directly for
@mariozechner/pi-coding-agentwith a drop-in source-level integration pattern. - 🧠 Built-in State Recovery: Ships with
MemoryRuntimeStore, natively supporting task suspension and user input (waiting_user). - 🛡️ 100% Type-Safe: Written in TypeScript with rigorous protocol and schema validation powered by Zod.
- 📂 Standardized Handoff: Automatically manages context dependencies and file handoffs between execution steps.
flowchart TB
subgraph hostApp["Host application"]
direction LR
planSpec["PlanSpec<br/>(host-created and confirmed)"]
hostCalls["Host calls<br/>runPlan / cancelRun / resumeStepWithInput / getState"]
hostEvents["Consumes RuntimeEvent<br/>(optional sink)"]
planSpec --> hostCalls
end
subgraph runtime["pi-plan-runtime"]
direction LR
sessionFactory["Pi session factory<br/>+ step prompts"]
runtimeCore["Runtime orchestration<br/>schedule (serial / parallel,<br/>maxConcurrency)"]
handoffWriter["Handoff writer"]
end
store[("RuntimeStoreAdapter<br/>MemoryRuntimeStore / custom implementation")]
piAgent["Pi Coding Agent<br/>(per-step sessions)"]
workspace[("Workspace<br/>.pi-plan-runtime/handoff/...")]
hostCalls -->|"PlanSpec / user input / queries"| runtimeCore
runtimeCore -->|"sink.emit(event)"| hostEvents
runtimeCore -->|"write run / plan / step / handoff / event"| store
store -->|"getRunnableSteps / getHandoff"| runtimeCore
runtimeCore -->|"StepExecutionInput<br/>(with dependency HandoffDescriptors)"| sessionFactory
sessionFactory -->|"create and prompt"| piAgent
piAgent -->|"StepResult JSON<br/>completed / failed / cancelled / waiting_user"| runtimeCore
runtimeCore --> handoffWriter
handoffWriter -->|"result.json / raw_output.txt"| workspace
piAgent -.->|"read dependency files<br/>via read / bash tools"| workspace
piAgent ~~~ workspace
This diagram shows the runtime boundary rather than a fixed three-step pipeline: the host provides the PlanSpec, the runtime relies on the store to schedule dependency-ready steps, each step runs in its own Pi session, and handoff is delivered to downstream steps in two layers — descriptors via the store and actual files in the workspace. Events are both persisted to the store and pushed to the host's optional sink.
This version is tested against and pins @mariozechner/pi-coding-agent@0.72.1.
Pi 0.72.1 uses tool-name allowlists in createAgentSession({ tools }). Runtime examples therefore pass built-in tool names such as "read" and "bash", not constructed tool objects.
pi-plan-runtime executes a host-provided PlanSpec with one full-capability Pi agent configuration.
The runtime does not generate plans, ask users to confirm plans, define roles, route by capability, or own business state. The host project owns plan creation, plan IDs, user confirmation, plan versioning, and durable product records.
Add the runtime source to a Pi project:
your-project/
.pi/
src/
pi-plan-runtime/ # pi-plan-runtime source
Import from the source path used by the project:
import { createPiPlanRuntime, MemoryRuntimeStore } from "./pi-plan-runtime";For a minimal host integration example, see docs/INTEGRATION.md.
import {
createPiPlanRuntime,
MemoryRuntimeStore,
type PlanSpec,
} from "./pi-plan-runtime";
const runtime = createPiPlanRuntime({
piConfigRoot: "/absolute/path/to/.pi",
workspaceRoot: "/absolute/path/to/workspace",
model: {
provider: "your-pi-provider-name",
id: "your-model-id",
},
thinkingLevel: "off",
tools: ["read", "bash", "edit", "write", "grep", "find", "ls"],
stepSystemPrompt: [
"You are the execution agent for one plan step.",
"Use the project skills and tools to complete the current step.",
].join("\n"),
store: new MemoryRuntimeStore(),
scheduler: {
mode: "serial",
maxConcurrency: 1,
},
});
const plan: PlanSpec = {
id: "deck-plan",
title: "Create a product pitch deck",
summary: "Create a 5-slide product pitch deck from host-approved requirements.",
executionMode: "serial",
steps: [
{
id: "outline",
title: "Draft outline",
goal: "Create the deck outline.",
dependsOn: [],
},
{
id: "slides",
title: "Create slides",
goal: "Create slide content and assets from the approved outline.",
dependsOn: ["outline"],
},
],
};
const run = await runtime.runPlan({
runId: "host-run-id",
plan,
metadata: {
projectId: "host-project-id",
},
});
// Optional: cancel a run (or a specific plan in that run) from the host side.
await runtime.cancelRun({
runId: run.runId,
planId: run.planId,
});The host passes a PlanSpec directly to runPlan.
{
"id": "plan-id",
"title": "Plan title",
"summary": "Plan summary",
"executionMode": "serial",
"steps": [
{
"id": "step-1",
"title": "Step title",
"goal": "Step goal",
"dependsOn": []
}
]
}Valid execution modes are:
serial: runs one dependency-ready step at a time.parallel: runs dependency-ready steps concurrently up tomaxConcurrency.
Schemas are intentionally permissive. Caller-owned fields such as role, capability, executor, priority, or owner are preserved and passed through as step context. The runtime does not interpret them.
Each step Pi session must return exactly one JSON object:
completed: the step finished.failed: the step failed.cancelled: the step was cancelled by the host.waiting_user: the step requires structured user input before it can continue.
For waiting_user, the result must include inputRequest. The runtime stores the waiting state and can continue with:
await runtime.resumeStepWithInput({
planId,
stepId,
values: {
topic: "Updated source material",
},
});When the host calls cancelRun(...), the runtime marks non-terminal steps as cancelled and attempts to abort in-flight step sessions.
In addition to existing events, the runtime now emits:
run.cancelled: the run was cancelled.plan.cancelled: the plan was cancelled.step.cancelled: the step was cancelled.step.context: emitted before step execution, carryingStepExecutionInput(including dependency handoffs, workspace, metadata, and user input).
After a completed step, the runtime writes handoff files under the workspace:
.pi-plan-runtime/handoff/<planId>/<stepId>/
result.json
raw_output.txt
Dependent steps receive handoff descriptors in their step execution context. The generated step prompt instructs the Pi agent to read dependency handoff files before doing substantial work.
createPiPlanRuntimeMemoryRuntimeStorecreateDefaultPiSessionFactorycreatePiSessionEventBridge- Protocol schemas and types:
PlanSpec,PlanStepSpec,StepExecutionInput,StepResult,RuntimeEvent,HandoffDescriptor,PiPlanCancelResult
- Only
serialandparallelare valid execution modes. - No planner, quick-answer, or user-confirmation flow is included.
- No business role, capability, or executor routing is implemented.
- No database store is included; use
MemoryRuntimeStoreor implementRuntimeStoreAdapter.
npm install
npm run typecheck
npm testMIT