Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

AOA

Python 3.12+ Apache-2.0 aoa-action-machine

aoa-action-machine

The core of AOA — a framework where a business operation is described as an executable contract. Every operation is an Action class with a typed input, output, and a straight chain of steps; the machine reads this description and executes it literally.

This README is a quick start: from installation to an operation published over HTTP and as an AI-agent tool. It deliberately does not cover everything — the full picture is in the tutorial, with links to individual topics placed along the way.


Installation

pip install aoa-action-machine

Optional extensions are installed as needed:

pip install "aoa-action-machine[fastapi]"   # HTTP API
pip install "aoa-action-machine[mcp]"        # tools for AI agents
pip install "aoa-action-machine[postgres]"   # asyncpg connections
pip install aoa-ocel                  # OCEL 2.0 event log

Your first action

An operation is an atomic business operation. Let us assemble the simplest one: it takes a name and returns a greeting. Three declarations are mandatory: the domain (@meta), access (@check_roles), and a single exit point (@summary_aspect).

from aoa.action_machine.auth import GuestRole
from aoa.action_machine.context import Context
from aoa.action_machine.domain.base_domain import BaseDomain
from aoa.action_machine.intents.aspects import summary_aspect
from aoa.action_machine.intents.check_roles import check_roles
from aoa.action_machine.intents.meta import meta
from aoa.action_machine.model import BaseAction, BaseParams, BaseResult
from aoa.action_machine.runtime.action_product_machine import ActionProductMachine
from pydantic import Field


class GreetingDomain(BaseDomain):
    name = "greeting"
    description = "Greeting domain"


class GreetParams(BaseParams):
    name: str = Field(description="Recipient name")


class GreetResult(BaseResult):
    message: str = Field(description="The assembled greeting")


@meta(description="Greet by name", domain=GreetingDomain)
@check_roles(GuestRole)            # GuestRole — the operation is open to everyone (declared explicitly)
class GreetAction(BaseAction[GreetParams, GreetResult]):

    @summary_aspect("Assemble the greeting")
    async def greet_summary(self, params, state, box, connections):
        return GreetResult(message=f"Hello, {params.name}!")

BaseParams and BaseResult are Pydantic models; field descriptions go into the external schema. Inside the operation there is no state — everything arrives through params.


Running it

The single entry point is ActionProductMachine. It reads the declarations and guides the operation along the pipeline.

import asyncio

async def main() -> None:
    machine = ActionProductMachine()
    result = await machine.run(Context(), GreetAction(), GreetParams(name="Alice"))
    print(result.message)   # Hello, Alice!

asyncio.run(main())

Context() is the call environment (user, roles, metadata); here it is empty.


Several steps and the state contract

Usually an operation consists of several steps. An intermediate step — @regular_aspect — returns a dict that becomes the new state. A checker @result_* declares the contract: what must appear in state after the step.

from aoa.action_machine.intents.aspects import regular_aspect
from aoa.action_machine.intents.checkers import result_string

@meta(description="Greet by name", domain=GreetingDomain)
@check_roles(GuestRole)
class GreetAction(BaseAction[GreetParams, GreetResult]):

    @regular_aspect("Normalise the name")
    @result_string("name", required=True, min_length=1)
    async def normalise_aspect(self, params, state, box, connections):
        return {"name": params.name.strip().title()}

    @summary_aspect("Assemble the greeting")
    async def greet_summary(self, params, state, box, connections):
        return GreetResult(message=f"Hello, {state['name']}!")

Aspects run strictly top to bottom. If a step does not fulfil the checker's contract, the machine stops the pipeline at its boundary — the next step does not run. In detail — in the tutorial: Action and pipeline and State: an x-ray of the operation.


Dependencies and logs

Everything external is declared by the operation in its header via @depends and obtained through box.resolve(...); the machine will not hand out an undeclared dependency. Business events are written through box, not print:

from aoa.action_machine.intents.depends import depends
from aoa.action_machine.logging import Channel

@meta(description="Greet by name", domain=GreetingDomain)
@check_roles(GuestRole)
@depends(GreeterService)
class GreetAction(BaseAction[GreetParams, GreetResult]):

    @summary_aspect("Assemble the greeting")
    async def greet_summary(self, params, state, box, connections):
        greeter = await box.resolve(GreeterService)
        await box.info(Channel.business, "greeting name={%var.name|cyan}", name=params.name)
        return GreetResult(message=greeter.greet(params.name))

Where to deliver events — console, queue, Telegram — is decided by the machine's logger, not by the operation code.


Service: FastAPI and MCP

The operation knows nothing about transport, so the same GreetAction can be exposed both over HTTP and as an AI-agent tool — by adding an adapter. The business code does not change.

HTTP (FastAPI):

from aoa.action_machine.adapters.fastapi import FastApiAdapter
from aoa.action_machine.auth import NoAuthCoordinator

machine = ActionProductMachine()

app = (
    FastApiAdapter(machine=machine, auth_coordinator=NoAuthCoordinator(), title="Greetings API")
    .post("/greet", GreetAction, tags=["greetings"])
    .build()
)
# uvicorn app:app

FastApiAdapter publishes the operation as a REST endpoint with a ready-made OpenAPI schema derived from Params/Result.

MCP (AI agents):

from aoa.action_machine.adapters.mcp import McpAdapter
from aoa.action_machine.auth import NoAuthCoordinator

server = (
    McpAdapter(machine=machine, auth_coordinator=NoAuthCoordinator(), server_name="Greetings MCP")
    .tool("greetings.greet", GreetAction)
    .build()
)
# python mcp_server.py

The detailed chapters: Step 13 — FastAPI adapter · Step 14 — MCP adapter.


What's next

That was the quick start. The full tutorial is on the Contents page: it leads from the first operation to the service layer and the domain model, with examples and review questions.

Useful by topic:


License

Apache-2.0

Metadata

Release files for aoa-action-machine 1.0.1a4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aoa-action-machine 1.0.1a4
File Size Uploaded Provenance
aoa_action_machine-1.0.1a4.tar.gz 641.5 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for aoa-action-machine 1.0.1a4
File Interpreter ABI Platform
aoa_action_machine-1.0.1a4-py3-none-any.whl Python 3 none any Details

Total release size: 1.2 MB

Release files / aoa_action_machine-1.0.1a4.tar.gz

Download URL aoa_action_machine-1.0.1a4.tar.gz
Size 641.5 kB
Tags Source
SHA-256 checksum
How to use checksums
6f81f24abf765df7b684d8eed1634a6fdf4d1cb265ff88970c38306e20526b64
BLAKE2b-256 checksum
How to use checksums
caa1b9e5aae7967ed646a3c846bf532c0b77e167637f02a25a0033876e2e36a3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / aoa_action_machine-1.0.1a4-py3-none-any.whl

Download URL aoa_action_machine-1.0.1a4-py3-none-any.whl
Size 524.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0107a1ddd934e73ead7c3131bfc1f8b2a8970c873202adec4c2dd80da6ec50ec
BLAKE2b-256 checksum
How to use checksums
bca890904d4168197f0f8c30ce24863d9ba5ae5bde80c0d83a97be79a2603ab2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for aoa-action-machine 1.0.1a4

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page