Skip to content

Artifact Schemas

Canonical on-disk format of the planning artifacts. Downstream agents (executor, verifier, plan-checker, nyquist-auditor) parse against these schemas. Breaking changes require a superseding ADR.

All artifacts live under .nubos-pilot/milestones/M<NNN>/ (milestone scope) or .nubos-pilot/milestones/M<NNN>/slices/S<NNN>/ (slice scope) or inside tasks/T<NNNN>/ (task scope).

ArtifactScopeProducerConsumer(s)Lifecycle
M<NNN>-CONTEXT.mdmilestone/np:discuss-phaseplanner, researcher, plan-checker, executorrewrite on save
M<NNN>-ROADMAP.mdmilestone/np:plan-phase (planner)plan-checker, verifierrewrite on save
M<NNN>-META.jsonmilestone/np:plan-phase (planner)status displaysrewrite on save
M<NNN>-RESEARCH.mdmilestone/np:research-phaseplanner, plan-checkerrewrite on save
M<NNN>-PLAN-REVIEW.mdmilestone/np:plan-phase loopuser, auditappend-only
M<NNN>-VERIFICATION.mdmilestone/np:verify-work/np:add-tests, userrewrite on save
M<NNN>-VALIDATION.mdmilestone/np:validate-phaseuserrewrite on save
S<NNN>-ASSESSMENT.mdslice/np:plan-phase (planner)plan-checker, executorrewrite on save
S<NNN>-PLAN.mdslice/np:plan-phase (planner)plan-checker, scaffolder, executorrewrite on save
S<NNN>-UAT.mdslice/np:plan-phase (planner)plan-checker, verifierrewrite on save
S<NNN>-SUMMARY.mdsliceexecutor (last task of slice)verifierrewrite on save
T<NNNN>-PLAN.mdtask/np:plan-phase (scaffolder from <task> block)executorrewrite on repromote
T<NNNN>-SUMMARY.mdtaskexecutor (after commit)verifierrewrite on save
S<NNN>/TODO.mdslicelib/todo.cjs::renderTodoMd (scaffolder + setTaskStatus hook)human, executor, verifierrewrite on every task-status transition
handoffs/<ts>__<from>-to-<to>__<slug>__<id>.mdmilestone or project-globallib/handoff.cjs::writeHandoff (via handoff-write CLI)target agent, filtered by to_agentappend on create, status mutated in place

M<NNN>-CONTEXT.md

Captures user decisions from the adaptive interview. Read by every downstream agent.

Template: templates/milestone/CONTEXT.md. Required sections: <goal>, <domain>, <decisions> (D-01..D-NN), <deferred>, <canonical_refs>.

S<NNN>-PLAN.md

Slice plan with inline <task> blocks. This is what the scaffolder parses to produce per-task files.

Frontmatter:

yaml
---
slice: "M001-S001"
milestone: "M001"
type: plan
status: pending
requirements: ["REQ-AUTH-01", "REQ-AUTH-02"]
---

Body sections:

  • <objective> — what this slice delivers.
  • <context> — @-references to CONTEXT, RESEARCH, prior SUMMARY files.
  • <tasks> — one or more <task> blocks (see below).
  • <verification> — slice-level verify commands.
  • <success_criteria> — per-slice SCs (separate from milestone-level SCs in roadmap.yaml).
  • <output> — what S<NNN>-SUMMARY.md should contain after execution.

<task> block schema

Every <task> block inside a slice PLAN must carry four attributes on the opening tag:

xml
<task id="M001-S001-T0001" depends_on="" wave="1" tier="sonnet">
  <name>Seed login form</name>
  <files>src/auth/LoginForm.tsx</files>
  <read_first>
    - src/auth/AuthProvider.tsx
  </read_first>
  <action>
Create LoginForm.tsx with email + password inputs. Wire it to useAuth().
  </action>
  <verify>
    <automated>npm test -- LoginForm</automated>
  </verify>
  <acceptance_criteria>
    - Form renders without runtime errors
  </acceptance_criteria>
  <done>LoginForm component committed, unit test green.</done>
</task>

Attribute rules:

  • id — full-id M<NNN>-S<NNN>-T<NNNN> matching the enclosing slice.
  • depends_on — comma-separated full-ids of earlier-slice tasks (never same-slice), or "".
  • wave — integer equal to the slice number.
  • tierhaiku | sonnet | opus.

