RFCs as a Delivery Tool, Not Paperwork
A practical guide to engineering RFCs: when to write one, how to keep it lightweight, and a template that reduces risk without slowing delivery.
RFCs as a Delivery Tool, Not Paperwork
I used to think RFC meant heavy process: long docs, big meetings, and a faint smell of bureaucracy. Then I noticed something annoying in real projects: the no-RFC path is not faster. It just postpones the conversation until the most expensive moment, after code is written, dependencies are touched, and rollout plans are already emotionally locked in.
So I treat an engineering RFC as a delivery tool. It is not there to impress anyone. It is there to surface risk while change is still cheap, and to create a durable shared understanding you can link to later.
Historically, RFCs literally began as a way to share evolving technical ideas for the Internet, notes intended to invite feedback, not declare perfection. That request-for-comments spirit matters more than the acronym. (IETF)
Why an RFC speeds delivery
An RFC earns its keep when it reduces churn.
If you have ever shipped a feature that worked but triggered late surprises, like QA environment failure, production readiness gaps, misunderstood data contracts, or integration assumptions, then you already know the price of not writing things down early. The cost is not the doc. The cost is rework plus coordination drag.
I also like RFCs because they create a clean timeline of intent. Michael Nygard’s classic argument for decision records is basically: large documents rot; small modular documents can stay current, and future teammates can understand why you did what you did. That is exactly the goal here. (Cognitect)
RFC vs ADR vs design doc
I do not get religious about naming, but I do keep the shape consistent.
An ADR captures one significant decision with context and consequences. That is perfect when the key value is why we chose X. (Cognitect)
An RFC is broader: it is the proposal plus feedback loop, usually including scope, rollout, and risk.
A design doc is often an RFC with more narrative. In practice, teams blend these. What I care about is that the document is short, reviewable, and directly tied to delivery.
Practical triggers for writing an RFC
Here is the rule I actually use: I write an RFC when a change crosses a boundary, between systems, between teams, between versions, or between expectations.
External integrations. If we touch an outside API, SSO, payment provider, message broker, or any system we do not fully control, we write an RFC. The point is to make assumptions explicit: auth model, retries, timeouts, idempotency, failure modes, and who owns what during incidents.
Schema changes. If a database schema changes, or an event payload changes, or anything that could break a consumer, I write an RFC. Schema changes are never just internal. They are time bombs unless you define compatibility strategy and migration order.
Data contracts. If another team’s code depends on the shape or meaning of our data, that is a contract. Contracts deserve a stable place to live: versioning approach, deprecation policy, examples, and test/validation strategy.
Risk or blast radius. If a failure could cause user-facing impact, money impact, or operational escalation, that is an RFC trigger even if the code diff is small. Risk scales nonlinearly.
Operational changes. If we add a new dependency, a new queue or topic, a new cron, a new permission, or anything that changes on-call reality, I write an RFC. Kubernetes’ KEP culture bakes this in via sections like risks and mitigations, test plans, and production readiness thinking. You do not need the whole KEP process, but you do want the habit. (KEP template)
User-visible behavior changes. If API behavior changes in a way clients will notice, error codes, semantics, latency expectations, rate limits, pagination, default ordering, I treat it as a contract change and write it down.
Ambiguity that will cost time later. If I can already predict a long Slack thread during implementation, I would rather turn that into a short RFC early, while everyone can still shape the outcome.
The lightweight RFC template that saves time
I borrowed the minimum structure from communities that take RFCs seriously, like Rust, which explicitly separates a guide-level explanation from a reference-level one, and from decision-record style writing. The trick is that the template forces clarity without forcing length. (Rust RFC template)
Here is the template I use. It is designed to fit in 30 to 60 minutes for most changes.
text
Title: Status: Draft | In review | Accepted | Rejected | Superseded Owner: Reviewers: Target release window: Context: What problem are we solving, and why now? Proposal: What are we changing? What stays the same? User-facing impact: What does a client/user/team member observe as different? Data contract: If any payload/schema changes exist, describe compatibility, versioning, and examples. Guide-level explanation: Explain the change like you are onboarding a capable engineer into using it correctly. Reference-level details: Interfaces, endpoints, message schemas, config flags, limits, edge cases. Risks and mitigations: What can go wrong? How do we reduce probability and blast radius? Alternatives considered: What did we consider and why not? Rollout plan: Migration order, feature flags, backfill steps, rollback plan. Testing plan: What will prove this works? Include contract tests and failure-mode tests. Observability: Logs, metrics, traces, dashboards, alert thresholds. Security and privacy: AuthZ/AuthN implications, secrets handling, sensitive data, threat considerations. Open questions: What do we need to decide before implementation is green lit?
This template is intentionally not cute. It is boring in the way a good seatbelt is boring.
A few notes on why these fields matter:
The guide-level explanation prevents the classic problem where a document is technically correct but operationally unusable. Rust’s RFC template calls this out explicitly: first teach the change, then specify it. (Rust RFC template)
The risks, rollout, testing, and observability cluster is what turns the RFC into a delivery tool. This is where you prevent it-worked-on-my-machine deployments and surprise on-call escalations, exactly the mindset you see in KEP templates. (KEP template)
The status field is underrated. Draft makes it safe to comment. Accepted makes it referenceable. Superseded keeps history without pretending old decisions never happened, which is a core ADR principle. (Cognitect)
How I run RFCs without slowing the team down
I keep the process lightweight and biased toward momentum.
I start the RFC as soon as I can name the boundary I am crossing. Often that is before the first line of code. The first version can be ugly; the goal is to open the feedback channel early.
I prefer RFCs as pull requests in the repo, because the review mechanism is already there and the doc stays close to the code that implements it. That also makes this is the source of truth very explicit.
I keep reviews asynchronous. Comments should be concrete and anchored to the template sections. If discussion gets stuck, I schedule a short decision sync, then record the outcome back into the RFC so the reasoning survives beyond the call.
Most importantly, I treat Accepted as permission to implement, not a guarantee of perfection. Reality will still teach you things. If reality changes the plan, you update the RFC or supersede it. That is not failure, that is the system working.
Common failure modes
An RFC can still become paperwork if you let it drift into these traps.
The first trap is writing an RFC as a sales pitch. If the doc is trying to win rather than clarify, reviewers will either disengage or fight it. The request-for-comments part matters. (IETF)
The second trap is pretending rollout is someone else’s problem. Delivery includes migration, monitoring, and rollback. If the RFC does not cover those, it is not a delivery tool, it is a partial design note.
The third trap is using RFCs for everything. If you require an RFC for trivial refactors, you will train people to treat RFCs as noise. The whole point is to reserve the ritual for boundary-crossing changes.
What I would do next in your team
If you want this to work culturally, I would start with one rule: when we cross a boundary, we write an RFC. Then I would pick a single shared location for RFCs, make the template a copy-paste snippet, and normalize Draft status.
You do not need a bureaucracy. You need a habit that helps you let go of late-stage surprises.
If you are building a developer-facing product, open source or commercial, this habit also markets you indirectly: it signals trust, maturity, and operational seriousness, without you having to say any of those words out loud.
If you want, message me on LinkedIn with your team’s typical boundary changes, and I will suggest a tightened RFC trigger rule and a template variant that fits your workflow.
Need help with this in your own stack?
If reliability or delivery friction is slowing your team down, we can fix it in focused steps.
Related posts
Impression Logs and Exposure Data: the Missing Half of Recommender Evaluation
Impression logs (exposure data) turn clicks into accountable metrics. Learn the request_id join pattern that makes recommender evaluation and debugging sane.
Software supply chain security in 2026: a reality check (SBOM, provenance, dependency risk) without paranoia
A pragmatic 2026 playbook for software supply chain security for mid-sized orgs: SBOMs, provenance, dependency risk, and an automation-first good enough bar.