Milestones, risks, and decisions — the working state
What you need: A populated project-state/ substrate.
The substrate stores project state as individual YAML files. One file per milestone. One file per risk. One file per decision. This is the core design choice: state is the source of truth, and reports are generated from state. When an artifact disagrees with state, you regenerate the artifact — you don't edit the artifact.
Adding a milestone
Tell Claude what you want:
Add a milestone: v4.0 release with matrix editor UI, target June 30
The milestone manager creates milestones/M03-matrix-editor.yaml:
id: M03-matrix-editor
kind: milestone
title: "v4.0 release — matrix editor UI"
owner_org: "Worksona"
owner_person: david-olsson-worksona
description: |
Visual editor for the reporting matrix. Allows adding, removing,
and reordering matrix entries without editing YAML directly.
planned_start: "2026-06-01"
planned_end: "2026-06-30"
actual_start: ~
actual_end: ~
percent_complete: 0
technical_progress: ~
deliverables:
- id: D03a
title: "Matrix editor React component"
status: not_started
- id: D03b
title: "YAML round-trip serializer"
status: not_started
status: not_started
created: "2026-05-28T00:00:00Z"
created_by: "david@atomic47.co"
last_modified: "2026-05-28T00:00:00Z"
last_modified_by: "david@atomic47.co"
The filename follows the convention M<NN>-<slug>.yaml. The NN counter increments automatically from state.json.
Updating progress
Natural language works:
M03 is at 40%, we finished the YAML serializer this week
The milestone manager updates two fields:
percent_complete: 40
technical_progress: |
YAML round-trip serializer complete. Preserves comments,
ordering, and frontmatter through edit cycles.
And updates the deliverable status:
deliverables:
- id: D03a
title: "Matrix editor React component"
status: not_started
- id: D03b
title: "YAML round-trip serializer"
status: delivered # ← updated
Every change appends an event to logs/activity.ndjson:
{"ts":"2026-05-28T14:30:00Z","actor":"david@atomic47.co","event":"milestone.updated","entity":"M03-matrix-editor","detail":"percent_complete 0→40, D03b delivered"}
The activity log is append-only. Corrections are new entries, not rewrites.
Querying state
Ask questions in natural language:
Which milestones are behind schedule?
The milestone manager reads all files in milestones/, compares planned_end against today's date and percent_complete, and returns:
# Behind Schedule
| ID | Title | Target | % Done | Gap |
|-----|------------------------|------------|--------|--------|
| M03 | Matrix editor | 2026-06-30 | 40% | on track (33 days left) |
| M02 | Starter pack hardening | 2026-06-15 | 40% | at risk (18 days, needs 60%) |
M02 is flagged at-risk: 60% of work remaining in 47% of time remaining.
Risks
Risks follow the same pattern. Here is a real risk from project-state's own substrate:
id: R-01-single-maintainer
kind: risk
title: "Single maintainer — bus factor of one"
likelihood: high
impact: high
score: 9
owner: david-olsson-worksona
mitigation: "Recruit at least one co-maintainer with write access.
Prioritize PIC pack co-ownership (only production pack).
Document the release process. Write a contributor onboarding guide."
contingency: "Substrate files remain functional on disk as plain YAML.
Another person can operate read-only. No new packs, features,
or bug fixes ship until a new maintainer is found."
status: open
last_reviewed: "2026-04-28"
created: "2026-04-28T00:00:00Z"
created_by: "david@atomic47.co"
last_modified: "2026-04-28T00:00:00Z"
last_modified_by: "david@atomic47.co"
Add a risk the same way:
Add a risk: enzyme supplier has a 12-week lead time,
single source, no backup. Likelihood medium, impact high.
The file lands in risks/R-03-enzyme-supply.yaml with the fields populated.
Decisions
Decisions use a date-prefixed filename:
Log a decision: we chose enzymatic hydrolysis over acid extraction
because it preserves protein functionality at pilot scale
Creates decisions/2026-05-28-extraction-method.yaml:
id: 2026-05-28-extraction-method
kind: decision
title: "Enzymatic hydrolysis over acid extraction"
date: "2026-05-28"
decision: |
Chose enzymatic hydrolysis for pilot-scale protein extraction.
Acid extraction denatures key functional proteins at temperatures
above 60°C. Enzymatic route preserves functionality.
rationale: "Protein functionality preservation at pilot scale"
alternatives_considered:
- "Acid extraction — rejected due to denaturation above 60°C"
- "Mechanical separation — rejected due to yield below 30%"
decided_by: "Jane Smith"
status: final
created: "2026-05-28T00:00:00Z"
created_by: "david@atomic47.co"
last_modified: "2026-05-28T00:00:00Z"
last_modified_by: "david@atomic47.co"
How state drives reports
Every report generator reads these YAML files. The weekly status report pulls milestone percentages, risk counts, and recent decisions. The doc suite generates styled .docx and .xlsx bundles from the same data. The kanban dashboard reads them on every page load.
The implication: you maintain state in one place, and every downstream artifact stays current. Update a milestone's percent_complete and the weekly report, the dashboard, the baseline report bundle, and the project website all reflect the change — the next time they're generated or loaded.
This is why the design insists that state is the source of truth. The alternative — editing a report and hoping the state catches up — creates drift. Drift compounds. Within a few weeks, the report says one thing and the substrate says another, and nobody knows which is right.
Next step
The state is there, the orchestrator reads it, reports are generated from it. But where do you see all of this at a glance? The kanban dashboard is a local web UI that renders the substrate as a visual board.