Five levels of immersion in a complex system

How do you make sense of a system's structure when you first open a new project — or come back to your own code a few months later?

In both cases a developer has to build the same internal map: working out where responsibility is divided, which capabilities exist, and how they connect to the concrete implementation. Experience helps you find the main anchor points faster, but when they are not expressed in the architecture itself — or shift from project to project and module to module — the way in has to be reinvented every time.

AOA moves that way in out of an experienced developer's head and into the structure of the system itself. From project to project it keeps the same five pillars: Domain, Action, Contract, Pipeline, and Code. Each pillar becomes its own level of immersion, and together they lay out a sequential path from the overall picture to concrete behavior.

A developer moves through these levels as if zooming in on a map step by step: first understanding the system as a whole, then adding detail. Each level gives a complete answer to its own question and does not require knowing in advance what lies deeper.

Documentation ↗

When all levels of context are mixed together

What prevents you from seeing the overall picture when every detail is available at once?

An ordinary repository does not separate information by scale: business areas, operations, data models, call order, infrastructure, and individual lines of code are all presented at once. Working memory has to hold facts from different levels and reconstruct the relationships between them on its own. Attention keeps switching between the structure of the system and local details, so understanding falls apart quickly after a break.

How the lack of a larger picture affects the work

Below, the same set of information is shown in two states: on the left it is mixed together, on the right it is arranged into five sequential questions. Let's start with the first: where does responsibility live in the system?

flat codebase · new facts without a frame
orders.pyBillingServiceapi.pycreate_orderreserve_stockOrderModelsend_mailutils.pydb.py
five levels · context is built in sequence
01Domainwhere responsibility lives
02Actionwhat the system can do
03Contractwhat the operation requires and promises
04Pipelinehow the scenario unfolds
05Codewhere concrete behavior lives
Full docs ↗

First, a map of responsibility

Where does responsibility live?

The first pillar is a map of responsibility. It gathers controllers, services, data models, infrastructure adapters, and call chains around the system's major areas — a frame to which operations, data, and code can then be attached.

Domains divide the system by durable areas of responsibility rather than by technical layers: orders, payments, communication, and analytics. Hundreds of equal-looking files give way to the first observable topology.

What the domain map provides

In the diagram, four domains form the top level of the application. From here we will unfold StoreDomain in sequence: first its Actions, then one operation's contract, its scenario, and its concrete implementation.

4 × ActionStoreDomain

Owns cart, ordering and the handoff to delivery

3 × ActionBillingDomain

Owns payments, invoices and refunds

3 × ActionMessagingDomain

Owns customer communication and webhooks

3 × ActionAnalyticsDomain

Owns events, marts and business reporting

Full docs ↗

The map becomes a catalog of capabilities

What can the system do?

The domain map shows where responsibility lives, but it does not yet say what the system can do. To answer that, each domain opens into a catalog of Actions — named capabilities it provides to the rest of the system.

An Action is a named public capability of a domain. The catalog shows not internal functions but the operations a domain provides to the rest of the system, along with the directed relationships between them. Each Action becomes a single point of invocation: the caller picks the capability it needs by meaning, instead of assembling the operation from separate methods.

What the Action catalog provides

The diagram highlights CreateOrderAction, the Action that starts order creation. Other StoreDomain Actions and the relationships between them appear alongside it. One operation may depend on another, but the dependencies are directed and never close into a cycle. At this level we see only Action names and their relationships; input, result, scenario, and code will be revealed later.

StoreDomainnamed executable capabilities
CreateOrderAction

Create an order: validate, reserve and charge

→ ChargePaymentAction

GetOrderAction

Read the current state of an order

CancelOrderAction

Cancel an order and initiate a refund

→ RefundPaymentAction

ShipOrderAction

Hand a confirmed order to delivery

Full docs ↗

Fix the operation boundary

What does the operation require and promise?

The capability already has a name and a place in the system. To use it as a black box, we need to know exactly which data it accepts and which result it promises to return.

A Contract makes the Action boundary typed and unambiguous. Params describe everything the operation accepts; Result describes everything it must return. Calling code depends on that promise, not on the internal shape of the Action. The implementation can change as long as input, output, and observable behavior remain compatible.

What the Action contract provides

The familiar CreateOrderAction now has a complete signature: CreateOrderParams on the left and CreateOrderResult on the right. Fields no longer exist as abstract data models; they belong to a specific operation with a known place and purpose. The inside of the Action is still not required in order to use it.

CreateOrderParams
customer_id: str
items: list[Item]
currency: str
CreateOrderAction

Coordinate order creation behind one declared boundary

Params → Result
CreateOrderResult
order_id: str
payment_id: str
status: OrderStatus
Full docs ↗

Unfold the operation into a scenario

How does the scenario unfold?

The contract fixes the beginning and end of an operation, but it does not show how one becomes the other. To see that, we unfold the internal flow of the business scenario.

A Pipeline makes the main business scenario linear and visible: validate the input, reserve inventory, charge payment, and assemble the result. That does not mean errors, branches, and compensations do not exist; they receive an explicit place relative to the main line. Every step also has a local contract: it declares what it reads from Params, State, and Context, and what it adds to State for later stages.

What the operation scenario provides

The domain gave the scenario a place, the Action a name, and the Contract a beginning and promised outcome. The Pipeline adds causal order and internal boundaries. The reserve_inventory step is isolated as a distinct state change: it receives already validated data and leaves reservation_id for the scenario to continue.

regularvalidatevalidated_items
regularreserve_inventoryreservation_id
regularcharge_paymentpayment_id
summarycreate_resultOrderResult
Full docs ↗

Only now, the concrete behavior

Where does the concrete behavior live?

The Pipeline locates the required behavior precisely and sharply narrows the search area. We can now open not the entire repository or even the entire Action, but one step whose purpose, input, and output are already known.

Code is the fifth level not because details are unimportant, but because they are now surrounded by meaning. We know which domain owns the behavior, which capability the Action implements, what the Contract promises, and where reserve_inventory sits in the Pipeline. The implementation reads as a local answer to a concrete task rather than an entrance to an endless investigation of the repository.

What isolating behavior in one step provides

When we open reserve_inventory, we already know its intent, inputs, and place in the scenario. The principle stays the same: external effects should pass through declared Resources and public Actions. The architectural grammar makes deviations visible and verifiable without pretending that descriptive text alone already makes every bypass physically impossible.

store/actions/create_order.py
@regular_aspect("Reserve inventory")@result_string("reservation_id", required=True)@context_requires("user.tenant_id", "env.inventory_region")async def reserve_inventory_aspect(    self, params, state, box, connections, ctx):    inventory = box.resolve(InventoryResource)    reservation_id = await inventory.reserve(        tenant_id=ctx.get("user.tenant_id"),        region=ctx.get("env.inventory_region"),        items=state["validated_items"],    )    return {"reservation_id": reservation_id}
$ uv run python store/actions/create_order.py
output
Full docs ↗

The five levels do not hide code or reduce the system to a diagram. They preserve the path from the overall model to a concrete detail. A newcomer builds context progressively, an architect manages grammar and boundaries instead of every file, and an AI agent performs local work within declared rules: people design the language of the system, and the machine acts in that language.

Five levels of immersion in a complex system · aoa.run