pm-go Orchestration
pm-go pulls planning, retries, approvals, budgets, worktree lifecycle, merge order, and completion audit out of model context and into a durable control plane (Postgres + Temporal + git worktrees + typed contracts). Runs are resumable; "done" comes from evidence (diff-scope, reviewer findings, deterministic audit) rather than model claims.
Operating Defaults
Prefer the CLI and typed operator tools over raw API calls. They preserve path containment, approval policy, process cleanup, and plan provenance. Use raw API only for manual recovery or when the user is explicitly working at the API layer; the current endpoint cheatsheet lives in references/api-surface.md.
When state is unclear, inspect first with pm-go status or pm-go why <id>. When command syntax is unclear, run pm-go <command> --help rather than inventing flags.
Activation Loop (start here)
pm-go is agent-first as of v0.9.x. The default invocation is the agentic operator: it ensures Docker + Postgres + Temporal + worker + API are reachable, then drives the control plane through typed MCP tools.
| Goal | Command |
|---|---|
| Default — agent drives a spec | pm-go --repo <repo> --spec <spec.md> (or pm-go agent ...) |
| Larger / multi-milestone spec | pm-go run --repo <repo> in one terminal, then pm-go decompose --repo <repo> --spec <spec.md> --edit |
| Interactive operator (no spec) | pm-go --repo <repo> |
| Resume an operator session | pm-go --resume <session-id> |
| Legacy one-shot drive | pm-go implement --legacy-drive --repo <repo> --spec <spec.md> |
| Manual stack control | pm-go run --repo <repo> (keep attached) + pm-go drive --plan <id> from another shell |
pm-go implement without --legacy-drive also defaults to agent mode since v0.9. The pre-v0.9 "boot + submit + drive in one process" semantics live behind --legacy-drive, including its 45-minute default plan-persistence wait (--plan-wait 60m overrides it).
Stay in the foreground; Ctrl+C tears it down cleanly. For the default agent, --approve interactive is the default; --yes changes the default approval policy to all; an explicit --approve all|none|interactive always wins. The legacy implement and low-level drive commands default to --approve all.
Do not interrogate the user about env vars before running. Trust --runtime auto (the default). It auto-detects, in priority order:
ANTHROPIC_API_KEYenv var- Claude Code OAuth session at
~/.claude/.credentials.json(i.e. the user is logged into Claude Code) claudeCLI onPATH
If none of those are present, the worker fails loudly at boot with an actionable error. You don't need to pre-check — the supervisor's first banner line tells you which auth source was selected.
If pm-go isn't on PATH, install it once (idempotent — re-running upgrades):
curl -fsSL https://raw.githubusercontent.com/alex-reysa/pm-go/main/scripts/install.sh | bash
Agentic Operator (default)
The operator runs inside the Claude Agent SDK and exposes a typed MCP tool surface so the model can drive pm-go without raw curl. Every tool call lands on agent_tool_calls (with FK to agent_runs.plan_id) for full replay.
Tool surface (13 tools):
- Lifecycle —
pmgo_ensure_stack,pmgo_recover,pmgo_stop,pmgo_doctor,pmgo_status - Submission —
pmgo_submit_spec(rejects model-suppliedspecPath/repoRootoutside operator scope) - Planning —
pmgo_decompose_spec(despite the name, this starts the normal full-spec planning workflow viaPOST /plans) - Manifest hooks —
pmgo_update_manifest,pmgo_plan_first(handler-backed hooks; use the CLI Layer-A flow unless production handlers are present) - Drive —
pmgo_drive_plan(rejectsapproveoverride; only operator's--approvepolicy wins) - Approve —
pmgo_approve_pending(returnsrequires_confirmationwhen operator chose--approve none) - Inspect —
pmgo_why,pmgo_tail_events
Path containment, approval-policy enforcement, and FK-safe planId extraction are wired into the tool wrappers. pmgo_decompose_spec intentionally does NOT record planId on the tool-call row: POST /plans returns the id synchronously, but the plans row is async-inserted by persistPlan, so recording the FK immediately would race.
Layer-A: Decompose Large Specs
For specs > ~100 lines or scoping multiple milestones, use the Layer-A CLI to split the spec into an ordered MilestoneManifest before driving implementation. The command talks to a running API, so start the stack first:
# Step 1 — start the control plane and keep it running in one terminal
pm-go run --repo .
# Step 2 — from another shell, decompose, optionally edit, then plan the first milestone
pm-go decompose --repo . --spec ./feature.md --edit
# Step 3 — drive the plan id printed by decompose
pm-go drive --plan <plan-id>
pm-go decompose submits the spec, waits for the manifest, prints it, and unless --manifest-only is set, starts plan-first for the first milestone and prints the resulting plan id. Add --edit when the operator should review or adjust the manifest in $VISUAL / $EDITOR / vi before plan-first. Use --manifest-only when the next step is human review rather than immediate planning.
Provenance (decompositionId, milestoneId) flows through to the plans row so a downstream operator can trace the plan back to the manifest entry that scoped it. Current Layer-A behavior plans the first milestone only; it does not expose --plan-first or --milestone-id CLI flags, and it does not auto-chain later milestones.
Visibility
Four commands cover most diagnostic needs without raw tctl or DB queries:
pm-go doctor # env + CLIs + auth + infra (postgres/temporal/migrations/ports)
pm-go doctor --repair # also auto-fix what it can (docker up, db:migrate, mkdir)
pm-go status # worker config, API /health, open Temporal workflows
pm-go why <id> # one-sentence state + next action for any plan/phase/task/spec id
Use pm-go status first when something looks stuck — it tells you the configured task queue + namespace + open workflows. A "scheduled but never picked up" workflow is almost always one of: stale dist/ from before a code change, mismatched TEMPORAL_TASK_QUEUE, or the worker pointing at the wrong Temporal cluster.
Picking a Model
pm-go defaults to claude-opus-4-7 for every role. Override per-role or globally with env vars:
PM_GO_MODEL=claude-sonnet-4-6 pm-go --repo . --spec ./feature.md # all worker roles
PLANNER_MODEL=claude-opus-4-7 IMPLEMENTER_MODEL=claude-sonnet-4-6 pm-go --repo . --spec ./feature.md
Recognized vars: PLANNER_MODEL, IMPLEMENTER_MODEL, REVIEWER_MODEL, PHASE_AUDITOR_MODEL, COMPLETION_AUDITOR_MODEL. PM_GO_MODEL is the shared fallback.
You should not need to edit packages/planner/src/*.ts — the env vars are the supported override.
When to Use Sub-Commands Instead of the Default Agent
The default pm-go (agent) covers the common case. Reach for sub-commands when:
| Goal | Command |
|---|---|
| Boot the stack and stay attached to drive manually | pm-go run --repo . |
| Drive an already-submitted plan (e.g. after a crash) | pm-go drive --plan <uuid> |
| Resume after pause-for-approval | pm-go drive --plan <uuid> after approving |
| Pre-flight checks before committing to a long run | pm-go doctor then pm-go --repo . --spec ... |
| Layer-A: split a multi-milestone spec first | pm-go run --repo . then pm-go decompose --repo . --spec ./feature.md --edit |
Legacy one-shot (pre-v0.9 implement semantics) |
pm-go implement --legacy-drive --spec ./feature.md |
pm-go run is the supervisor without the auto-drive. pm-go drive assumes the API is already up on :3001 and a plan exists in Postgres.
Spec Format
The spec is a Markdown file. Key sections:
- Objective — one-paragraph scope
- Scope — what's in / out
- Constraints — technical bounds (no new dependencies, must preserve API X, etc.)
- Acceptance Criteria — bullet list, each becomes a test target the reviewer enforces
- Repo Hints — files and directories the planner should focus on
See examples/spec-input-template.md and examples/golden-path/spec.md in the pm-go repo for canonical examples.
One spec → one plan → one repo. If the work spans multiple repos (e.g. a candidate API in repo A and a router in repo B), file two specs. Specs accept an explicit repoRoot per submission, so a single API can host plans for multiple repos.
State Machine
spec → plan → [for each phase] execute tasks → review → fix? → integrate → audit → complete → release
- Tasks within a phase run in parallel where dependencies allow.
- Review produces a
ReviewReportwithpass | changes_requested | blocked.changes_requestedtriggers up tomaxReviewFixCyclesrepair cycles before escalation. - Phase audit runs after task integration;
completion auditruns after all phases complete. - Release is gated on a passing completion audit.
pm-go drive runs all of this for you. The TUI (pnpm tui from the pm-go repo) is useful for human inspection but is not required.
Approval Gates
High-risk tasks/phases pause for explicit approval. In the default agent, --approve interactive is the default and prompts the operator; --yes defaults the agent to --approve all for autonomous runs. In pm-go drive and legacy pm-go implement --legacy-drive, --approve all is the default. --approve none exits when an approval is needed; resume by approving and re-running drive.
When legacy pm-go implement --legacy-drive pauses for approval, it leaves the API + worker up and prints the exact approval URL. Resolve the approval, then re-run pm-go drive --plan <uuid>.
Recovery
If the supervisor died mid-flight, or you don't know what state a plan/phase/task is in, run:
pm-go why <id> # id can be plan, phase, task, or spec-document UUID
It returns one sentence with the current state and the exact next action. This is the single best diagnostic — reach for it before grepping source, opening the TUI, or hitting Postgres directly. If why says the plan is mid-phase, follow up with pm-go drive --plan <uuid> to resume from where the supervisor left off.
Common Failure Modes
- Worker workflow stuck "scheduled but never picked up" — almost always stale
dist/. Runpnpm -r buildfrom the pm-go repo, then restart the worker.pm-go statusconfirms task queue + namespace alignment. - Diff-scope violation — the implementer wrote files outside the task's
fileScope. Either widen the scope (requires a plan re-draft) or tighten the task description. - Content-filter rejection —
agent_runs.error_reason="ContentFilterError"and the task isblocked. Adjust the spec wording for the affected task and re-run. - Phase won't advance — every task in the phase must be
ready_to_merge. Find the laggards:curl http://localhost:3001/plans/<id>and look for tasks inreviewing/fixing. - Workflow-id collision on resume —
drivereportsWorkflowExecutionAlreadyStartedafter a supervisor restart. Runpm-go why <id>andpm-go statusfirst, sweep stale process state withpm-go recoverif needed, then re-runpm-go drive --plan <id>. - Legacy
pm-go implement --legacy-driveexits before the plan exists in DB — the supervisor's plan-persistence poll defaults to 45 minutes and reports whether the Temporal workflow is still running when it times out. If hit, keep the API + worker up, runpm-go status, and onceGET /plans/<id>returns the plan envelope (which it does as soon as the planning row is inserted; see "Planning-row visibility" below) resume withpm-go drive --plan <id>. Use--plan-wait 60mfor larger specs.
Planning-row visibility
GET /plans/:id returns the plan envelope as soon as the planning row is inserted — it does NOT 404 during planning. Older runbooks treated a 404 as the planning-pending signal; that has not been the behaviour since v0.8.8.x. The correct signal that a plan is still being authored is plans.status='draft' and an empty (or partial) plans.phases array in the envelope, NOT an HTTP 404. Use pm-go why <plan-id> rather than re-polling for an HTTP 200 to determine planning progress.
Release-evidence vocabulary
Three classes of evidence travel with a release; treat each class differently:
- Pre-release evidence MAY block the completion audit. Includes every required phase-audit report, every review report for a required acceptance criterion, the plan-level diff, and pre-release browser smoke when it can run against an integration worktree or pre-release preview. Missing pre-release evidence is the auditor's job to flag.
- Release artifacts never block. The PR summary (
pr_summary) and the completion evidence bundle (completion_evidence_bundle) are produced BYFinalReleaseWorkflowAS the release lands. Their absence at audit time is expected, not a gap. - Post-release evidence never blocks completion audit. Post-release smoke transcripts, deploy confirmations, and PR/merge metadata are recorded after release via the
post_release_evidenceartifact path (see the newrecordPostReleaseEvidenceactivity). They feed traceability and operator dashboards, not the audit verdict. An operator who wants to gate release on post-release signal does so viacompletion_audit_reports.override_reason/overridden_by/overridden_at, NOT by waiting for post-release evidence inside the audit.
Do not advise operators to delay completion audit until post-release smoke arrives, and do not interpret a missing post_release_evidence artifact as a completion-audit blocker.
[pm-go] port <port> is held by another service
Phase-1 commands (status, drive, why, recover, run, implement, decompose) probe the API's identity endpoint before driving against it. When the probe gets HTTP back but the response isn't a pm-go identity envelope ({"service":"pm-go-api",...}), the CLI bails with a PmGoIdentityMismatchError whose first line is the stable, greppable prefix:
[pm-go] port 3001 is held by another service
Cause. Something other than the pm-go API is bound to the configured port — most often another tsx-loaded server squatting on 3001 (a stale apps/api process from a prior dev session, an unrelated local service, or a second pm-go instance that claimed the port first).
Remediation (two options).
- Re-run the command with
--port <other>to point pm-go at a free port. - Stop the conflicting process (
pm-go psthenpm-go stopif it's a tracked pm-go supervisor; otherwiselsof -i :3001and shut down the offending service yourself).
API Surface (advanced)
pm-go drive orchestrates the API for you. If the user is explicitly debugging API behavior or writing tests, read references/api-surface.md for the current raw endpoints. Do not copy old snippets from memory: POST /spec-documents uses body, and POST /plans needs both specDocumentId and repoSnapshotId.
Stub Mode (CI / fast smoke)
Set --runtime stub for fixture-driven runs that exercise the full pipeline without calling Claude:
pm-go --repo . --runtime stub --spec ./examples/golden-path/spec.md
Useful for smoke-testing pm-go itself, not for solving real specs. Stub fixtures live under packages/sample-repos/ and examples/.
Anti-Patterns
- Don't pre-check
ANTHROPIC_API_KEY. OAuth alone is sufficient. Ask the supervisor instead — it prints which source it picked at boot. - Don't edit
packages/planner/src/*.tsto swap models. UsePM_GO_MODELor per-role env vars. - Don't invent Layer-A milestone-selection flags. The public CLI plans the first milestone only; verify
pm-go decompose --helpbefore documenting new flags. - Don't
pkill -f pm-go— it kills the operator's monitors too. Usepm-go psto inspect the supervisor-owned process registry, thenpm-go stopto shut them down cleanly. (Both commands target only pm-go's tracked PIDs, so editor and dev-server processes survive.) - Don't run two supervisors against the same Postgres + Temporal. Port 3001 collides.
pm-go statuswill tell you something is already on that port. - Don't push to
mainof the pm-go repo while running pm-go on its own repo. Baseline drift causes false phase-audit scope violations on every phase. Seedocs/dogfood/v0.8.6-run.mdfor the cautionary tale.
Reference
examples/golden-path/— full walkthrough of a stub-mode rundocs/getting-started.md— manual API flowdocs/runbooks/— operational recovery playbooksreferences/api-surface.md— raw API fallback for advanced/manual flowspackages/contracts/src/— domain types (Plan, Task, AgentRun, ReviewReport)artifacts/plans/— generated plan Markdown afterSpecToPlanWorkflowfinishes
Source: alex-reysa/pm-go — distributed by TomeVault.