--- slug: boundary type: concept summary: "The line that separates one part of a system from another, letting a team reason about what's inside, what's outside, and what must hold at the crossing." created: 2026-04-04 updated: 2026-05-23 related: abstraction: relation: implied-by note: "Every abstraction implies a boundary between what is exposed and what is hidden." agent: relation: scopes note: "Boundaries define where an agent's work begins and ends." architecture: relation: scoped-by note: "Architecture decisions determine where the major boundaries fall." architecture-fitness-function: relation: used-by note: "Many fitness functions enforce module boundaries and dependency rules." big-ball-of-mud: relation: violated-by note: "Mud has no meaningful boundaries." bounded-agency: relation: depended-on-by note: "Agency envelopes sit on top of system and team boundaries; unclear boundaries produce unclear authority." bounded-context: relation: informed-by note: "Bounded contexts provide a domain-driven rationale for placing system boundaries." cascade-failure: relation: prevents note: "Clear component boundaries with defined failure contracts limit cascade paths." component: relation: defines note: "Boundaries define what is inside a component or module." contract: relation: enforces note: "Contracts are verified at boundaries." conways-law: relation: used-by note: "Team and agent boundaries become system boundaries." coupling: relation: measured-by note: "The amount of traffic across a boundary indicates coupling." decomposition: relation: produced-by note: "Decomposing a system creates new boundaries." dependency: relation: crossed-by note: "Dependencies reach across boundaries, creating coupling." domain-model: relation: informed-by note: "Map to system boundaries." greenfield-and-brownfield: relation: related note: "A consumer on the other side of a boundary is what turns greenfield into brownfield." harnessability: relation: related note: "Clear boundaries are a core harnessability property." interface: relation: hosts note: "An interface lives at a boundary." inverse-conway-maneuver: relation: produced-by note: "The maneuver creates system boundaries by creating team boundaries." local-reasoning: relation: enables note: "Clear boundaries make local reasoning possible." make-illegal-states-unrepresentable: relation: enables note: "Constructors that enforce invariants define boundaries between valid and invalid state." module: relation: defines note: "Boundaries define what is inside a component or module." monolith: relation: resists note: "Clear boundaries counteract monolithic drift." ownership: relation: depended-on-by note: "Clear boundaries make ownership assignable; blurred boundaries create ownership disputes." separation-of-concerns: relation: supports note: "Boundaries enforce the separation between different concerns." strangler-fig: relation: related note: "The routing layer introduces a clear boundary between old and new implementations." task-decomposition: relation: supports note: "System boundaries guide how work is divided among agents." team-cognitive-load: relation: depended-on-by note: "Boundaries define the scope of what a team or agent must reason about." --- # Boundary *A boundary is the line that separates one part of a system from another, and the word is what lets a team reason about what's inside, what's outside, and what has to be true at the crossing.* > **Concept** > > Vocabulary that names a phenomenon. ## What It Is A boundary is the dividing line between one part of a system and another. It's the membrane between inside and outside, between "my responsibility" and "yours," between a [component](component.md)'s internals and the rest of the world. Some boundaries are physical (a process boundary, a network boundary, a service boundary). Others are conceptual (a module boundary, a layer boundary, a domain boundary). The word covers all of them, because the structural question is the same in every case: what belongs on each side, and what passes across. Boundaries exist at every level of scale, and it helps to keep the levels distinct in vocabulary: - **Function and class boundaries.** The narrowest kind. The signature of a function and the public surface of a class are boundaries; the body and the private fields are inside. - **Module and package boundaries.** A grouping of code that depends on its neighbors through a declared interface. The contents are reachable to the inside and opaque to the outside, when the language and tooling let you say so. - **Component and service boundaries.** A deployable unit, a process, a microservice. The contract between two services is the boundary; the database, the cache, and the internal classes are the inside. - **System and integration boundaries.** Where an application meets another application or another organization. Webhook endpoints, public APIs, file-format contracts. - **Organizational boundaries.** Team boundaries, agent boundaries, role boundaries. These are also boundaries, and per [Conway's Law](conways-law.md) they end up showing through the code one way or another. Three properties are worth keeping in vocabulary alongside the levels. A boundary has a **surface**: the interface, contract, or call signature where the crossing happens. The size of the surface determines how much can leak across. It has **enforcement**: the mechanism that holds the boundary in place, ranging from compiler visibility rules to code review to network isolation. An unenforced boundary is one that exists on paper only. And it has **direction**: which side calls the other, which side defines the contract, which side knows about the other. A boundary that allows traffic in both directions is doing something different from one that allows traffic in one. In agentic coding the same vocabulary applies, with one operational difference. A boundary doesn't just structure the code anymore; it scopes the agent. When you tell an agent "work within this module," the boundary names the read set, the write set, and the interfaces the agent must respect. A boundary the agent can name is a boundary the agent can stay inside; a boundary that exists only in the team's heads is one the agent will routinely cross by accident. ## Why It Matters Software is too large to hold in one mind at once, and the only way to make it tractable is to slice it into pieces that can be reasoned about separately. The boundary is the slice. A team without the vocabulary describes each problem locally ("the search code keeps reaching into the cart") rather than naming the recurring class behind those problems ("the boundary between search and cart isn't holding"). Naming the class is what makes the response designable. A boundary in the right place does four things at once. It contains failure: a fault on one side doesn't sink the other side as long as the contract holds. It enables ownership: a team or agent can be responsible for everything inside it without needing to coordinate every change. It supports independent evolution: the inside can be rewritten without breaking the outside, as long as the contract is preserved. And it bounds reasoning: a reader (or an agent) can understand what happens inside without holding the whole system in mind. A team fluent in boundaries collapses dozens of architecture micro-decisions into a single question: *what should be inside this boundary, and what crosses it?* For agentic workflows the diagnostic urgency is sharper. The rate of edits goes up when agents are doing the writing, and the rate of accidental cross-boundary changes scales with edits unless the boundaries are explicit. An agent given a fuzzy boundary will routinely reach into another module to "fix" the thing it's working on, because nothing in the prompt or the structure of the code told it to stop. An agent given a sharp, enforced boundary works inside it and respects the contract at the edge. The boundary isn't just a design quality anymore; it's the unit of safe agent autonomy. ## How to Recognize It A well-placed, well-enforced boundary feels invisible most of the time and obvious when you need to change something. A few concrete signs to look for, and a few to look for when boundaries are missing or drawn wrong: - **Independent change.** Edits inside the boundary don't require edits on the other side. If a change to one module forces changes in five others before the build passes, the boundary isn't holding — what you have is one tangled module with five names. - **A nameable contract.** You can point at the interface, the schema, the API spec, the public method list, and say "this is what crosses." If the contract is "whatever the consumers happen to depend on at the moment," the boundary is implicit, and implicit boundaries drift. - **Failure containment.** A bug, exception, or outage on one side doesn't cascade. The other side notices through a defined failure mode (a returned error, a timeout, a fallback path) rather than through silent corruption. - **Assignable ownership.** One team or one agent owns everything inside. Disputes about who owns what are usually disputes about where the boundary actually falls. - **Local reasoning.** A reader looking at the inside can reason about it without loading the rest of the system. The agent's context window doesn't have to include the world to make a confident edit. When boundaries are missing or drawn wrong, you see the inverse patterns. Boundaries that are too coarse leave you with large, tangled units that everything depends on. Boundaries that are too fine create communication overhead and indirection, where every operation crosses six interfaces and the cost of crossing dominates the cost of doing. Boundaries that aren't enforced erode over time: code reaches across them, knowledge of the other side's internals leaks in, and within a year the boundary exists only on paper. Boundaries placed by the org chart rather than by the domain (or vice versa) feel arbitrary and accumulate exceptions, because the code keeps trying to follow a structure the boundaries don't reflect. > **💡 Tip** > > The honest test for a boundary is the rate-of-change test: if you change something on one side, how much changes on the other? If the answer is "a lot," the boundary is in the wrong place, isn't enforced, or doesn't really exist. ## How It Plays Out A backend team draws a boundary between their API layer and their data access layer. The API layer handles HTTP concerns (routing, serialization, authentication). The data access layer handles persistence (queries, caching, transactions). Neither layer reaches into the other's internals. When the team later migrates from one database to another, the API layer doesn't change at all. The contract between the layers was the database-agnostic shape of a repository interface, and the migration replaced the implementation behind the interface without touching anything else. A platform team realizes that "ownership of the notifications service" is contested. Two teams have been editing it for months, each with its own conventions, and recent outages keep being traced to interactions between code one team wrote and code the other team wrote. The fix isn't more coordination; it's pulling a sharper boundary. They formalize the notification API as a single contract, hand the implementation behind it to one team, and let the other team become a consumer. The boundary that already existed in the org chart now exists in the code too, and the outage class disappears within a quarter. An AI agent is tasked with adding a feature to a large repository. The developer scopes the task: "Work only within the `notifications/` directory. The interface with the rest of the system is the `NotificationService` class. Don't change its public methods, and don't touch any file outside the directory." The boundary tells the agent what files to read, what interfaces to respect, and what's out of scope. The agent makes confident changes inside the module and stops at the contract. A previous attempt without a clearly named boundary had the agent "fixing" a related issue three modules over, which created a downstream regression that took two hours to track down. The structural lesson is the same one human engineers have learned for decades, applied to a new kind of contributor: bounded work needs a named boundary. > **💡 Example Prompt** > > "Work only within the notifications/ directory. The interface with the rest of the system is the NotificationService class — don't change its public methods. You can refactor anything inside the module freely." ## Consequences When a team has the vocabulary of boundaries and uses it deliberately, the structural arguments get smaller. Discussions about "should this go in package X or package Y" are settled by pointing at the boundary and asking which side the new code's responsibilities live on. Changes stay where they're put. Ownership is assignable. Agents can be turned loose inside a module without reaching outside it. The honest tradeoffs are worth naming, because drawing more boundaries isn't free. - **Every boundary has a crossing cost.** Each interface, contract, or API introduces indirection, marshalling, and a place where the two sides can drift out of sync. Over-bounded systems spend more time crossing boundaries than doing the work the boundaries were supposed to enable. - **Wrong boundaries are worse than mild tangling.** A boundary placed in the wrong spot ossifies the wrong cut. The contract calcifies, every change has to route around it, and the boundary becomes a permanent feature of the system's friction rather than a temporary scaffold the team can revise. - **Boundaries need enforcement to stay real.** A boundary that lives only in the README erodes. The enforcement mechanism — module visibility, deployment isolation, code review discipline, an automated architecture-fitness check — is what keeps the boundary honest a year later. - **Some boundaries are necessary friction.** A team boundary or a service boundary that genuinely separates two domains will cost you coordination across it. The vocabulary helps you distinguish the necessary cost from the accidental kind, but it doesn't make the necessary cost go away. The goal isn't more boundaries; it's appropriate boundaries in the right places, enforced enough to hold, with the inside free to evolve and the outside dependent only on the contract. ## Sources - David Parnas's "[On the Criteria To Be Used in Decomposing Systems into Modules](https://dl.acm.org/doi/10.1145/361598.361623)" (*Communications of the ACM*, 1972) is the foundational argument for placing boundaries around design decisions that are likely to change. The rate-of-change test in the How to Recognize It section is Parnas's information-hiding principle restated for the agentic era. - Eric Evans's *[Domain-Driven Design](https://www.domainlanguage.com/ddd/)* (Addison-Wesley, 2003) supplies the domain-driven rationale for where major boundaries fall, through the bounded-context concept referenced in the front matter. Evans argues that model boundaries should follow regions where a particular language and meaning apply, rather than organizational charts or deployment topology. - Michael Nygard's *[Release It!](https://pragprog.com/titles/mnee2/release-it-second-edition/)* (Pragmatic Bookshelf, 2nd ed. 2018) frames boundaries as failure-containment devices in distributed systems. The bulkhead pattern — boundaries sized so that a fault inside one cannot sink the whole ship — is the practical form of the failure-containment property described above. --- - [Next: Cohesion](cohesion.md) - [Previous: Contract](contract.md)