The code
is the graph.
AOA is a Python framework where the business operation is the backbone of the whole architecture. The operation declares for itself who may run it, what it depends on and what happens on failure; a graph is assembled from the catalogue of operations, and it checks itself and keeps infrastructure from bleeding into business logic. A violation is caught at start-up, not in production three weeks later.
pip install aoa-action-machine01 — Overview
Architecture lives in the code.
Not in people's heads, not in documents.
Systems don't die from bugs — they die because, over time, people stop understanding them, as the team changes and complexity grows. We're taught to write code, apply patterns, follow style guides — but not how to manage complexity and keep a system readable a year, two, five years on. Every minute spent figuring out "what's going on here" is a minute stolen from progress.
AOA takes a different approach: it moves knowledge about the architecture and the system out of people's heads and out of stale documents — into the code, as machine-readable intent: executable, verifiable, observable.
Design the model, then the code.
Entities, their relations and their lifecycle are declared as a pure domain model — not bound to an ORM or any particular database. The integrity of the whole model is checked at start-up.
Design the model →One class, the whole operation.
Roles, steps, compensations, errors, cache and dependencies are declared in one place — Params in, Result out, and no side doors between them.
Action X-Ray.
See the input, output and duration of every Aspect in one operation — in development, tests or production, without observability code inside business logic.
Open Action X-Ray ↗Hexagonal architecture ports, not dependencies.
@depends / @connection / @context_requires declare an Action's ports — adapters plug in from outside. box.resolve(T) returns only what's declared.
Swap the world, not the code.
The real pipeline runs unmodified against a substituted world — user, mocks, context. What passes here is what runs in production.
02 — Maxitor
The graph above is real.
Here's the tool that draws it.
Every domain, role and dependency becomes a node the moment it's imported — four different views of the same running system, all unedited exports.
03 — Capabilities
More than a pipeline.
The same declared graph feeds caching, business events, observability and agent frameworks — without a line of glue.
The machine that runs them.
Every Action is run by one machine — roles, cache, events and rollbacks are its doing.
Cache, declared.
cache_key short-circuits straight to the result; a miss runs the aspect and on_cache_write stores it.
Events are facts, not log lines.
box.info(Channel.business, …) emits a typed fact — read by Maxitor, OCEL and dashboards alike.
Observers can't intervene.
Remove every observer and behavior doesn't change. Telemetry is a guarantee, not a hope.
One place errors end up.
A single end-of-pipeline handler decides what an operation returns after a failure.
Sagas roll back on their own.
A failure unwinds completed steps in reverse, each rollback declared beside its step.
RBAC first. Then ABAC, ever deeper.
check_roles — pure RBAC: does the role exist. grant.when / guard= — lightweight, call-level ABAC. access_decide() — complex ABAC: object and fact.
Domain and Role — the map of the system.
Domain is the assembly point for an operation: graph, access matrix, visualization. Role carries authority through inheritance. Together, they are that matrix.
04 — Extension
The core stays small.
The ecosystem doesn't.
The same declared graph plugs the operation into the outside world: agent frameworks, queues, interfaces, observability. The core doesn't grow — what's around it does.
A typed node in LangGraph.
LangGraphController drops an Action into an agent graph — same contracts, same rollback.
One Action. Every front.
An HTTP route and an AI tool call are the same operation, with the same guarantees.
OCEL/OCPM — the real process, not a diagram.
Every run already produces lifecycle events. Process mining sees the real process and the entities involved through them — not a diagram drawn once.
OTel — the same events, as a trace.
The same plugin that builds the online projection for Action X-Ray emits events to OpenTelemetry: one source — traces and a live run at once.
Intent-Based UI — not a boolean, but a verdict.
Route → typed capability: verdict() explains the denial, call() invokes it. One contract — React, mobile, Flet.
Digital twin
Actom combines entity, behavior, and agent in a single declaration — a stateful actor, observable through Action X-Ray. Files become a graph twin of the system.
05 — Concepts
Five topics.
Each will become its own page.
Five independent angles on one system — from the grammar of declarations to AOA's place among other frameworks.
01
Enforced grammar
Grammar turns every rule into a declaration (@check_roles, @depends, validators, naming rules); on import, these declarations merge into a graph — so the code is the specification, and the graph is its machine-readable form: checked at startup, drawn in Maxitor, tracked by Action X-Ray — not documentation floating somewhere near the code.
02
The AOA Constitution
Eight primitives and a set of mandatory declarations — not a style convention, but a law the machine checks on every import. You can't break it quietly: a violation stops the run, it doesn't surface in production.
03
Separation of business logic and infrastructure
Action knows the domain and the rules; how to reach Postgres, Kafka, or a third-party API is known by the adapter behind the Gateway or Resource interface. Business code never imports drivers directly — the boundary runs through the declaration, not through an agreement between developers.
04
Principles of intent-oriented programming
Code declares intent — what should happen and under what conditions — not a sequence of calls into specific libraries. The implementation is plugged in through ports (@depends, @connection) and can change while the intent stays the same.
05
Comparison with other frameworks
FastAPI and Django give you routing and an ORM, but they don't check roles, dependencies, and state transitions at import time. LangGraph explicitly builds a graph, but for orchestrating LLM steps, not business operations. AOA sits closer to both at once — an executable graph of application architecture, not just transport or a model pipeline.
06 — Practical recipes
One task.
One recipe.
Task-oriented, not tutorial-oriented — you already know what you're building. These say how.
The skeleton of an Action: Params, the aspect pipeline, checkers, connections.
A decision guide for the primitive that actually fits what you're building.
Wrap a database, queue, or SDK as a managed, injectable dependency.
Expose existing Actions over a new protocol, without touching them.
Plug a different cache backend into cache_key / on_cache_write.
Observe the machine's lifecycle events, with no right to intervene.
Wire a custom authentication scheme into @check_roles.
Route box.info / box.warning to wherever your logs actually live.
Declare a new participation grammar other primitives can opt into.
Add your own slice to what an Action can see about the call.
Bring an existing codebase in gradually, Action by Action.
07 — faq
Questions, answered.
Isn't this just Clean Architecture with more decorators?
The difference is enforcement. Clean Architecture is a set of conventions a reviewer checks by hand. AOA's contracts — @check_roles, @depends, state checkers — are checked by the machine before the operation runs, not by whoever remembers to look.
Do I have to rewrite my existing FastAPI or Django app?
No. aoa-fastapi-adapter exposes existing Actions as ordinary routes. You adopt Action by Action, not framework by framework.
What does the aspect pipeline cost at runtime?
Each aspect is a plain async call — no serialization, no process boundary. The cost is the checks you asked for, not the framework around them.
Do I need Maxitor to use AOA?
No. Maxitor reads the same Domains, Roles and Actions you already declared — it's a viewer, not a dependency. AOA runs without it.
How is @compensate different from a database transaction?
A transaction rolls back one datastore. @compensate rolls back a business operation that may have touched a payment gateway, an email service and a database — none of which share a transaction boundary.
When is AOA the wrong tool?
For a script, a one-off CLI tool, or a service with a single operation and no compliance requirement, plain functions are the right call.
⭐ If you like AOA, star it on GitHub and join the discussion on GitHub Discussions.
