Architecture
The Software Architecture Document: Describing a System Clearly and Sufficiently
How to write a Software Architecture Document (SAD) that is clear and sufficient, separates verified fact from intent, links to code and decisions, and…
A new person joins the team and wants to know what can be changed safely. In the repository she finds an architecture description that names React, PostgreSQL and Kubernetes, but it does not tell her what already works, who is responsible for the data, where free time slots are checked, or what the team must verify during a failure. She needs to explain why a booking must not be confirmed only after the email service has responded. A list of technologies is not enough for that: an architecture document should help her make a safe change, not just name the tools.
A Software Architecture Document (SAD) should help the reader reconstruct the important reasons and boundaries of a system: who it was built for, what it consists of, which data and actions belong to each part, how the key scenarios unfold, what happens when something fails and which questions are still open. It is not an attempt to write down every class and configuration parameter. It is a description at a sufficient level for people to make changes, find the code, and check whether the implementation still matches the design.
What you will be able to do
After this unit, the reader can:
- define the audience, scope and purpose of an architecture document;
- describe a system in words, tables and links so that responsibilities, external dependencies and important flows are clear;
- separate the current implementation, the agreed design, a proposal and an assumption;
- connect the document to code, contracts, decisions and operational instructions;
- find a contradiction between what is written and the code, without presenting documentation as proof of what has actually been deployed;
- identify what must be updated after the system changes, and assign an owner and a review date to it.
Prerequisites and scope
Earlier units helped clarify needs, acceptance criteria, quality characteristics and architectural trade-offs. Here we gather the necessary information into a description the team can read and verify. Neal Ford's page about Software Architecture: The Hard Parts describes the book's focus as trade-off analysis and documenting decisions; this is general context, not proof of any specific requirement for a SAD (author's page). A SAD complements requirements and acceptance criteria, architecture decision records and operator instructions; it does not replace them.
The C4 model gives shared names to the levels of structure, and Architecture Decision Records covers records of individual architectural decisions. A SAD can link to both without copying them.
Who a SAD is for
A document is useful when it helps specific people do specific work. A product owner looks for the link between goals and technical boundaries. A developer needs data owners, contracts and the place where a change belongs; a reviewer needs claims with verifiable links to code and contracts, and a clear distinction between the current state and the future design. An operator looks for dependencies, failure signals and the way to recover, and a security specialist looks for trust boundaries, roles and the places where access is checked. An agent needs confirmed sources, prohibitions and the questions it must not guess the answers to. These are different needs: one document should give a short overview and navigation, not require every reader to use all of its sections equally.
Not everyone has to read everything. At the start, give a short overview and navigation to sections by role or by question. Name the audience explicitly. If the document is written only for the development team, do not promise that it is enough for an audit or for disaster recovery.
Documentation should be proportionate to the risk and cost of change. A small internal tool may need only one page of context, responsibilities, how to run it and known limitations. A payment service, a system holding medical data or critical infrastructure will need deeper descriptions of flows, access, recovery, evidence and approvals. A page count proves neither completeness nor quality.
A SAD is not a synonym for all technical documentation

| Material | Which question it answers | What it does not prove on its own |
|---|---|---|
| Product requirements and acceptance criteria | What behaviour and quality does the user or process owner expect? | How the system is actually built today |
| SAD | Which structure and interactions between parts matter for understanding the system, and on which facts do they rest? | That every element described has already been deployed, or that the user is satisfied with the result |
| ADR (Architectural Decision Record) | Which significant decision was made, why, and what were the alternatives and consequences? | A full description of every scenario and of the system's state |
| Application programming interface (API) contract or data schema | Which exact fields, types, operations, errors and compatibility rules does an integration have? | Why the boundary was drawn this way and what happens outside the contract |
| Operations guide (runbook) | Which steps does an operator take in a specific incident or during maintenance? | The overall map of components and product goals |
| Source code, configuration and version history | What exactly was saved in a particular commit and how has it changed? | Whether the code meets current needs and has all the operational procedures it requires |
These materials should link to one another where that saves time, but they should not copy large fragments that will inevitably drift apart. A SAD says, for example, where the current contract lives; the contract holds the exact fields; an ADR explains why that particular approach to compatibility was chosen.
A responsible document starts with status and scope

The most dangerous phrasing in an architecture document is a timeless "the system does". It may describe an intention, an old implementation, an unverified vendor claim or a future phase. Mark the status of every important statement separately:
- Current state: confirmed by code, configuration, an environment check or a reliable record of the system's operation; give the date or version of the evidence.
- Agreed target state: the decision has been made, but part of the implementation is not finished; say what remains to be done.
- Proposal: an option under discussion; it must not be presented as a decision.
- Assumption or open question: information is missing; name who owns the answer, the risk and the next check, if they are known.
- Obsolete / superseded: the statement no longer describes the current system; keep a reference to the date, the reason or the newer material if it matters for the history.
A hypothetical SAD header might look like this:
Title: Consultation booking system
Status: current state v1; section 7 contains the agreed design for v1.1
Document owner: bookings team
System and scope: client web portal, back end and booking store
Verified on: 23 September 2026
Implementation version checked against: release 1.4.2, commit 8c21d3a
Next review: after a change to the confirmation rule or the start of the storage migration;
before switching traffic, check compatibility of the old and new schemas, deployment order,
migration and reconciliation of records, the recovery point and a tested rollback method.
If a safe rollback is impossible, record a fix-forward plan before the migration begins
Sources: product requirements document (PRD) No. 17, ADR-004, contract /docs/api/booking.yaml
These are teaching identifiers and an example date, not a record of a real system. A commit pins the text to a specific state of the code better than the words "latest version". But even an exact commit does not prove that production is running that commit. That claim needs separate evidence of deployment.
What a short, useful SAD should answer
A template is a set of questions, not a checklist to fill in mechanically. The official arc42 template has twelve parts: goals, constraints, context, solution strategy, building blocks, runtime scenarios, deployment, crosscutting concepts, architecture decisions, quality requirements, risks and a glossary. Its authors explicitly allow the scope to be adapted to the system. The practical conclusion for a small document or project: keep a section if the reader needs it; explain its absence if it matters but is not yet known; do not copy the full template just to tick boxes (arc42: template overview, arc42: method).
A useful SAD usually answers these questions.
1. Which product is described, and why?
Briefly name the problem, the target audience, the important business outcomes and the boundary of the system. Separately, write down what the document does not cover. If the product has several independent subsystems, narrow the page to one coherent solution or say where another document begins.
2. Which constraints and requirements shape the structure?
State contractual or regulatory limits, environments, teams and important quality characteristics: for example, confidentiality, availability, latency, recovery or compatibility. Distinguish an accepted requirement, a measured fact and an unverified number. For every critical threshold, give its source and the method of checking it; this is discussed in more detail in Non-Functional Requirements.
3. Who or what is outside the boundary?
Name the people and external systems that interact with the product. For each dependency, record the type of data or request, its direction, its purpose, the relevant access mechanism, the responsible supplier and the behaviour on error. "Payment system" is not enough: you need to understand who accepts the payment, who stores the order state and who can confirm the outcome after a timeout.
4. Which parts are inside, and what is each responsible for?
List logical components or modules by responsibility, not just by technology or file directories. For each, state its inputs, its output, the owner of its state, its interface and the duties it must not take on. Identify external dependencies and what is not part of the system. Do not call every package a separate architectural part.
The C4 model helps change the level of detail consistently: from the system and its surroundings, to the applications and data stores inside it, to the components within those and, only where needed, to specific code elements. Its author stresses that it is a model for describing static structure, not a development process; questions of state and the sequence of operations may need a separate written description (C4: official site, abstractions, FAQ on combining it with arc42). In this unit the structure is written in words and tables; C4 diagrams are the subject of the C4 model.
5. How do the important scenarios run?
Describe the normal path and the failure paths: who initiates an action, which part checks the rule, where the change is recorded, who receives a notification and what a person sees during a partial failure. There is no need to retell every request. Choose the scenarios that affect quality, data, security or expensive architectural decisions.
6. Who owns the data, and how is it protected?
Name the source of truth for each important fact, the owner of changes, derived copies, retention period, recovery procedure and integration rules. For a data migration, state the compatibility of the old and new schemas, the sequence of the transition (for example: deploy compatible code, migrate and reconcile the records, and only then switch reads), the recovery point and a tested rollback method; if a reverse transition is not supported, name a safe fix-forward plan before the migration starts. For access, state where permission is checked, the roles and the untrusted inputs. Personal data, secrets, backups and logs should be mentioned only at the level the corresponding risk requires; an actual password or secret is never stored in the document.
7. Where, and under what conditions, does the system run?
Separate the current deployment from the target one. Describe the environments, what runs the application, background jobs, required external services, configuration boundaries and the process for restoring the previous version, if it has been confirmed. Do not claim that automatic recovery or redundancy exists until this has been checked against the real configuration and operating procedure.
8. What can go wrong, and what remains unknown?
Record expensive or likely risks, technical debt, dependencies on suppliers and assumptions that could change the structure. For each open question, state whom to ask or which fact to check. The statement "we need to scale" without an expected load, a time limit and data about the bottleneck is not a conclusion.
9. How will the reader verify the description?
Provide links to requirements, code directories, specifications, ADRs, operational steps and public sources. Give the date of verification. Say which parts were checked against the code, which against the infrastructure and which come from the agreed design. A precise link to code with a line number may drift over time; a pinned commit or a stable module name makes things easier to find.
Worked example 1: a compact SAD for a football statistics and squads website
What follows is an entirely fictional teaching example. Team A, Team B, the player and event identifiers, dates, roles, metrics and storage options do not describe any real club, supplier or deployment. Assume that a hypothetical owner has agreed the target behaviour for the first version; before implementation the team will still need the current data-source contract and confirmed publication rights. The example shows which architectural answers must be recorded; it does not prescribe a universal technology.
SAD: football statistics and squads website, version 1
Status and owner. This is a target description of the first release, agreed within the teaching scenario, not a report on an implemented or deployed product. A fictional sports data team owns the document; the product owner sets the public rules, and a data steward approves the source and corrections. The document is reviewed after any change to the feed contract, to player and club identification, to effective-date rules or to the publication policy. Claims about the current state need separate evidence from the code and the environment.
Goal. A reader sees a club's current squad, the history of transfers and a player's statistics by match and season. In the fictional event demo-017, the official feed reports that player P-42 is moving from Team A to Team B with effect from 2026-08-31T00:00:00Z. Once that moment has arrived, the current squad must show Team B, while historical matches and statistics recorded for Team A must remain attached to Team A. The feed message is evidence of where the event came from, but it does not replace checking its contract, its correctness and the right to publish it.
Out of scope. Live broadcasts, predictions, betting, transfer negotiations, players' medical or contact details, and editing the external league's register are not part of the system. The site publishes only data that is permitted for this product. Before launch, the owner must confirm the source licence, the correction rules and the limits of publication; the example does not assume that any feed allows reuse.
People and external systems. A public reader browses squads and match statistics; an analyst reads permitted aggregates; a data editor reviews events that automated checks cannot safely match. In the scenario, a licensed competition data supplier delivers a feed of transfers and match results with external identifiers, effective time, version and record provenance. Its role and right to publish are conditions of the example that must be confirmed by contract and schema. The supplier has no direct access to the database, and the site does not change the supplier's register.
Key quality attributes and limits. For teaching measurements, assume 200 concurrent readers on a representative set of clubs, matches and seasons. One hypothesis is that p95 (the 95th percentile of latency) for reading a player card does not exceed 700 ms; separately, at least 99% of requests return a correct result within two seconds. Another hypothesis is that an accepted and effective event appears in the public squad within five minutes of its validated delivery. These are teaching targets, not agreed guarantees. Before drawing a conclusion, the owner must approve the measurable limits, period, sample, load, correctness criterion, and whether the source contract can support that freshness.
Containers and internal responsibilities. At the container level, the teaching design has a public website, a read API, a background importer and a relational store. The table spells out the responsible components within these boundaries and the external integration; it is a logical division of the target state, not a statement about specific technologies or deployed processes.
| Container or component | Responsibility and own state | Boundary and failure |
|---|---|---|
| Public website | Shows the squad "as of a date", transfer history and match statistics | Does not decide whether the feed is authoritative and does not edit club membership in the browser; shows the time of the last update |
| Read API | Returns public fields and builds the current squad from approved membership intervals | Does not read unverified messages directly from the queue; rejects requests outside the permitted contract |
| Importer and validator | Checks contract, source, schema, stable identifiers, revision and time sequence; quarantines unknown cases | Does not guess a person from a similar name; a repeat of the same (source_id, event_id, source_revision) with the same digest does not duplicate state, while a different digest for that key is a conflict |
| Canonical store | Stores stable player_id and club_id, match appearances, membership intervals, accepted events, revisions and provenance |
Recording an accepted event and changing the squad projection are committed transactionally; a correction adds a version rather than silently erasing history |
| External feed | Supplies data under the agreed contract | A delay, a repeat, a schema change or a data withdrawal may require quarantine, a visible staleness status or manual review |
Success scenario and membership over time.
- Receipt. The importer receives the fictional event
demo-017with an externalplayer_id, theclub_idof Team A and Team B, the effective time2026-08-31T00:00:00Z, the source identifier and its version. - Commit. After checking the contract and the identifier mappings, a transaction records the event's immutable provenance, closes
P-42's membership interval in Team A at the effective time and creates a Team B interval from that time. A match appearance record has theplayer_id, thematch_idand theclub_idof the team the player played for in that match; it is not re-linked to the current club. - Reading as of a date. A query takes the intervals as of the selected date: before the effective time, Team A remains the current club; after it, the page shows Team B and keeps the earlier history. If a transfer is scheduled for the future, the current squad does not switch early.
The effective_at field sets the moment from which a fact takes effect; the time a message was received only shows when the system saw it. The two must never be substituted for each other. If the contract contains only a calendar date without a time zone, the owner must define the league's calendar rule before implementation; the file's receipt time is not a safe fallback rule. A match appearance keeps the club for which that particular match was played, even if the player later moved elsewhere.
Repeat and conflict scenario.
- Exact repeat. The tuple
(source_id, event_id, source_revision)holds the identifiers of the source, the event and its revision. If the source resends the same tuple with the samedigest(a hash of the normalised content), it is an exact repeat. The system may record a metric, but it creates no second transfer or interval. - Content has changed. If the tuple matches but the
digestdiffers, both observations are kept for reconciliation. The new one is not silently discarded and does not overwrite the accepted version; the canonical event and the projection do not change until a decision is made under the source contract. - Contradictory data. If two different events conflict for the same player and period, or an external ID has not been mapped to a canonical club, the importer does not pick a winner by arrival time and does not match an entity by name. It isolates the records for an editor. The owner must define the rules for concurrent registrations, loans and parallel competitions for the specific league: the assumption that "a player always has only one club" may be false.
Source failure scenario.
- Halt. If the feed is unavailable or its schema has changed, the importer does not invent a new squad and does not overwrite the canonical state. The operator sees the time of the last successful import, the delay, errors and the quarantine queue. When data passes the freshness limit, the public page marks it as stale and shows the time of the last accepted synchronisation.
- Recovery. Once the source is back, the stable key and version allow events to be safely reprocessed. If the store transaction was committed but the acknowledgement to the feed was lost, a retry does not create a second membership. If the contract requires a reply to the source, the exact acknowledgement protocol remains an open question.
Data and access. The canonical store owns the accepted history of squads and statistics; the external feed is a candidate source with recorded provenance, not a direct database for the page. The public API returns only approved fields. The editor role is needed for manual matching and for accepting corrections; a change must record the author, reason, source and timestamp. Feed credentials are kept in an approved secrets store with minimal permissions and never end up in code, logs or public error messages. Even statistics about well-known players may be subject to licensing restrictions; the permitted fields, storage of the raw feed, deletion and the procedure for withdrawing data remain contractual conditions and open questions until the owner confirms them.
Deployment and operations. The target, not yet verified, layout has the website and read API, a separate background importer and a relational store; the hosting provider, topology, region, backup policy and recovery time have not been chosen. No separate cache is needed in the first phase: if measurement proves it useful, it will be a derived projection with an explicit freshness period, not a source of truth. Operational signals: time of the last successful import, feed delay, number of retries and conflicting revisions, quarantine size, read errors, backups and the date of the last recovery test. The operator guide must describe how to rerun an import, how to match records manually, and how to roll back a faulty projection through a new verified version without hiding the original event.
Alternatives, risks and open questions. Simply overwriting current_club_id is cheaper, but it loses the history of membership and can wrongly attribute earlier matches to the new club. A full event-sourced system (one where the current state is rebuilt each time from the complete stream of events) may be unnecessary complexity for the first release. In this example it is enough to store versioned source events and explicit membership intervals alongside the canonical statistics records; the details of the contract and of reconciliation remain decisive. Before implementation, these must be answered: which source and licence are authoritative; how to identify a player or club after a rename; how to represent loans, dual registration rights, corrections and cancelled transfers; which time zone and calendar determine when something takes effect; which fields may be shown and how long the raw feed may be kept; and which freshness and recovery the owner has agreed. Unresolved answers must not be replaced with technical assumptions.
Verifying the design. Before launch, contract and integration checks must produce the following evidence:
| Check | Expected result |
|---|---|
Transfer of P-42 from Team A to Team B |
The current squad changes only from effective_at; the squad as of an earlier date and matches before the transfer stay with Team A. |
Exact repeat of (source_id, event_id, source_revision) |
The same digest creates no second event or interval. |
| Conflicting digest or unknown ID | The observation is kept for reconciliation but does not change the accepted version or the public projection until a decision is made under the contract. |
| Unavailable source or lost acknowledgement | The page shows the time of the last synchronisation; a retry after recovery creates no duplicate and adds no unconfirmed data. |
| Access and recovery | Editor permissions, secret handling and the recovery procedure are verified separately. |
In addition, the checks must confirm that a transfer never takes effect before the date set by the contract; a future transfer does not change the current squad early; a match record does not move to the new club along with the player's profile; conflicting observations remain available to the editor for reconciliation; and the freshness limit marks data as stale without replacing it with an unverified snapshot. These points spell out the required behaviour; they are not the results of tests already run.
These criteria describe the evidence that is needed in future, not checks that have already passed.
Why the example helps
Status, owner, sources and open questions show where the target description ends and unverified assumptions begin. Before implementation, the contract and licence must be confirmed, the effective-date rule defined, and the behaviour checked against the competition's requirements. Only then can the claims be tied to the code and to separate evidence of deployment.
Worked example 2: turning a list of technologies into a responsible description
Original excerpt:
"The system is built on React, PostgreSQL, Kafka and Kubernetes. The architecture is modular and scalable. Data is encrypted. There is full automatic recovery, 99.99% availability. An email is sent after payment."
This paragraph does not let anyone make a safe change. "Modular" does not name the modules; "scalable" does not say what kind of load; "encrypted" does not identify the data, the boundary or the keys; "full recovery" does not say what is recovered or how quickly; the availability figure has no measurement window or success rule; and the email could be a condition of payment or merely a notification. The technologies may be facts about the repository, but on their own they do not explain the system.
Questions that separate facts from assumptions:
- Which release or commit are we describing? Is that code deployed right now?
- Who are the users and external suppliers? Which fields or commands pass between them?
- Is Kafka in the current configuration, or is it a future plan? Which messages does it carry, who acknowledges them, and can they be replayed?
- Which action must be consistent with payment? Can an order be confirmed if the payment provider responded with a timeout?
- Which store owns the payment status and the order status? Can those facts diverge?
- For whom does 99.99% matter; what event counts as success, what is the period, the denominator and the source of measurement?
- Which recovery exactly is automatic, who tested it, and when was the last successful exercise?
Until these questions are answered, there is no need to fill in complicated details. A SAD can say: "A PostgreSQL dependency was found in the current branch; deployment has not been verified. Kafka is mentioned in a proposal dated 12 August, but its deployment and consumers have not been confirmed. The 99.99% threshold has no owner or source, so it is not yet a target." That is more accurate than turning a described option into a "done" status.
Corrected excerpt after checking the assumptions:
"In this teaching example, a check of release 1.4.2 confirmed that the Bookings module is the single owner of an active slot. One transaction creates the booking, and a PostgreSQL partial unique index on the slot identifier (
slot_id) with the predicateWHERE status = 'active'prevents two active bookings from being saved for the same slot; if two requests arrive at the same time, the first is confirmed and the second receives a conflict without a record being created, and can refresh the list of slots. In PostgreSQL, uniqueness over only a subset of rows is defined with a partial unique index, not with an ordinaryUNIQUEconstraint (table constraints, partial indexes). The Orders module stores the confirmed order state, and the Payments module passes the request to an external provider. After a timeout, the payment's state is 'outcome unknown'; the system first reconciles it using the provider's identifier rather than blindly retrying the payment. The notification is sent after the order record is confirmed and is not proof of payment. A background message broker is not deployed; it is only a future option, if measurement shows the need.Unknown payment outcome: the Payments module calls the external provider. After a timeout, the status remains unknown; the system reconciles it using the provider's identifier rather than retrying the payment blindly. The email is sent after the order is confirmed, but on its own it does not prove that the payment went through. The message broker is not yet deployed and remains a proposal.
Recovery according to the runbook: the on-call operator restores a copy into an isolated environment and checks the recovery point and the count and integrity of bookings. The data owner confirms the result before traffic is switched. Before a schema change, the team also checks compatibility of the old and new versions, the deployment order, the migration and reconciliation of data, a checkpoint and a tested rollback. If rollback is unsafe, a fix-forward plan must be defined before switching. This is a manual procedure; automatic recovery, the target time and the acceptable data loss remain unagreed. That is why the SAD does not promise automation or 99.99% availability."
This excerpt separates a fact confirmed by the code, an unknown payment outcome, a manual procedure and a future proposal. It explains why retrying a payment after a timeout is dangerous, how recovery is checked and which schema changes need a separate plan. The excerpt does not stand in for the payment provider's contract; its exact rules must be checked separately.
A real example: checking an architecture description against code and history
The temporalio/temporal repository has a general architecture overview and a separate description of the History Service. The overview explains the roles of Frontend, History and Matching and separates them from Workers, which run users' workflows. Workers execute users' workflow code; they are not the same boundary as the cluster's server-side services. The History Service description makes clear that it does not cover all of the component's responsibilities, and it links to the corresponding code directory in the repository.
The same repository has separate directories service/frontend, service/history, service/matching and service/worker. Git history shows how links to code go stale: after the handler was refactored in May 2024, the path workflow_task_handler_callbacks.go moved. A commit from June 2026 updated it to service/history/api/respondworkflowtaskcompleted/api.go (link fix, current path).
These steps confirm two limited things: the documentation names boundaries that can be found in the code tree, and the change history shows that links need maintaining after refactoring. They do not prove that every section is fully current, that main is deployed in Temporal Cloud, or that behaviour is the same under every configuration. Documentation, code and the running environment are separate sources of evidence. For your own SAD, record exactly what you checked, against which version and when.
Temporal's pages contain diagrams and links to images; this text-based example does not copy them. We restate only the verifiable claims and the history of the links. A team can apply the same method to its own SAD: open the document, follow the key links to a directory or contract, compare a critical flow with the code and its change history, and record the date and the limits of the check.
The document must change along with the system

