Using AAA¶
AAA (Autonomous Agent Architecture) is a Python library for everything around the model in an agent system. It does not ship "an agent class". You pick an algorithm (a ToolLoop, a workflow DAG, a generate-and-verify loop, ...), run it on an engine, and the engine supplies the parts that are easy to get wrong:
| AAA gives you | So you do not hand-write |
|---|---|
| Append-only event log as the source of truth | ad-hoc state, "what did the agent do?" archaeology |
Budget (steps, tokens, cost, deadline) |
runaway loops and surprise bills |
Typed @tools with schemas, timeouts, approval gates, idempotency keys |
glue code and unsafe retries |
SSRF-hardened http_fetch, web_search (SearXNG/Brave), MCP client |
a fetch tool a web page can turn against you |
SQLiteEventStore + engine.recover() |
resumable, crash-safe runs on one machine |
| Validators, guardrails, human approval | "the prompt says please" as a safety mechanism |
It is MIT-licensed and on PyPI: pip install autonomous-agent-architecture.
This agent uses it for every stage, so the examples below are real code from this repository.
The 5-step pattern¶
Every AAA program, including this one, has the same shape.
Plain Python functions become tools. Types and the docstring become the schema the model sees; unknown arguments are rejected.
An algorithm decides what happens next. Pick the simplest that fits (guide).
from aaa.algos import Node, WorkflowGraph
graph = WorkflowGraph(
[Node("gh0", "gh_search", args={"topic": "ai-agent"}, on_error="continue"),
Node("hn0", "hn_search", args={"query": "AI agent"}, on_error="continue"),
Node("ingest", "ingest_and_score", after=["gh0", "hn0"],
args=lambda r, inp: {"batches": [r.get("gh0"), r.get("hn0")]})],
output="ingest",
)
Handlers answer the engine's requests: the LLM, tools, validators. They attach to the engine, which is the only moving part.
from aaa.runtime import Engine
from aaa.llm import LLMHandler, build_client, LLMConfig
from aaa.tools import ToolHandler, ToolRegistry
from aaa.validate import ValidationHandler
from aaa.events import SQLiteEventStore
async with Engine(store=SQLiteEventStore("aaa_events.db")) as engine:
LLMHandler(build_client(LLMConfig(model="gpt-4o-mini", base_url="https://api.openai.com/v1"))).attach(engine)
ToolHandler(ToolRegistry([gh_search, hn_search])).attach(engine)
ValidationHandler({"facts": my_validator}).attach(engine)
Our full engine wiring is runtime.py:
return ingest_and_score
@asynccontextmanager
async def runtime(s: Settings | None = None, llm_client=None, fetcher: RawFetcher | None = None):
s = s or Settings()
s.data_dir.mkdir(parents=True, exist_ok=True)
repo = Repo(s.botshub_db)
fetcher = fetcher or RawFetcher(FetchPolicy())
registry = build_registry(repo, s, fetcher)
store = SQLiteEventStore(s.events_db)
def pages_for(ctx: dict) -> dict[str, str]:
cid = ctx["task"]["candidate_id"] if isinstance(ctx.get("task"), dict) else None
return {p["url"]: p["text"] for p in repo.pages(cid)} if cid else {}
client = llm_client or build_client(llm_config(s))
if any(is_local(m, s.local_prefixes) for m in (s.critic_model, s.embed_model, s.llm_model)):
client = RoutingClient(client, LocalClient(), s.local_prefixes)
async with Engine(store=store) as engine:
LLMHandler(client).attach(engine)
ToolHandler(registry, pools={"default": 16, "github": 2, "hn": 2, "searxng": 2, "fetch": 6, "db": 1, "browser": 2}).attach(engine)
ValidationHandler({"facts": make_validator(pages_for), "classify": classify_validator,
"default": json_validator(Classification)}).attach(engine)
try:
from botshub_agent.algos import make_algos
Where to go next¶
- Choosing algorithms: which AAA algorithm we used at each stage, and why.
- Patterns we used: reusable shapes (verification gates, single-tool graphs, trusted run metadata, routing between local and hosted models).
- Lessons from a live run: sharp edges we hit, so you do not have to.
- AAA's own docs: Getting started, Algorithms, Tools, Persistence and recovery.
AAA is alpha (0.x)
Its own status page is deliberately blunt about what is verified. We pin the version and wrap it behind thin interfaces
(algos.py, runtime.py, local.py). See status and known gaps.