If any of these is missing, the task is silently dropped when scaffold-all-tasks runs.

T<NNNN>-PLAN.md

Scaffolded from a slice's <task> block. Frontmatter matches TASK_REQUIRED_FIELDS from lib/tasks.cjs:

yaml
---
id: "M001-S001-T0001"
slice: "M001-S001"
milestone: "M001"
type: execute
status: pending
tier: "sonnet"
owner: executor
wave: 1
depends_on: []
files_modified:
  - "src/auth/LoginForm.tsx"
autonomous: true
must_haves: {}
---

Body mirrors the <task> block sub-elements (<read_first>, <action>, <verify>, <acceptance_criteria>, <done>, <output>).

TASK_ID_RE = /^M\d{3,}-S\d{3,}-T\d{4,}$/ — enforced in frontmatter validation.

M<NNN>-PLAN-REVIEW.md

Append-only audit of each planner → plan-checker iteration. Every iteration appends a YAML block:

markdown
## Iteration 1 - 2026-04-20T10:00:00Z

**Planner output:** S001-PLAN.md committed
**Checker verdict:** issues_found
**Findings:**

\`\`\`yaml
status: issues_found
findings:
  - category: missing-success-criterion
    severity: critical
    target: M001-S001-UAT.md §login
    message: No task covers the "locked account" acceptance case.
\`\`\`

**Planner response:** revision

Pre-existing bytes must remain a SHA-256-verified prefix of post-append bytes. Abort never truncates.

M<NNN>-VERIFICATION.md

Schema-bound by lib/schemas/verification.cjs (ADR-0017). Frontmatter is the canonical machine-readable signal; body blocks must match ### SC-N: … (H3, colon). Drift is rejected by output-lint check --schema verification --enforce at write time inside /np:verify-work.

markdown
---
schema_version: 2
milestone: "M001"
milestone_name: "Auth & Basic UI"
verified: "2026-04-20"
milestone_status: verified
sc_total: 2
passed: 1
failed: 1
deferred: 0
pending: 0
---

# M001 — Auth & Basic UI — Verification

**Verified:** 2026-04-20
**Milestone Status:** verified

## Success Criteria

### SC-1: User can log in
- **Status:** Pass
- **Classified by:** verifier
- **Evidence:** src/auth/loginHandler.ts, test/auth.test.cjs "login happy path"
- **Notes:**

### SC-2: Profile visible after login
- **Status:** Fail
- **Classified by:** verifier
- **Evidence:** src/profile/ (empty)
- **Notes:** No profile page shipped in M001; promoted to M002.

Milestone Status derivation (kept in lib/verify.cjs):

  • Any Failfailed.
  • Else any Defer or unresolved needs_user_confirmdeferred.
  • Else (all Pass) → verified.

Frontmatter invariant: sc_total === passed + failed + deferred + pending. The output-lint engine enforces this and rejects [object Object] titles.

M<NNN>-VALIDATION.md

Schema-bound by lib/schemas/validation.cjs (ADR-0017). The np-nyquist-auditor agent writes this; frontmatter holds the canonical counts. The aggregator in /np:close-project reads frontmatter, never body grep — see ADR-0017 §Context for the historical failure.

markdown
---
phase: 1
slug: auth-basic-ui
audited_at: 2026-04-20T14:30:00Z
requirements_total: 12
covered: 9
under_sampled: 2
uncovered: 1
nyquist_compliant: false
status: issues_found
---

## Summary

## Covered

## Under-Sampled

## Uncovered

## Remediation Guidance

Invariant: requirements_total === covered + under_sampled + uncovered. Hard-gated by output-lint check --schema validation --enforce inside /np:validate-phase.

M<NNN>/research/spawn-<i>.md (per-spawn researcher output)

Schema-bound by lib/schemas/researcher-output.cjs (ADR-0018). One file per Stage-1 researcher spawn, written to .nubos-pilot/milestones/M<NNN>/research/spawn-<i>.md. A Reasoning field is required per Decision/Risk/Pattern; the reconciler uses Reasoning traces to classify agreement as orthogonal, overlapping, identical, or unknown.

markdown
---
schema_version: 1
agent: np-researcher
spawn_index: 0
seed_delta: -7
task_query_hash: "abc123..."
decision_count: 2
risk_count: 1
pattern_count: 1
open_question_count: 0
source_count: 2
---

## Decisions

### D-1: Use jose@6 for JWT signing
- **Rationale:** maintained, zero peer deps
- **Confidence:** high
- **Evidence:** [CITED: https://github.com/panva/jose v6.0.10]
- **Reasoning:** compared vs jsonwebtoken (deprecated) and jws (unmaintained); jose is the only modern actively-developed option.

## Risks       # ### R-N with Severity, Mitigation, Reasoning
## Patterns    # ### P-N with Description, Source-Type, Reasoning
## Open Questions  # ### Q-N with Why-blocked
## Sources         # ### S-N with Type, Notes

Each of the five sections must be present (use _None._ if empty). Linted by output-lint check --schema researcher-output --enforce per spawn at workflow Step 4.5.

M<NNN>-RESEARCH.md (reconciler output)

Schema-bound by lib/schemas/research-final.cjs (ADR-0018). Written by np-researcher-reconciler after seeing all k per-spawn outputs plus the deterministic-merge proposal. Frontmatter exposes disagreement metrics that the workflow's Step 5.7 hard-gate reads.

markdown
---
schema_version: 2
milestone: "M001"
type: research
agent: np-researcher-reconciler
k: 3
agreement_score: 0.667
contested_count: 1
reconciler_verdict: issues_flagged
decision_count: 2
risk_count: 1
pattern_count: 1
open_question_count: 0
source_count: 3
---

## Reconciler Summary
## Final Decisions     # ### D-N with Reconciled-from, Confidence, Reasoning-Trace-Agreement, Reasoning
## Contested Decisions # ### CD-N with per-spawn verdicts + Reconciler pick + reason
## Final Risks
## Final Patterns
## Final Open Questions
## Sources

reconciler_verdict ∈ {clean, issues_flagged, needs_re_spawn}. The disagreement gate (researcher-reconcile gate <N>) keys on agreement_score and contested_count.

PROJECT-SUMMARY.md

Project-level aggregate produced by /np:close-project write-summary (ADR-0016). Renders one block per milestone with verification and validation summaries plus a blocker list. Regenerated on each close. It is never authoritative: milestone-level signals live in M<NNN>-VERIFICATION.md.

Templates

All artifact templates ship under templates/:

  • templates/milestone/{CONTEXT.md, ROADMAP.md, META.json}
  • templates/slice/{ASSESSMENT.md, PLAN.md, RESEARCH.md, SUMMARY.md, UAT.md}
  • templates/task/{PLAN.md, SUMMARY.md}
  • templates/VALIDATION.md — Nyquist audit skeleton
  • templates/PROJECT.md, templates/REQUIREMENTS.md — project-level

Template rendering uses lib/template.cjs with placeholders. render() fails loud on unresolved placeholders, so workflows must supply every variable the template declares.

S<NNN>/TODO.md (slice-level rollup)

Per-slice Markdown rollup of task statuses as a checkbox list. Lives at .nubos-pilot/milestones/M<NNN>/slices/S<NNN>/TODO.md.

Producer paths:

  • bin/np-tools/plan-milestone.cjs::_scaffoldAllTasks — initial render for every slice after task scaffolding.
  • lib/tasks.cjs::setTaskStatus — re-renders on every task-status transition (so skip / park / unpark / commit-task all keep it live).
  • bin/np-tools/render-todo.cjs — explicit re-render via node np-tools.cjs render-todo <slice-full-id>.

Consumers: human reader, executor on resume, verifier for overview. There is no parser contract: TODO.md is a derived view, never a source of truth. Task frontmatter remains authoritative.

Schema

markdown
---
schema_version: 1
milestone_id: M<NNN>
slice_id: M<NNN>-S<NNN>
total: <int>
pending: <int>
in_progress: <int>
done: <int>
skipped: <int>
parked: <int>
updated_at: <ISO-8601>
---

# Slice M<NNN>-S<NNN>

- [ ] **M<NNN>-S<NNN>-T0001** — <task name from H1 of T0001-PLAN.md>
- [~] **M<NNN>-S<NNN>-T0002** — ...
- [x] **M<NNN>-S<NNN>-T0003** — ...
- [-] **M<NNN>-S<NNN>-T0004** — ...
- [!] **M<NNN>-S<NNN>-T0005** — ...

Status → checkbox mapping

Task frontmatter statusTODO checkbox
pending[ ]
in-progress[~]
done[x]
skipped[-]
parked[!]

Mapping lives in lib/todo.cjs::STATUS_CHECKBOX.

Task-name extraction

The human-readable name is read from the first # <heading> line of the task's T<NNNN>-PLAN.md. The template format is # <task_full_id> — <task_name>; everything after becomes the name. Empty slices render the literal placeholder _No tasks yet._; a missing H1 renders (unnamed).

Agent Handoff

Persistent agent-to-agent note produced outside the plan/verify directory tree. It carries cross-phase signals that do not fit in commit messages or frontmatter:

  • Executor flags a compromise the verifier must know about → --to np-verifier.
  • Executor flags a plan flaw for the next planner run → --to np-planner.
  • Researcher flags a cross-milestone trap future planners must see → --to np-planner (global, no --milestone).
  • Planner clarifies SC interpretation for the verifier → --to np-verifier.
  • Shared-code trap applies broadly → --to "*" (broadcast).

Location

  • Milestone-scoped (default when --milestone M<NNN>): .nubos-pilot/milestones/M<NNN>/handoffs/
  • Project-global (no --milestone): .nubos-pilot/handoffs/

Filename

<iso-8601>__<from_agent>-to-<to_agent>__<topic-slug>__<id>.md

Example: 2026-04-23T11-48-26-642Z__np-executor-to-np-verifier__feature-flag-x__c209db90.md

ISO colons and dots are replaced with - for filesystem safety. Within one Node process, timestamps are monotone (lib/handoff.cjs bumps to _lastTs + 1ms if Date.now() hasn't advanced) so rapid writes stay chronologically sortable. The trailing <id> is an 8-char hex tie-breaker for the edge case of parallel writers landing on the same millisecond.

Frontmatter schema

yaml
---
schema_version: 1
id: <8-char hex>
from_agent: <agent slug>
to_agent: <agent slug | "*" for broadcast>
topic: <quoted string>
created_at: <ISO-8601>
milestone: M<NNN> | null
slice: M<NNN>-S<NNN> | null
task: M<NNN>-S<NNN>-T<NNNN> | null
status: open | read | acted | archived
---

<freeform Markdown body — the actual message>

Status lifecycle

StatusMeaning
openDefault on write. Not yet looked at by the target agent.
readTarget agent loaded the body but hasn't acted yet.
actedTarget agent integrated the signal into its work.
archivedKept for record but no longer actionable.

Transitions: handoff-status <id> <new-status>lib/handoff.cjs::setHandoffStatus rewrites the status: line in-place without touching other frontmatter fields.

Agent-name validation

from_agent / to_agent must match /^[a-zA-Z0-9_\-\*]+$/. Spaces, slashes, and other punctuation raise handoff-invalid-agent.

Listing semantics

handoff-list without --milestone and without --global scans every milestone's handoffs/ directory plus the project-global .nubos-pilot/handoffs/. --milestone M<NNN> narrows to that milestone only; --global narrows to non-milestone-scoped only. Results are sorted by created_at, tie-broken by id.

--for <agent> filters to entries where to_agent equals the agent name or equals * (broadcast). --status <s> filters on the current status value.

Agent-prompt contract

Each agent's prompt documents what it reads and what it may write:

  • np-executor — reads handoffs at start; may write to np-verifier (compromises), np-planner (plan flaws), or * (shared-code traps).
  • np-verifier — read-only; folds consumed handoffs into its evidence. Handoffs from executors often flip what would otherwise read as Fail to Pass or Defer by explaining a deliberate compromise.
  • np-planner — reads handoffs at start; may write to np-executor (scope nuances) or np-verifier (SC interpretation).
  • np-researcher — reads handoffs at start; may write to np-verifier (evidence hints) or np-planner (cross-milestone traps, typically global — no --milestone).

See agents/np-*.md → "Handoff Protocol" sections for the exact CLI invocations each agent is told to run.