Solo Founder Product Engineering Handbook
Technical Decision Record
Make a consequential engineering choice explicit, including the evidence behind it, the burden it creates, and the conditions for changing it.
Record the Bet While It Can Still Be Changed
Six weeks after a hurried architecture choice, the code usually remembers what you chose. It does not remember the customer request that forced the choice, the alternative you nearly took, or the risk you agreed to carry. When a new failure appears, you have to reconstruct the decision while also repairing the product.
A technical decision record preserves that missing reasoning. It is not a meeting transcript or a permanent defense of the architecture. It is a dated account of a bet: given the evidence and constraints available now, this is the smallest coherent choice, this is what it will cost, and this is what would justify changing it.
Write one when a decision creates a boundary that will be expensive or dangerous to move casually. Data ownership, authentication, tenancy, external vendors, background work, public contracts, deployment shape, and irreversible migrations are common candidates. A local naming choice or easily replaced library usually is not. Recording every choice buries the decisions worth finding.
Write the Decision Before the Rationale
Open with one sentence that can govern implementation:
For this product and stage, we will make this choice to satisfy this constraint, while deliberately accepting this cost or limit.
If the sentence cannot name the cost, the decision is probably still a preference. If it cannot constrain implementation, it is only a topic. “Use a queue” is a topic; “persist each delivery request before placing it on a managed queue, accepting provider dependence so that an application restart cannot lose accepted customer work” is a decision.
The record should contain enough context for a future reader to disagree intelligently. Include the customer behavior, incident, load observation, contractual promise, or founder-capacity limit that created the decision. Separate observations from forecasts. “Three design partners asked for scheduled delivery” is evidence. “This architecture will support enterprise scale” is an unbounded prediction.
Copyable Record
Keep the record beside the code or operational documentation it governs. Give it a stable identifier so later records can supersede it without erasing its history.
# TDR-[number]: [decision stated as an action]
Status: proposed | accepted | superseded
Owner:
Decision date:
Last reviewed:
Supersedes / superseded by:
### Decision
For [product, users, and current stage], we will [specific choice]
to satisfy [binding constraint], accepting [important cost or limit].
### Evidence and context
Observed customer behavior or request:
Current system behavior or failure:
Decision needed now because:
Known unknowns:
### Constraints and non-goals
Must remain true:
Founder time or operating limit:
Trust, security, privacy, or contractual boundary:
Deliberately not solved by this decision:
### Options considered
Option A:
Strongest reason to choose it:
Reason not chosen now:
Evidence that would make it preferable:
Option B:
Strongest reason to choose it:
Reason not chosen now:
Evidence that would make it preferable:
### Consequences
This enables:
This makes harder:
New recurring work and owner:
Failure modes introduced:
Mitigations required now:
Risk consciously accepted:
### Commitment and reversal
Smallest implementation that honors the decision:
Boundary that keeps replacement possible:
Data, contract, or state that must survive a replacement:
Steps to reverse or migrate:
Point after which reversal becomes materially harder:
### Review
Signals to observe:
Revisit when:
Do not revisit merely because:
Next review event or date:
Links to evidence, code, runbooks, and follow-up work:
“Accepted” means the decision currently governs the system. It does not mean the decision is correct forever. When the choice changes, write a new record, mark the old one superseded, and link both directions. The old record still explains the system you inherited and any migration assumptions that remain.
Worked Example: Delivering Reports After the Request Ends
Consider an illustrative reporting product run by one founder. Seven paying agencies create client digests. Three design partners now want approved digests delivered on a schedule. The current application sends email inside the web request. A provider timeout makes the request fail even when the message may already have been accepted, and an application restart loses work held only in memory.
The founder could write a record like this:
# TDR-014: Persist deliveries and dispatch them through a managed queue
Status: accepted
Owner: founder
Decision date: 2026-07-17
Last reviewed: 2026-07-17
### Decision
For scheduled digest delivery during the design-partner stage, we will
persist a delivery record before dispatching work through a managed queue.
We accept dependence on one queue provider so accepted work survives an
application restart without requiring the founder to operate queue servers.
### Evidence and context
- Three of seven paying agencies requested scheduled delivery.
- In-process sending has produced ambiguous outcomes during provider timeouts.
- The product must show whether a delivery is queued, attempted, accepted by
the provider, failed, or of unknown outcome.
- We do not yet know the eventual delivery volume or channel mix.
### Constraints and non-goals
- An accepted request must have durable product state before external work.
- A retry must refer to the same delivery rather than create a second one.
- Delivery content and recipient addresses must not appear in queue metadata.
- The founder will not operate a queue cluster at this stage.
- Multi-region dispatch and multiple queue providers are out of scope.
### Options considered
1. Keep in-process sending and add retries.
Strongest case: least new infrastructure and fastest initial change.
Rejected now: process loss and ambiguous provider outcomes remain coupled
to the customer request. Reconsider if scheduled delivery is removed.
2. Run an open-source queue on the existing server.
Strongest case: control over behavior and no managed-queue dependency.
Rejected now: backup, upgrade, capacity, and recovery work belongs to the
founder. Reconsider if provider limits or cost become a measured burden.
3. Persist deliveries and use a managed queue. Chosen for the current stage.
### Consequences
- The database delivery record becomes the source of product-visible state;
the queue message carries only its identifier.
- Workers must make attempts idempotent and reconcile unknown outcomes.
- Queue delay, oldest pending delivery, and terminal failure need alerts.
- Provider outage is accepted; silent loss of accepted work is not.
- The founder owns the worker and reconciliation runbook while the vendor
owns queue-server operation.
### Commitment and reversal
- Start with one worker, one queue, bounded retries, and a dead-letter path.
- Keep provider calls behind a small dispatch interface; do not expose queue
identifiers in the public API.
- Preserve delivery IDs, attempt history, and customer-visible states during
a replacement.
- To migrate, pause new dispatch, drain or export pending IDs, enqueue them in
the replacement, reconcile in-flight attempts, then switch the producer.
### Review
Observe queue delay, failed and unknown outcomes, retry volume, monthly cost,
and founder intervention time.
Revisit when provider constraints block a required workflow, operating cost
is material relative to delivery revenue, recovery cannot meet the customer
promise, or sustained load exceeds tested capacity.
Do not revisit merely because self-hosting appears cheaper before operating
and recovery time are measured.
Review after the first production incident or before adding a second channel.
The example does more than announce “managed versus self-hosted.” It assigns durable state, draws a security boundary, reveals the new operational work, and describes a migration that preserves customer-visible truth. Those consequences are part of the choice, not cleanup for later.
Keep Alternatives Honest and Reversal Concrete
An alternatives section fails when the rejected options are caricatures. Write the strongest reason a reasonable person would choose each one. Then state why the present evidence does not justify it. This makes the record useful when the constraints change: a rejected option may become the right one without anyone having been foolish earlier.
“We can replace it later” is not a reversal plan. Name the state that must move, the public behavior that must remain stable, the period of dual operation or downtime, and the signal that proves the migration finished. Also name the hardening point. A vendor is easier to replace before its identifiers leak into your API, its workflow shapes your core data model, or customer history exists only in its system.
Reversibility is not the absence of commitment. The system still needs one authoritative choice. It means choosing the seam, preserving the state, and knowing the work required to cross back over it.
Review on Evidence, Not Restlessness
Technical decisions should not expire on a ritual schedule, but neither should they disappear into an archive. Give each record event-based triggers: a load boundary, an incident, a new trust obligation, a measured support burden, a vendor constraint, or a change in customer behavior. A calendar date can prompt inspection; it is rarely evidence by itself.
During a review, ask three questions:
- Which original constraints still bind?
- Which forecast has become observable fact?
- Does the accepted cost still buy something the product needs?
Leave the decision in place when the answer remains yes. Supersede it when the evidence changes. The value of the record is not the volume of architecture history it creates, but the founder time and customer risk it saves when a consequential choice has to be understood again.
Continue reading
Full table of contents