An architecture document goes stale if a change to a contract, a data owner or the deployment does not update the text. On the other hand, it does not need editing after every variable rename. Use a practical rule: update the SAD when a fact changes that affects a choice, a risk, compatibility, security, an operational action or the understanding of a boundary.
For a change, ask its author to answer:
- Which SAD statement has changed or become unverified?
- Is this the actual implementation, an agreed target, a proposal or an experiment?
- Which code, contract, configuration or observation proves the new state?
- Which ADR or requirement should reference the change?
- Have data, access, failure handling, startup, recovery or visibility for the operator changed?
- Which links and examples are now out of date?
- Who owns the next check, and which event triggers it?
A single change may update a requirement, the SAD and a short ADR. Do not create the same table three times. In the requirement, record the expected behaviour; in the SAD, its place in the structure; in the ADR, the reason for the specific choice. The implementation stays in the code, and the instructions for action during an incident stay in the runbook.
For documentation as code, it helps to keep the text next to the code in version control: you can see the author, the change, the commit, the discussion and revert to an earlier version. This makes it easier to check links and ownership, but it does not make the content true automatically. Claims still need a source and a date.
How an agent should read and maintain a SAD
An agent can find a gap between the document and the code, extract a list of dependencies, propose a responsibilities table or point out that a quality criterion has no verification method. But it cannot on its own turn an unknown into a fact, accept a product threshold, approve an access model or declare a deployment successful without evidence.
A well-bounded instruction might read:
Compare the "State and flows" section of this SAD with the files in the listed directories.
For each claim, record one of four outcomes: confirmed by code / contradicts the code /
cannot be confirmed from the available files / describes an agreed target, not the current state.
For a confirmation, give the exact path and symbol or commit. Do not draw conclusions
about the production deployment without separate evidence of it. Do not change thresholds,
access roles, requirements or ADR status. Record any question the system owner
must answer. After the analysis, do not edit code; propose specific SAD text.
Before asking an agent to edit anything, tell it which files it may touch, the sources, the branch version, the form the evidence should take, and that it must not approve decisions on its own. If the agent has no access to the infrastructure, do not ask it to "confirm production" from the configuration in a local repository. This approach is consistent with the official advice to start with clear criteria and controlled boundaries, and to complement automated checks with human assessment of whether requirements are met (OpenAI, a practical guide to building agents, Anthropic, "Building effective agents"). Such limits make the result verifiable and protect the team from confident but invented documentation.
Common SAD mistakes
| Mistake | Why it hurts | How to fix it |
|---|---|---|
| A list of technologies without responsibilities | The reader does not know what can be changed without breaking the rules | Describe the boundary, state, interface and owner of every significant part |
| The design is written in the present tense | The agent and the team take what is planned for what already exists | Mark state, date and evidence separately for the current and the target version |
| A success path without partial failure | The operator does not know what state may be left after a timeout | Add one or two risky flows with retry, failure and recovery |
| "Secure", "scalable", "highly available" without a criterion | There is no way to verify the claim | Reference the agreed requirement, the method of measurement and the limits of the claim |
| The same contract copied into several places | One copy goes stale, and a change quietly contradicts another | Keep the details in one source; in the SAD, leave a link and the architectural meaning |
| No owner and no verification | The document gradually turns into an archive | Assign an owner and an update trigger; a review is not a substitute for verification after changes |
| A diagram mixes levels and flow directions | The reader confuses a component, a server and a team | Name the type, boundary, direction, protocol and purpose of each connection; the C4 model explains how |
| The source only speaks to the code, but the conclusion is about production | The code may not be deployed, or the configuration may differ | Separate the evidence from code, infrastructure and the actual environment |
Order of preparation and review
Work in small passes rather than filling in the template from start to finish without checks.
- Choose the reader and the question. For example: "What must not be changed, so that a double booking cannot happen?"
- Mark the scope and the date. Give the system name, the release or commit, the environment and the document owner.
- Gather the source materials. Review the requirements, code, interfaces, configuration, ADRs, runbooks and direct answers from the system owner.
- Separate fact from intent. For every important claim, record its source, status, date and what is unknown.
- Describe the external context and responsibilities. Name the people, systems, components and data; record who is responsible for state.
- Choose the critical scenarios. Show the normal path and the actions where a state change, an integration or a partial failure has expensive consequences.
- Add important quality attributes, access, deployment and risks. Include information that changes the structure or a decision; for unknowns, write down how they will be checked.
- Check the links. Trace from a significant requirement to a criterion, from a responsibility to code or a contract, from a risk to an owner.
- Reconcile the text with the implementation. Ask a developer to walk through the key path and, separately, to confirm the state of the infrastructure. Do not accept matching file names as evidence of deployment.
- Check readability and currency. Will a reader find the boundary, the data owner, the failure path, the date and the open question without verbal explanations? Record the owner and the trigger for the next update.
Before publishing, go through the full checklist:
- the title and status say exactly what is described and as of when;
- the system boundary does not confuse your own code with an external supplier;
- every significant part has a responsibility, an interface and an owner of its state;
- the normal path and at least one important failure path are described;
- the current state is separated from the agreed or proposed future;
- quality figures are tied to a requirement and a method of measurement;
- personal data, permissions, secrets and recovery are described to the extent the risk requires;
- links lead to versioned code, contracts and decisions;
- every unknown has an owner or is clearly marked as unresolved;
- the text can be read without the author's verbal explanations and contains no claims broader than the available evidence.
Summary
A SAD is a map of the system's important boundaries and arguments, not a catalogue of every technical detail. Start with the reader, the purpose and the status of the claims; show the external context, responsibilities, data, flows and failures; separate the current code from the agreed design; give sources, risks and open questions. Links to code and its change history make it possible to verify the description, but they do not prove the state of deployment. Keep the document alive through an owner and update triggers, and size it according to risk and benefit.
Sources and further reading
Online sources and the repository were checked on 10 October 2026. The sources below support the description of the structure and the examples; they do not prescribe a mandatory number of sections or pages for every project.
- arc42. Template overview, method and documentation of the twelve sections. Used for the structure covering goals, constraints, context, building blocks, scenarios, deployment, quality, risks and glossary; the authors themselves allow the scope to be adapted.
- Simon Brown. The official C4 site, in particular abstractions, introduction, the container level, the component level and FAQ. Used for the names of the levels of detail and the limits of the model; this unit does not reproduce its diagrams.
- PostgreSQL Global Development Group. Table constraints and partial indexes. Used to distinguish an ordinary unique constraint from a partial unique index, which enforces uniqueness only for rows matching a predicate.
- Temporal. Architecture overview, History Service description and code for the Workflow Task completion handler. These are the project's own materials, not an independent audit. Commit 25d19f4, 5 June 2026 documents the fix of stale links to code after it was moved.
- OpenAI. A practical guide to building agents. Used as a current official source for bounded instructions to an agent, human responsibility and gradual adoption.
- Anthropic. "Building effective agents", 19 December 2024. Used for the limits of automation, criteria and the need for human assessment of system-level requirements; the authors note that the tooling environment has changed since publication.