plan.yaml Fields
plan.yaml lives at the repository root. PRIME reads it every day to generate the Story Card for the current story.
Schema version
| Version | Format | Since |
|---|---|---|
| v3 (current) | stories: flat list with id, title, status | v3.0.0 |
| v2 (legacy) | days: list (still accepted) | v1.0.0 |
PACE normalises both formats internally. To migrate a v2 plan file:
python pace/migrations/v3_plan_naming.py --plan-file plan.yamlv3 structure (current)
release: string # required — matches releases[].name in pace.config.yamlcontext_version: string # optional — context manifest version this plan was built against
stories: - id: story-N # required — e.g. story-1, story-2 title: string # required status: string # required — pending | in_progress | shipped | hold shipped_at: string # optional — ISO 8601 timestamp, set by PACE on SHIP human_gate: bool # optional — pause after this story for human review acceptance_criteria: - string out_of_scope: - string notes: stringTop-level fields
| Field | Type | Required | Description |
|---|---|---|---|
release | string | Yes | Release name. Must match a releases[].name entry in pace.config.yaml. |
context_version | string | No | Records which context manifest version was current when the plan was written. Informational only. |
stories | list | Yes | One entry per sprint story. See below. |
stories[]
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Stable identifier in the form story-N (1-indexed). Used by PACE to locate the correct story for a given day. |
title | string | Yes | Short imperative description. Becomes the story title in the Story Card. |
status | string | Yes | Lifecycle state. One of pending, in_progress, shipped, hold. PACE updates this automatically. |
shipped_at | string | No | ISO 8601 timestamp written by PACE when GATE issues a SHIP decision. |
human_gate | boolean | No | If true, PACE pauses after all agents complete and waits for human review. Default: false. |
acceptance_criteria | list[string] | No | Testable conditions that GATE evaluates. Each criterion is checked independently. |
out_of_scope | list[string] | No | Explicitly deferred items. GATE can issue PARTIAL (instead of FAIL) for criteria that map to an out-of-scope item. |
notes | string | No | Free-text context injected into PRIME’s prompt. Not shown directly to FORGE. |
disable_file_hints | boolean | No | If true, skip file hint injection for this story. Use for architectural stories that intentionally explore broadly (e.g. greenfield modules, structural refactors). Default: false. |
status lifecycle
pending → in_progress → shipped ↘ holdPACE sets in_progress when it starts running agents for a story and shipped or hold based on the GATE decision. You can manually set a story to hold to skip it on the next run.
Writing effective acceptance criteria
Each criterion should be:
- Testable — GATE verifies it from test output, CI results, or code inspection.
- Specific — name the function, endpoint, HTTP status, or assertion.
- Atomic — one condition per line.
- Verb-first — start with an action: “Returns”, “Stores”, “Validates”, “Tests”, “Exits”.
Examples
acceptance_criteria: - "POST /auth/login returns 200 + signed JWT for valid credentials" - "POST /auth/login returns 401 for incorrect password" - "JWT payload includes user_id and exp (24h)" - "pytest exits 0 with at least 3 tests in test_auth.py" - "CI workflow passes on the commit from today's handoff"out_of_scope mapping
When GATE cannot verify a criterion, it checks whether the criterion maps to an out_of_scope item. If it does, GATE issues PARTIAL instead of FAIL.
stories: - id: story-1 title: "Login endpoint" status: pending acceptance_criteria: - "POST /auth/login returns 200 + JWT" - "CI workflow passes" out_of_scope: - "CI workflow — pipeline not yet configured in this sprint"If CI is not set up yet, GATE can PARTIAL the CI criterion and still issue a SHIP decision for the story. Without the out_of_scope entry, GATE would FAIL and HOLD the day.
Full example
release: v1.0
stories: - id: story-1 title: "User model and password hashing" status: shipped shipped_at: "2026-03-10T09:14:22Z" acceptance_criteria: - "User.create() stores bcrypt-hashed password, not plaintext" - "User.verify_password() returns True for correct, False for incorrect" - "Unit tests cover both cases, pytest exits 0" out_of_scope: - "OAuth / social login" - "Email verification"
- id: story-2 title: "POST /auth/login endpoint" status: shipped shipped_at: "2026-03-11T08:52:07Z" notes: > Use python-jose for JWT. RS256 signing with JWT_PRIVATE_KEY env var. Token payload: {user_id, exp: now + 24h}. acceptance_criteria: - "Returns 200 + {access_token, token_type} for valid credentials" - "Returns 401 for invalid password" - "Token exp is ~24 hours from issue time (verified in test)"
- id: story-3 title: "Advisory clearance + protected routes" status: pending notes: "SENTINEL and CONDUIT will receive the advisory backlog from stories 1-2." acceptance_criteria: - "All SENTINEL and CONDUIT advisories from stories 1-2 are resolved" - "Protected routes return 401 for missing token" - "Protected routes return 401 for malformed token" - "Protected routes return 200 for valid token"
- id: story-4 title: "POST /auth/refresh" status: pending acceptance_criteria: - "Issues new access token from valid refresh token" - "Rotates refresh token (old token is invalidated)" - "Returns 401 for expired or revoked refresh token"
- id: story-5 title: "Sprint acceptance" status: pending human_gate: true acceptance_criteria: - "All advisory backlog items resolved or escalated" - "CI pipeline green on final commit" - "README documents all new authentication endpoints" out_of_scope: - "Load testing" - "Multi-factor authentication"Legacy v2 format
The days: list format from v1.x is still fully supported. PACE detects it automatically and normalises it to the v3 shape internally.
# Legacy — still works, no migration requiredsprint: goal: "Ship JWT-based authentication" duration_days: 5
days: - day: 1 theme: "User model and password hashing" stories: - title: "Secure user creation" acceptance_criteria: - "User.create() stores bcrypt-hashed password"To convert to v3 in place:
python pace/migrations/v3_plan_naming.py --plan-file plan.yamlThe script backs up the original to .pace/releases/<release>/plan.yaml.bak.<timestamp> before writing.