Skip to content

Plan file contract — host and container

Nyia's plan tooling has two sides that never call each other:

Side Runs where Does
nyia (the orchestrator) on the host, only reads and renders: nyia todo, nyia plans status, nyia plans decisions; one-time store operations: nyia plans migrate, nyia plans status-backfill
Skills and system prompts inside the assistant container read the files below, and write exactly two things: a plan's Status: line and entries in its decisions.md

nyia is never installed, mounted, or shimmed into the container — that is the product: the AI CLI lives in the box, control stays outside. A skill or prompt that says "run nyia …" is a bug (tests/bats/test_skills_host_boundary.bats fails the build on it). The boundary is files, in the formats below.

1. todo.md — generated by the host, read by everyone

.nyiakeeper/todo.md is the plan inventory: one line per plan, worst status first, with a GENERATED header. The host regenerates it at launch (right after the plan-layout gate) and after the container exits; nyia todo --write regenerates it on demand. The regeneration is quiet and best-effort: it never fails a launch and it never overwrites a file that does not carry the GENERATED header (a hand-written todo.md is left alone, with a one-line hint at launch; archive it, or NYIA_TODO_FORCE=1 nyia todo --write).

Inside the container: read it, never write it. If it is missing or older than the plans, derive the same view by reading each plans/NNN-slug/plan.md Status: line — read-only; the host writes the file on the next launch or exit.

2. plan.md — the Status: line

Line 2 of every plans/NNN-slug/plan.md (the line after the # Plan: title) is:

Status: <Draft|Ready|Active|Blocked|Review|Done|Dropped>

Exactly one token from that enum. Skills (nyia-make-a-plan, nyia-implement-plan, nyia-run-plans, nyia-code-review, nyia-kickoff, nyia-checkpoint) edit this line to move a plan on the board; the inventory is generated from it. Optional, near the top: Roadmap: <label> — a free-text grouping axis read by nyia-plan-status.

3. decisions.md — the decision log

plans/NNN-slug/decisions.md is an append-only log. One entry per decision, a blank line between entries:

## D-<YYYYMMDD>-<HHMMSS>-<n> (<YYYY-MM-DD>) · decided-by: <user|llm|<assistant-name>>
Topic: <one line>
Question: <one line>
Options: <one line — alternatives considered, "|"-separated>
Decision: <one line — what was decided and why>
Supersedes: <optional — an earlier decision id>

Rules for a writer (a skill, or a person in an editor):

  • The header is the only line that starts with ##; the id is D- + date + time + a random number (unique in the file); the date in parentheses is ISO; decided-by is user when the person decided, llm (or the assistant's name) when the assistant did.
  • Every field is one line — fold newlines into spaces. Do not put secrets in a decision (the host viewer redacts KEY=, TOKEN=, PASSWORD= patterns on display, but the file is the record).
  • Topic, Question and Decision are required; Options and Supersedes are optional.
  • Append; never rewrite or delete an earlier entry. A changed mind is a new entry with Supersedes:.

The host viewer (nyia plans decisions [N] [--by X] [--since DATE]) is tolerant: it renders what parses and prints warning: malformed entry at <file>:<line> for what does not. Inside the container, nyia-show-decisions reads the same files directly.

4. Reviews

plans/NNN-slug/reviews/ holds plan-review-*, code-review-* and pair-review-* files. Those file-name prefixes are parsed by the migrator and the resolvers — they are not skill names and do not change when skills are renamed.

5. Shipped skills are Nyia-owned

The built-in skills under docker/shared/skills/ are refreshed (overwritten) on every upgrade, at every seeding target; every shipped copy carries a .nyia-builtin <version> marker. Never edit a shipped copy in place — it will be replaced. To customize a built-in under its own name, use a team source or the project's .nyiakeeper/shared/skills/ (an override copied from there is user-owned: the marker is stripped, and it is never replaced); to change the image itself, use an overlay (TEAM_SHARING.md). A skill you write under another name in your global skills directory is never touched. Inside the box, the image's copy of a built-in replaces the host-propagated copy only when the image is at least as new — rebuild the image (nyia-<assistant> --build) to refresh skills in the session after a dist-only upgrade. When a shipped skill is renamed (the nyia- prefix, Plan 338), every directory under the old name inside Nyia's skill directories — global, per-assistant, persona profiles — is deleted on upgrade and again at every launch, whatever its content or marker: those directories only ever receive Nyia's copies, and a CLI's own commands never live there.