Persistent, auditable plan-then-execute workflow for pi agents.
Agent proposes a plan → human reviews → approves or rejects → executor runs in-session.
AI agents that write to external systems (ERP, email, calendars) need guardrails. Confirmation per tool call doesn't work — you can't assess a 5-step workflow one click at a time.
pi-planner lets the agent propose the full sequence, the human review it as a unit, and approve once. Plans persist on disk as markdown files — auditable, diffable, survives crashes.
See MOTIVATION.md for the full rationale.
npm install @marcfargas/pi-plannerAdd to your pi config:
{
"pi": {
"extensions": ["@marcfargas/pi-planner"]
}
}@mariozechner/pi-coding-agent>= 0.50.0
@marcfargas/pi-safety— safety classification registry (installed automatically)
The extension registers 8 tools:
| Tool | Description |
|---|---|
plan_mode |
Enter/exit plan mode (read-only + plan tools) |
plan_propose |
Propose a plan with title, steps, and context |
plan_list |
List plans, optionally filtered by status |
plan_get |
Get full details of a plan by ID |
plan_approve |
Approve a proposed plan for execution |
plan_reject |
Reject a plan with optional feedback |
plan_skill_safety |
Register skill safety classifications (called after reading skills) |
plan_run_script |
Report step outcomes during plan execution |
Plans are for consequential external actions — Odoo writes, email sends, calendar changes, deployments, anything irreversible or on behalf of others.
Not for file edits, git, build/test, or reading from systems. Those are normal dev work.
The SKILL.md file guides the agent on when to use plan mode and when to propose.
| Command | What it does |
|---|---|
/plan |
Toggle plan mode, or review pending plans if any exist |
/plans |
Browse all plans — approve, reject, retry, clone, delete, view details |
/safety |
Inspect the skill safety registry |
When the agent enters plan mode (plan_mode(enable: true)):
- Allowed:
read, safebash(ls, cat, grep, git status…), allplan_*tools - Allowed if registered: Skill operations classified as READ (search, list, get, describe)
- Blocked:
write,edit, destructive bash, skill operations classified as WRITE
This prevents accidental side effects while the agent researches and builds the plan.
When a plan is approved and executed:
- Plan mode auto-exits (full tools needed for execution)
- Tools are scoped to the plan's requirements +
plan_run_script - The agent receives an executor prompt via
sendUserMessage - The agent follows the steps in order, calling
plan_run_scriptafter each - On completion or failure, tools are restored to the previous state
The executor protocol requires the agent to call plan_run_script with:
step_complete/step_failedafter each stepplan_complete/plan_failedwhen done
proposed ──┬──► approved ──► executing ──┬──► completed
│ ├──► failed ──► retry / clone
├──► rejected ──► clone └──► stalled ──► retry / clone
└──► cancelled
- Retry: Reset a failed/stalled plan to approved and re-execute
- Clone: Create a new proposed plan from any terminal plan's steps and context
- Optimistic locking: version increments on every write — concurrent edits are detected
- Crash recovery: plans stuck in
executingpast the timeout are markedstalled
Plans are markdown files with YAML frontmatter in .pi/plans/:
{project}/.pi/plans/
├── PLAN-a1b2c3d4.md # Plan files
├── sessions/ # Executor step logs (JSONL)
│ └── PLAN-a1b2c3d4.jsonl
└── artifacts/ # Large context data
Example plan file:
---
id: PLAN-a1b2c3d4
title: "Send invoice reminder to Acme Corp"
status: proposed
version: 1
tools_required:
- odoo-toolbox
- go-easy
---
## Steps
1. Query overdue invoices for Acme Corp (odoo-toolbox: search → account.move)
2. Send payment reminder email (go-easy: send → [email protected])
3. Log reminder activity on invoice (odoo-toolbox: write → account.move)
## Context
Invoice INV-2024-0847 is 30 days overdue. Amount: €1,500.The agent reads skill documentation, extracts safety annotations, and calls plan_skill_safety with command-matching glob patterns. pi-planner stores the patterns and uses @marcfargas/pi-safety to match them against bash commands at runtime.
Before: Plan mode blocked all bash — agent couldn't search Odoo or check Gmail while researching.
After: Agent reads skills, registers safety patterns. READ operations (search, list, get) pass through in plan mode. WRITE operations stay blocked.
plan_skill_safety({
tool: "npx go-gmail",
commands: {
"npx go-gmail * search *": "READ",
"npx go-gmail * send *": "WRITE",
},
default: "WRITE"
})
Use /safety to inspect the current registry.
Optional. Create .pi/plans.json in your project:
{
"guardedTools": [],
"stale_after_days": 30,
"executor_timeout_minutes": 30
}| Option | Default | Description |
|---|---|---|
guardedTools |
[] |
Tool names that log a warning when called without an active plan |
stale_after_days |
30 |
Days before a proposed plan is considered stale |
executor_timeout_minutes |
30 |
Minutes before an executing plan is marked stalled |
src/
├── index.ts Extension entry — mode switching, TUI commands, plan_run_script, lifecycle
├── tools/
│ ├── index.ts Plan CRUD tools (propose, list, get, approve, reject)
│ └── safety.ts plan_skill_safety tool
├── mode/
│ └── hooks.ts Hooks — before_agent_start, tool_call blocking, safety filtering
├── executor/
│ ├── runner.ts In-session execution via sendUserMessage + setActiveTools
│ ├── checkpoint.ts Step-level checkpointing (JSONL)
│ ├── preflight.ts Pre-flight validation (tools exist, plan is approved)
│ └── stalled.ts Stalled plan detection and timeout
└── persistence/
├── plan-store.ts CRUD, atomic writes, optimistic locking, cache
├── types.ts Plan, PlanStep, PlanScript, PlanStatus, PlannerConfig
└── config.ts Reads .pi/plans.json
Safety classification types and registry live in @marcfargas/pi-safety.
| Hook | What it does |
|---|---|
before_agent_start |
Injects plan-mode context + skill safety extraction instruction |
tool_call |
Safety registry resolution → allowlist → block. Logs guarded tools |
agent_end |
Cleans up execution state, updates widgets |
session_start |
Restores plan mode, detects stalled plans from previous session |
context |
Filters stale plan-mode messages when not in plan mode |
MIT