Skip to content

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

VersionFormatSince
v3 (current)stories: flat list with id, title, statusv3.0.0
v2 (legacy)days: list (still accepted)v1.0.0

PACE normalises both formats internally. To migrate a v2 plan file:

Terminal window
python pace/migrations/v3_plan_naming.py --plan-file plan.yaml

v3 structure (current)

release: string # required — matches releases[].name in pace.config.yaml
context_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: string

Top-level fields

FieldTypeRequiredDescription
releasestringYesRelease name. Must match a releases[].name entry in pace.config.yaml.
context_versionstringNoRecords which context manifest version was current when the plan was written. Informational only.
storieslistYesOne entry per sprint story. See below.

stories[]

FieldTypeRequiredDescription
idstringYesStable identifier in the form story-N (1-indexed). Used by PACE to locate the correct story for a given day.
titlestringYesShort imperative description. Becomes the story title in the Story Card.
statusstringYesLifecycle state. One of pending, in_progress, shipped, hold. PACE updates this automatically.
shipped_atstringNoISO 8601 timestamp written by PACE when GATE issues a SHIP decision.
human_gatebooleanNoIf true, PACE pauses after all agents complete and waits for human review. Default: false.
acceptance_criterialist[string]NoTestable conditions that GATE evaluates. Each criterion is checked independently.
out_of_scopelist[string]NoExplicitly deferred items. GATE can issue PARTIAL (instead of FAIL) for criteria that map to an out-of-scope item.
notesstringNoFree-text context injected into PRIME’s prompt. Not shown directly to FORGE.
disable_file_hintsbooleanNoIf 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
↘ hold

PACE 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 required
sprint:
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:

Terminal window
python pace/migrations/v3_plan_naming.py --plan-file plan.yaml

The script backs up the original to .pace/releases/<release>/plan.yaml.bak.<timestamp> before writing.