Hello, world!
Let's start with an Action that does nothing useful — it prints a line and returns an empty stub. Its value is elsewhere: it shows the minimum of declarations AOA requires before it will run anything at all.
$ uv run python examples/step_01_Action_and_pipeline/01_hello_world.pyThat is a fair number of lines for a plain greeting — but none of them is a ritual: each declares something without which an Action in AOA is not considered complete. It all begins with a domain. @meta is the Action's passport; @check_roles(GuestRole) declares access out loud, not by default. And @summary_aspect is the single exit point — skip any of the three and the machine refuses to run the Action, before the first call.
Params, Result, and box
Stubs are fine for introductions; a real Action works with data. Let's give it an input and an output — and swap print for the instrument Actions in AOA use to speak with the outside world.
$ uv run python examples/step_01_Action_and_pipeline/02_params_result_and_box.pybox is more interesting than print — it is a structured logger bound to the current step, carrying channel, level, domain, action and aspect name, so it lends itself to filtering, routing and export. Where the event goes is not the Action's decision — that is the machine's concern, wired in from the outside.
Multiple aspects
One step is rarely enough. AOA leaves no freedom for intermediate data to hide in local variables or object fields — it flows through the pipeline in an explicit state, while the Action itself stays empty between calls.
$ uv run python examples/step_01_Action_and_pipeline/03_multiple_aspects.pyThe returned dictionary replaces state entirely — it does not extend it. @result_string("cleaned", required=True) is a checker: a verifiable contract on an aspect's output. Break the promise and the machine stops the aspect on the spot, never letting spoiled state pass further.
Inheritance
Actions inherit like ordinary classes — with one deliberate exception: aspects are not inherited into the pipeline. The machine builds the pipeline only from what is declared in the class itself.
$ uv run python examples/step_01_Action_and_pipeline/04_inheritance.pyChildOrderAction runs fine but its pipeline contains only child_summary — the parent's validate_aspect never executes. ExtendedOrderAction does it right: it re-declares the aspect and calls super() to build on the ancestor's logic.
Experiments
This chapter has accumulated a fair number of rules — but they share one property: almost all of them are checked at class declaration, at module import, not at the Action's call. Code here is an executable specification; a contract violation surfaces before the first run, not one night in production. Try it — pick a way to break SayHelloAction from the first example and press Run.
$ uv run python examples/step_01_Action_and_pipeline/01_hello_world.pySummary
The core of the model is already in hand: an operation is an Action with a typed boundary (Params and Result), a linear pipeline of aspects, and contracts that are mostly checked at initialization. Behavior is expressed in the structure of the code, not in documentation lying next to it.
Review questions
- Which invariant secures an Action's "from one class" readability, and at what moment is it checked?
- Why is the absence of
@check_rolesan error and not a silent "open to everyone"? - The AOA pipeline is linear — no branches, no side exits. What does this constraint buy, and at what cost?
- Why are aspects not inherited into the pipeline automatically? Compare with ordinary method inheritance in OOP.
- What does "code is an executable specification" mean, and why are most contracts checked at initialization rather than at runtime?
