Request for Comments
beta · Family decision-docs · Phase develop · Sizes lean, full · ~2,300 tokens
Proposes a change and gathers input before the decision is made: the motivation, the proposal, the alternatives, the trade-offs, the open questions, and a place to record the outcome. The pre-decision counterpart to the ADR, which records a decision after it is made.
Fast reference for using the RFC bundle. For the full reasoning, history, and sources, read
rfc_companion.md.
When to use
Section titled “When to use”- You want to make a change that affects people beyond yourself, and their input could improve it or catch a flaw before you build.
- The decision is not yet made, and you genuinely want to shape it with feedback. (If it is made, see “When NOT to use.”)
- More than one reasonable approach exists, and choosing well matters.
- You need durable alignment across people who were not in the room, and a record of why.
When NOT to use
Section titled “When NOT to use”- The decision is already made. Writing an RFC to perform consultation that already happened is RFC theater; everyone can tell. Write an ADR instead.
- The change is trivial or fully reversible. A quick message or a pull-request description is enough. An RFC for a one-line, back-out-in-a-minute change is process for its own sake.
- It affects only you or only your immediate team, with no external impact. Talk it through and record it lightly.
- Speed is the binding constraint and the RFC’s review period would cost more than the mistake it prevents. Some decisions are cheaper to make and reverse than to circulate.
- You have no decider and no deadline. Do not start an RFC you cannot finish; a process with no authority to decide produces open threads, not decisions.
- You mean ITIL’s RFC. In IT service management the same three letters stand for a request for change, a different document that asks an authority to approve altering something already agreed or already running. For a change to an agreed scope, requirement or plan, use a change request; a change to a running production system is IT service change management, which this library does not template.
RFC or ADR? (the question people actually have)
Section titled “RFC or ADR? (the question people actually have)”| RFC | ADR | |
|---|---|---|
| Timing | Before the decision | After the decision |
| Question | “Should we, and how?” | “What did we decide, and why?” |
| State | Mutable while open | Immutable once accepted |
| Asks for | Input from an audience | Nothing; it records |
| Lives | In shared docs / discussion | Next to the code, in docs/internal/decisions/ |
They are a sequence, not a choice: RFC to decide, ADR to record. An accepted RFC often produces one or more ADRs. If you only adopt one, use RFC-for-discussion and ADR-for-record.
Pick a variant
Section titled “Pick a variant”- Lean (default): Summary, Motivation, Proposal, Alternatives Considered, Open Questions, Outcome. Enough for most changes worth an RFC at all.
- Full: adds Goals and Non-Goals, Detailed Design, Drawbacks and Trade-offs, and Rollout and Adoption. Use it when the change is hard to reverse, crosses teams, carries security/privacy/ regulatory weight, or reasonable people will disagree.
Grow lean into full by adding sections; never reorder the shared ones. The scaling signal is the cost of being wrong, not the size of the system.
Quality rubric (self-grade before you circulate)
Section titled “Quality rubric (self-grade before you circulate)”- The summary contains the proposal, not just the problem.
- The motivation would survive a different solution winning. It describes the problem, not a case for your favorite option.
- Alternatives are real, including “do nothing,” and each is described well enough that a reader could prefer it. No straw men.
- The open questions are genuinely open, and they point reviewers at where their input matters. The section is not empty or “None.”
- (Full) Non-goals are stated, so the discussion stays bounded.
- (Full) At least one real drawback of the proposal is named, one someone would act on.
- (Full) The rollout has an owner, a sequence, and a backout.
- There is a named decider and a review-by date (or, for lean, at least a clear owner).
- Status is current, and the Outcome section is present even while the RFC is open.
- The proposal is concrete enough to disagree with.
- Every guidance comment is deleted; no placeholders remain.
Named anti-patterns (the usual wrecks)
Section titled “Named anti-patterns (the usual wrecks)”- The RFC with no decider. No approver, no deadline; the thread never closes. The default outcome is nothing.
- The rubber stamp. Written after the decision; no live questions, decoy alternatives. Write an ADR instead.
- The comment pile-on. Reviewers flood it, blocking and non-blocking feedback indistinguishable. Fix with named approvers and “yes, if.”
- Writing to exhaustion. Burying objections under volume until reviewers give up and approve.
- Template bloat. Ten mandatory sections a small change cannot justify; people route around the process. Use lean.
- No alternatives. The proposal cannot show it was reasoned; reviewers supply them adversarially.
- No recorded outcome. Abandoned at
in-review; nobody can later tell what was decided. Fill in Outcome and update status the moment it is decided.
No paired skill (yet)
Section titled “No paired skill (yet)”Unlike the ADR bundle, which pairs with the develop-adr skill, there is no develop-rfc skill
in the product-on-purpose org today, so this bundle’s pairs_with is empty. That is a genuine gap:
the org has a skill for the record (ADR) but none for the proposal (RFC) that precedes it. Until
one exists, this template is filled by hand. The gap is noted as a finding in STATE.md.
The artifacts
Section titled “The artifacts”rfc_template-lean.md · ~2,300 tokens
---title: "{{title}}"status: "{{status}}"authors: ["{{authors}}"]created: "{{date}}"updated: "{{date}}"rfc_id: "{{rfc_id}}"doc_type: rfcsize: leansource_template: rfcsource_template_version: 0.1.0---
<!--LEAN RFC. The smallest proposal that can still gather real feedback and reach a decision. Use itfor a change that affects more than your own corner but is not a multi-team, hard-to-reversecommitment. To grow it into a full RFC, ADD sections (see rfc_template-full.md); never rename orreorder the ones below, because the full variant is a strict superset of this one.
WHAT AN RFC IS, AND IS NOTAn RFC proposes a change and asks for input BEFORE the decision is made. That is the whole point,and it is the opposite of an ADR, which RECORDS a decision after it is made. If the decision isalready taken and you are writing this to look consultative, you are writing RFC theater, andeveryone can tell. Write an ADR instead.
STATUS is a lifecycle, not a label. Move it as the RFC moves: draft -> in-review -> accepted | rejected | withdrawn (and later, possibly, superseded)The status field in the frontmatter is the single most important thing to keep current. A stalestatus is how an RFC graveyard forms.
HOW TO FILL THIS IN1. Read the comment under each heading: WHAT it wants, WHY it matters (with a pointer into rfc_companion.md for the deep reasoning), guiding questions to ASK, a GOOD and a WEAK example, and the TRAP to avoid.2. Replace each {{placeholder}} with your content.3. If a section does not apply, write "N/A" and one line of why, rather than deleting it.4. Before you circulate it: self-grade against rfc_guide.md, then DELETE every HTML comment. They are guidance, not content.-->
# {{title}}
## Summary
<!-- WHAT Two or three sentences: what you are proposing and what changes if it is accepted. A reader should grasp the whole idea before any detail. WHY The summary is the triage surface. Most people decide from it whether to engage, and an RFC's entire value is the engagement it attracts. If you cannot summarize the proposal, it is not shaped enough to circulate. Deep dive: rfc_companion.md section 3 (Anatomy > Summary). ASK What are you proposing? What becomes true if it ships? Who should care? GOOD "Propose moving service-to-service auth from shared static tokens to short-lived mTLS certs issued by an internal CA, so a leaked credential expires in minutes rather than living until someone rotates it by hand." WEAK "We should improve our auth." (a wish, not a proposal) TRAP Writing the summary as a problem statement with no proposal in it. The Motivation section is for the problem; the summary must contain the actual proposed change. -->
{{summary}}
## Motivation
<!-- WHAT The problem or opportunity, and why it is worth solving now. The forces in play: technical, product, organizational. What breaks or stays broken if nobody acts. WHY Motivation is what earns the reader's attention and what a reviewer argues with first. A proposal whose motivation is thin gets bikeshed on the solution, because there is nothing more substantial to engage. Spend real effort here. Deep dive: rfc_companion.md section 3 (Anatomy > Motivation). ASK What is wrong today? Who feels it, and how often? What is the cost of doing nothing? Why now rather than next quarter? GOOD "Static service tokens have leaked twice this year via logs. Each rotation is a manual, coordinated, multi-team scramble that takes a day. As we add services, the blast radius of a single leak grows and the rotation cost grows with it." WEAK "Our current approach is not best practice." (an appeal to fashion, not a cost) TRAP Motivation written backwards from the solution you already like, so it only justifies that one option. Describe the problem so honestly that a DIFFERENT solution could win. -->
{{motivation}}
## Proposal
<!-- WHAT The proposed change, at the level of a shared mental model: the core idea and how people experience it. Enough that a reader can reason about it, not every implementation detail. WHY This is the object of discussion. It should be concrete enough to disagree with. A vague proposal produces vague feedback and no decision. Deep dive: rfc_companion.md section 3 (Anatomy > Proposal). ASK What exactly changes? What is the shape of the solution? What is explicitly in, and what is left for later? GOOD "Stand up an internal CA. Each service gets a workload identity and requests a cert valid for 15 minutes, auto-renewed by a sidecar. The mesh rejects any connection without a valid cert. Rollout is per-namespace behind a flag, dual-running static tokens until each namespace is cut over." WEAK "Adopt mTLS." (names a technology, not a proposal anyone can evaluate) TRAP Jumping to the lowest-level detail before the shape is clear, or leaving it so abstract that no one can find anything to push on. -->
{{proposal}}
## Alternatives Considered
<!-- WHAT The other options you genuinely weighed, and why you are not proposing them. Include "do nothing" when it is a real option, which is usually. WHY The alternatives are the strongest evidence that the proposal is the result of thinking rather than preference, and they are what let a reviewer surface the option you missed. An RFC with no alternatives invites the reader to supply them, adversarially. Deep dive: rfc_companion.md section 3 (Anatomy > Alternatives Considered); the missing- alternatives anti-pattern is in section 7. ASK What else could solve the motivation? What is the obvious option you rejected, and why? What would a skeptic propose instead? Is "do nothing" acceptable, and why not? GOOD "Rotate static tokens automatically (rejected: reduces but does not remove leak lifetime; still a shared secret). Buy a vendor mesh (rejected: cost, and lock-in for a capability we can run). Do nothing (rejected: leak cost is already being paid, twice this year)." WEAK One decoy option dismissed in half a sentence. TRAP Straw men. Listing only options you would never pick makes the proposal look inevitable, which reads as a sales pitch and costs you the reviewer's trust. -->
{{alternatives_considered}}
## Open Questions
<!-- WHAT What is genuinely undecided or unknown, that you want input on. Each as a real question, ideally with who might answer it. WHY The open questions are the actual request for comment. They tell reviewers exactly where their input changes the outcome, which is how you get engagement instead of a silent rubber stamp. An RFC with no open questions is usually hiding them. Deep dive: rfc_companion.md section 3 (Anatomy > Open Questions). ASK What are you unsure about? Where could a reviewer change your mind? What did you punt on? GOOD "How do we bootstrap identity for a brand-new service before it has a cert? Do we need to support non-mesh clients, and if so how do they authenticate? What is the CA's own rotation and recovery story?" WEAK An empty section, or "None" on a proposal that obviously has unknowns. TRAP Hiding the hard questions to look more finished. A confident-looking RFC with no open questions gets rubber-stamped, and the questions surface later as production incidents. -->
{{open_questions}}
## Outcome
<!-- WHAT The decision, once it is made: accepted, rejected, or deferred; who decided; the date; and a one-line why. While the RFC is open, this says so. WHY An RFC with no recorded outcome is the single most common way the format fails. The discussion happens, a decision is reached in someone's head or a meeting, and the document is abandoned mid-thread, so the next person cannot tell what was decided or why. This section is what turns a proposal into a durable record and what an ADR is later written FROM. Deep dive: rfc_companion.md section 3 (Anatomy > Outcome) and the RFC-to- ADR relationship in section 8. ASK Has a decision been made? By whom, and when? If accepted, what happens next (an ADR, a tracking issue)? If deferred, what would reopen it? GOOD "Accepted 2026-05-02 by the Platform review group. Rollout tracked in PLAT-1421. The decision itself is recorded as ADR 0031; this RFC is its rationale trail." WEAK Deleting this section because "the RFC is still open." Keep it, and write "Status: in review. No decision yet." TRAP Leaving this blank forever. An RFC that never records its outcome is indistinguishable from one that was ignored. Set the status and write the one line the moment it is decided. -->
{{outcome}}rfc_template-full.md · ~3,200 tokens
---title: "{{title}}"status: "{{status}}"authors: ["{{authors}}"]reviewers: ["{{reviewers}}"]created: "{{date}}"updated: "{{date}}"review_by: "{{date}}"rfc_id: "{{rfc_id}}"doc_type: rfcsize: fullsource_template: rfcsource_template_version: 0.1.0---
<!--FULL RFC. Every section, for a change that earns the weight: hard to reverse, crosses teams,carries regulatory or security consequence, or expensive if it goes wrong. Most changes do notearn it. Reaching for this variant by reflex is how an RFC process turns into the bureaucracy itscritics warn about, where writing to exhaustion substitutes for deciding.
The full variant is a strict superset of the lean one: the shared sections keep their names andtheir order, and this file only ADDS (Goals and Non-Goals, Detailed Design, Drawbacks andTrade-offs, Rollout and Adoption).
WHAT AN RFC IS, AND IS NOTAn RFC proposes a change and asks for input BEFORE the decision is made. That is the opposite ofan ADR, which RECORDS a decision after it is made. If the decision is already taken, do not dressit as an RFC; write an ADR.
STATUS is a lifecycle, not a label. Keep it current: draft -> in-review -> accepted | rejected | withdrawn (and later, possibly, superseded)Name real reviewers and a real review-by date. An open-ended comment period is how an RFC dies ofendless discussion; a named decider and a deadline are how it reaches a decision.
HOW TO FILL THIS IN1. Read the comment under each heading: WHAT it wants, WHY it matters (with a pointer into rfc_companion.md for the deep reasoning), guiding questions to ASK, a GOOD and a WEAK example, and the TRAP to avoid.2. Replace each {{placeholder}} with your content.3. If a section does not apply, write "N/A" and one line of why, rather than deleting it.4. Before you circulate it: self-grade against rfc_guide.md, then DELETE every HTML comment.-->
# {{title}}
## Summary
<!-- WHAT Two or three sentences: what you are proposing and what changes if it is accepted. The whole idea, graspable before any detail. WHY The summary is the triage surface; most reviewers decide from it whether to engage, and engagement is the entire value of an RFC. Deep dive: rfc_companion.md section 3 (Anatomy > Summary). ASK What are you proposing? What becomes true if it ships? Who should care? GOOD "Propose moving service-to-service auth from shared static tokens to short-lived mTLS certs issued by an internal CA, so a leaked credential expires in minutes." WEAK "We should improve our auth." (a wish, not a proposal) TRAP A summary that states the problem but never the proposed change. -->
{{summary}}
## Motivation
<!-- WHAT The problem or opportunity and why it is worth solving now: the forces (technical, product, organizational) and the cost of doing nothing. WHY Motivation earns the reader's attention and is what a reviewer argues with first. Thin motivation invites bikeshedding on the solution. Deep dive: rfc_companion.md section 3 (Anatomy > Motivation). ASK What is wrong today? Who feels it, how often, at what cost? Why now? GOOD "Static service tokens have leaked twice this year via logs; each rotation is a manual multi-team scramble, and the blast radius grows with every service we add." WEAK "Our current approach is not best practice." (fashion, not cost) TRAP Motivation reverse-engineered from your preferred solution, so only that option fits. -->
{{motivation}}
## Goals and Non-Goals
<!-- WHAT Goals: what the proposal must achieve to succeed, ideally observable. Non-goals: what it is deliberately NOT trying to do, to keep the discussion bounded. WHY Non-goals are the highest-value line of scope control in an RFC. Most runaway comment threads are reviewers arguing about something the author never intended to address; naming it a non-goal ends that thread before it starts. Deep dive: rfc_companion.md section 3 (Anatomy > Goals and Non-Goals). ASK What must be true for this to have worked? What are we explicitly not solving here, and why? What is a reasonable-but-separate concern? GOOD Goal: "Any leaked service credential is unusable within 15 minutes." Non-goal: "End-user auth. This RFC is service-to-service only; user sessions are out of scope and unchanged." WEAK A goals list with no non-goals, so every reviewer expands the scope in a different way. TRAP Omitting non-goals. It is the single most reliable cause of an RFC that will not converge. -->
{{goals_and_non_goals}}
## Proposal
<!-- WHAT The proposed change at the level of a shared mental model: the core idea and how people experience it. Concrete enough to disagree with. WHY This is the object of discussion. A vague proposal produces vague feedback and no decision. Deep dive: rfc_companion.md section 3 (Anatomy > Proposal). ASK What exactly changes? What is the shape? What is in, and what is deferred? GOOD "Stand up an internal CA. Each service gets a workload identity and a 15-minute cert, auto-renewed by a sidecar; the mesh rejects any connection without a valid cert." WEAK "Adopt mTLS." (a technology name, not an evaluable proposal) TRAP Lowest-level detail before the shape is clear, or so abstract no one can push on it. -->
{{proposal}}
## Detailed Design
<!-- WHAT The how, for reviewers who must implement or integrate: interfaces, data flows, failure modes, migration, security and privacy surface. The parts a careful reviewer will probe. WHY This is where a proposal that sounded fine reveals whether it actually works. The detail is what lets a domain expert catch the flaw now, on paper, instead of in production. It is also the section that most tempts over-writing; include what a reviewer needs to find problems, not everything you know. Deep dive: rfc_companion.md section 3 (Anatomy > Detailed Design). ASK What are the interfaces and contracts? What are the failure modes and how are they handled? How does data migrate? What is the security, privacy, and compliance surface? GOOD "Cert issuance flow (sequence). Sidecar renewal at 50% of TTL with jitter. CA outage behavior: existing certs remain valid to TTL; issuance pauses; a documented break-glass path. Identity bootstrap for new workloads via the orchestrator's attestation." WEAK Repeating the Proposal at more length without adding anything a reviewer can test. TRAP Using length as a weapon. An exhaustive design that no one can finish reading is how RFCs get approved by fatigue rather than by understanding. Detail the risky parts. -->
{{detailed_design}}
## Alternatives Considered
<!-- WHAT The other options you genuinely weighed and why you are not proposing them. Include "do nothing" when it is real, which is usually. WHY Alternatives are the evidence the proposal is reasoned rather than preferred, and they are how a reviewer surfaces the option you missed. Their absence invites the reader to supply them adversarially. Deep dive: rfc_companion.md section 3 (Anatomy > Alternatives Considered); the anti-pattern is in section 7. ASK What else could meet the goals? What obvious option did you reject, and why? What would a skeptic propose? Why not "do nothing"? GOOD "Auto-rotate static tokens (rejected: still a shared secret). Vendor mesh (rejected: cost, lock-in). Do nothing (rejected: the leak cost is already being paid)." WEAK One decoy dismissed in half a sentence. TRAP Straw men. Two decoys and a winner is a sales pitch, not an RFC. -->
{{alternatives_considered}}
## Drawbacks and Trade-offs
<!-- WHAT The honest costs of the proposal itself: what gets worse, harder, or riskier if it is accepted. The price you are asking the org to pay. WHY The drawbacks are what make the RFC trustworthy. A proposal presented with no downsides tells reviewers you are selling, not proposing, and it invites them to go find the cost you hid. Naming it yourself is disarming and faster. Deep dive: rfc_companion.md section 3 (Anatomy > Drawbacks and Trade-offs). ASK What does this make worse? What new operational burden, dependency, or risk does it add? Who pays, and how much? What could go wrong at scale? GOOD "Adds a CA as a new critical dependency, with its own uptime and recovery burden. Adds a sidecar to every pod (memory, one more failure surface). A CA compromise is now a system-wide event. The mesh becomes a hard requirement for all service traffic." WEAK "There is a learning curve." (a cost nobody would act on) TRAP A drawbacks section so weightless it is really an advantages section in disguise. If you cannot name a real cost, you have not understood the proposal yet. -->
{{drawbacks_and_tradeoffs}}
## Open Questions
<!-- WHAT What is genuinely undecided or unknown, that you want input on. Real questions, ideally with who might answer. WHY The open questions are the literal request for comment: they point reviewers at exactly where their input changes the outcome. An RFC with none is usually hiding them. Deep dive: rfc_companion.md section 3 (Anatomy > Open Questions). ASK What are you unsure about? Where could a reviewer change your mind? What did you punt on? GOOD "How do we bootstrap identity for a brand-new service? Must we support non-mesh clients? What is the CA's own rotation and recovery story?" WEAK "None," on a proposal that plainly has unknowns. TRAP Hiding hard questions to look finished. They resurface later as incidents. -->
{{open_questions}}
## Rollout and Adoption
<!-- WHAT How the change actually reaches production and gets adopted: sequencing, flags, migration, backout, and how you will know it is working. Who has to do what. WHY Many RFCs die at rollout, not at design: the idea is sound but no one owns the messy path to production, so it never ships. A credible adoption plan is often what separates an RFC that happens from one that is merely admired. Deep dive: rfc_companion.md section 3 (Anatomy > Rollout and Adoption). ASK What is the sequence? What is behind a flag? How do teams migrate, and who supports them? What is the backout plan if it goes wrong? How do you measure success? GOOD "Per-namespace behind a flag, dual-running static tokens until each namespace is cut over. Backout: disable the flag, fall back to tokens. Success: 100% of service traffic on mTLS, zero manual token rotations, by Q3. Platform owns the CA; each team owns its cutover." WEAK "Roll it out gradually." (names no sequence, owner, or backout) TRAP Treating rollout as an afterthought. The gap between an accepted design and a shipped change is where most proposals quietly die. -->
{{rollout_and_adoption}}
## Outcome
<!-- WHAT The decision, once made: accepted, rejected, or deferred; who decided; the date; a one-line why; and what happens next. While the RFC is open, this says so. WHY An RFC with no recorded outcome is the most common way the format fails: the discussion happens, the decision is reached somewhere off the page, and the document is abandoned mid-thread. This section turns the proposal into a durable record and is what an ADR is later written FROM. Deep dive: rfc_companion.md section 3 (Anatomy > Outcome); the RFC-to- ADR relationship is in section 8. ASK Has a decision been made? By whom, when? If accepted, what is the resulting ADR or tracking issue? If deferred, what reopens it? GOOD "Accepted 2026-05-02 by the Platform review group, on condition that the break-glass path is designed first (open question 3). Rollout tracked in PLAT-1421. Recorded as ADR 0031." WEAK Blank, because "it is still open." Instead: "Status: in review, decision due 2026-05-02." TRAP Never filling this in. An RFC that does not record its own outcome is indistinguishable from one that was ignored. Set the status and write the line the moment it is decided. -->
{{outcome}}---title: "A metadata schema so agents can select bundles deterministically"status: acceptedauthors: ["jprisant"]reviewers: ["jprisant", "claude"]created: "2026-07-16"updated: "2026-07-17"review_by: "2026-07-30"rfc_id: "RFC-0001"doc_type: rfcsize: fullsource_template: rfcsource_template_version: 0.1.0---
# A metadata schema so agents can select bundles deterministically
## Summary
Propose a machine-readable metadata schema for the template library: a single JSON Schema that everybundle's `<type>_meta.yaml` must validate against, plus a generated `index.json` that lists everybundle with its selectable fields. The goal is that an agent can pick the right bundle for a task byreading structured data, rather than by parsing prose or guessing from filenames. This RFC proposesthe shape of that schema and the mechanism; it does not yet propose the field-by-field contents.
## Motivation
The library's front door claims to be "agent-native," and today that claim is on credit. There is nomachine-consumption path: an agent that wants the right template for "document an architecturedecision" has no structured way to discover that `adr` is the answer. It must read the catalog prose,or the companion files, or guess. Every bundle already ships a `<type>_meta.yaml`, but nothingdefines what fields are required, what their legal values are, or how an agent should query acrossbundles. The `sizes_available` field is the only one the gate understands, and it understands itthrough a regular expression.
The cost of the gap grows with every bundle. Five bundles are hand-tractable; twenty-seven Tier-1types are not. Without a schema, each new bundle's meta drifts a little from the last, and the day wewant an agent (or an `npx` installer, or a marketplace listing) to consume the library, we will beretrofitting a contract onto twenty-seven inconsistent files instead of validating one.
## Goals and Non-Goals
**Goals**- An agent can select a bundle by reading structured metadata alone, with no prose parsing.- Every bundle's meta is validated against one schema, in CI, so drift is caught the way YAML validity now is (check G).- The schema is versioned, so it can evolve without silently breaking consumers.
**Non-Goals**- Deciding the full field list. This RFC proposes the *mechanism and shape*; the exact required fields, enums, and their semantics are follow-on work (and at least one of them, whether `phase` is a required enum, is blocked on the open question TX-1 in `STATE.md`).- A distribution surface (`npx skills add`, a marketplace manifest). Those consume the schema but are separate decisions.- Runtime tooling that selects bundles. This defines the data an agent reads; it does not build the agent.
## Proposal
Two artifacts, both in `tools/` or a new `schema/` directory:
1. **`meta.schema.json`**, a JSON Schema (draft 2020-12) describing a valid `<type>_meta.yaml`. It codifies what already exists across the five bundles (`id`, `title`, `summary`, `doc_type`, `family`, `sizes_available`, `status`, `template_version`, `tags`, `related_templates`, `aliases`, `catalog_ref`, `license`) plus the fields whose presence is currently inconsistent (`phase`, `pairs_with`, `methodology`, `maintainer`, `last_reviewed`), marking each required or optional and giving enums where the values are closed.
2. **`index.json`**, generated by a script from every `<type>_meta.yaml`, listing each bundle with the subset of fields an agent needs to select: `id`, `title`, `summary`, `doc_type`, `phase`, `family`, `aliases`, `tags`, `sizes_available`, `status`. Generated, never hand-edited, so it cannot drift from the metas it summarizes.
The gate gains a check H: every `<type>_meta.yaml` validates against `meta.schema.json`, and`index.json` is up to date with the metas on disk (the same generated-and-checked pattern thedashes and links gates already use). Schema validation needs a JSON Schema library, which is the samedependency question ADR 0014 answered for PyYAML; this RFC's Detailed Design treats that as settledprecedent, not a new fight.
## Detailed Design
**Schema location and versioning.** `schema/meta.schema.json`, with a `$id` carrying a version(`.../meta.schema.v1.json`). A bundle meta declares which schema version it targets via a`meta_schema_version` field, so a v2 schema can be introduced without a flag day: bundles migrate oneat a time, and the gate validates each against the version it declares.
**Field treatment.** Required fields fail the build if missing. Enums (`status`:beta/stable/deprecated; `doc_type`: the bundle id; `sizes_available`: the two known vocabularies) failon an illegal value. `catalog_ref` is validated as an integer in range. `pairs_with` entries, ifpresent, are validated for shape but not resolved against pm-skills (cross-repo resolution is out ofscope and belongs to a separate check).
**index.json generation.** A script walks `templates/*/`, loads each meta, and emits the selectablesubset. The gate runs it with a `--check` flag that fails if the committed `index.json` differs fromfreshly generated output, so the index is always current without a human remembering to regenerateit.
**Failure modes.** A meta that targets an unknown schema version fails clearly. A meta missing arequired field names the field and the bundle. A stale `index.json` prints the diff. None of thesedegrade silently.
**Security and privacy.** None of consequence: the metadata is public repository content by design.
## Alternatives Considered
- **A single generated catalog index, no per-meta schema.** Generate `index.json` from the metas and call it done, without a JSON Schema validating each meta. Rejected: it makes the index consumable but does nothing to stop the metas themselves from drifting, so the index would faithfully report inconsistent data. The schema is the part that keeps the inputs honest.
- **Adopt an existing standard** (Backstage catalog-info, JSON Resume-style, a plugin manifest format). Rejected for now: none of the surveyed standards fits a document-template library without distortion, and adopting one would import a vocabulary that fights the catalog's own. Worth revisiting if a distribution target (a marketplace) requires a specific format.
- **Do nothing; keep meta as convention.** Rejected: "agent-native" stays on credit, and the retrofit cost grows with every bundle. The whole point of building the schema at five bundles rather than twenty-seven is that the retrofit is cheap now and expensive later.
- **A richer schema language than JSON Schema** (e.g. CUE). Rejected: JSON Schema is boring, ubiquitous, and has validators everywhere an agent might run, which matters more than expressive power for a contract this simple.
## Drawbacks and Trade-offs
- Adds a **second gate dependency** (a JSON Schema validator) on top of PyYAML. ADR 0014 set the precedent that a justified single dependency is acceptable, but each one chips further at the "pure stdlib" property, and this is the second chip.- **The schema becomes a contract that constrains every future bundle.** That is the point, but it means a genuinely novel bundle type that does not fit the schema is now a schema change, not just a new folder. Versioning mitigates this; it does not remove it.- **`index.json` is a generated file in version control**, which some consider an anti-pattern (build output in the repo). The alternative, generating it in CI and publishing elsewhere, needs a distribution surface this repo does not have yet, so committing it is the pragmatic interim.- Codifying the current fields **freezes some accidents.** A few meta fields exist because the first bundle happened to include them, not because they were designed. Writing the schema forces a reckoning with which fields are real, which is good, but it is work the RFC's field-list follow-on must actually do rather than rubber-stamp.
## Open Questions
- **Is `phase` required?** This is blocked on TX-1 in `STATE.md`: pm-skills carries a two-axis taxonomy (some skills have a lifecycle `phase`, some instead have a `classification`), and this library has never decided whether every document type has a phase to declare. A glossary or a team charter may not. `phase` cannot become a required enum until TX-1 is settled. What should the schema do in the meantime: make `phase` optional, or block this RFC on TX-1?- **Where does the schema live**, `schema/` or `tools/`? Small, but it sets a convention.- **Should `index.json` include the companion's one-line summary or a longer selection blurb?** The richer the index, the better an agent selects, but the more it duplicates the metas.- **Does adopting a JSON Schema validator reopen ADR 0008/0014**, or is it covered by 0014's precedent? The Detailed Design assumes covered; a reviewer may disagree.
## Rollout and Adoption
1. Land `meta.schema.v1.json` codifying the current five bundles' fields, with everything contested (notably `phase`) marked optional, so all five validate on day one.2. Add gate check H (schema validation) and the `index.json` generate-and-check, behind no flag: the five existing bundles must pass before merge.3. Generate the first `index.json`. From here, every new bundle validates against the schema as part of its Definition of Done.4. Once TX-1 is settled, tighten the schema (e.g. make `phase` required or introduce the second axis) as a v2, and migrate bundles one at a time.
Backout: check H is additive; if the schema proves wrong, remove the check and the schema filewithout touching any bundle. Success: an agent can select a bundle from `index.json` alone, and nometa can merge that violates the schema.
## Outcome
**Status: accepted (2026-07-17).** Decision owner: jprisant. Decided ahead of the 2026-07-30 target.
Accepted, and implemented. `tools/meta.schema.json` (JSON Schema draft 2020-12) now validates everybundle's `<type>_meta.yaml` in CI, via the gate's new check J. The decision is recorded as[ADR 0016](../../docs/internal/decisions/0016-adopt-machine-checkable-metadata-schema.md), written fromthis Outcome exactly as the bundle teaches that an accepted RFC's Outcome is what an ADR is written from,and the second gate dependency it needs is recorded as[ADR 0017](../../docs/internal/decisions/0017-gate-may-use-jsonschema-for-meta-validation.md). The onehard blocker, whether `phase` could be required, had already been settled by[ADR 0015](../../docs/internal/decisions/0015-second-taxonomy-axis-phase-xor-classification.md): a metadeclares `phase` XOR `classification`, which the schema encodes.
Three of this RFC's open questions were answered in the deciding. The schema lives in `tools/`, besidethe gate that reads it, not a new `schema/` directory. The per-meta `meta_schema_version` field isdeferred until a real v2 migration needs it, rather than added speculatively. And the JSON Schemavalidator dependency is covered by ADR 0014's precedent, as the Detailed Design assumed, now confirmed byADR 0017. The proposal's second artifact, a generated catalog, followed in roadmap WP-22: it ships as`manifest.json` ([ADR 0018](../../docs/internal/decisions/0018-machine-catalog-generated-manifest.md)),the name reconciled from this RFC's `index.json`. The schema, the half that keeps the metas honest,shipped first.
This example is left standing at `accepted` on purpose. An RFC that records its own outcome, and whoseoutcome became an ADR, is the complete lifecycle the template exists to demonstrate.Provenance
Section titled “Provenance”The reasoning, the history and every source, in the repository:
- Companion - the long-form argument: why these sections, where the sources disagree, and what the bundle refuses to claim
- History - what changed in this bundle, and when
- Research log - every source consulted, with what each one actually supports
- Catalog metadata - the machine-readable record this page is generated from
Catalog record: 10 sections across 1 format(s).