A Python framework for building production-ready AI agent microservices with event-driven architecture.
Blueprint Agents gives you a component-based toolkit for building intelligent microservices that process events, call LLMs, expose REST APIs, and run scheduled tasks -- all wired together with a fluent builder API and backed by production-grade observability.
- Component Architecture -- Five composable base classes (
EventHandlerBase,ServiceBase,RestApiBase,AgentRuntime,SchedulerBase) assembled via a fluentAppBuilder - Event-Driven Processing -- CloudEvents v1.0 with chain-of-responsibility handlers, Dapr and NATS pub/sub support; on NATS, concurrent runs per agent with JetStream heartbeats and run timeouts, and named connections (several brokers or accounts per agent, supplied by a connection source)
- LLM Integration -- AI agents powered by Pydantic AI with structured outputs, tool calling, and multi-model support (OpenAI, vLLM)
- Built-in Observability -- OpenTelemetry tracing, metrics, and structured logging out of the box
- Multi-Agent Grouping -- Run one agent per process, or host several in one process, as a deployment choice: the agent's code is identical either way
- CLI Scaffolding -- Generate complete project structures and individual components with the
asbsCLI - Deployment Ready -- Docker, Kubernetes with Helm charts, health checks, and CI/CD patterns included
# Install the framework
pip install avs-blueprint-agents
# Scaffold a new project -- asbs setup writes into the current directory
mkdir my-agent && cd my-agent
asbs setup my-agent
# Start developing
pip install -e .
asbs devYour service is now running at http://localhost:8000 with interactive API docs at /docs,
and this agent's routes under /api -- served alone, at the root. asbs dev --name my_agent
serves it under the namespace a group image would give it, with its routes under /api/my_agent.
asbs setup writes your project's main.py as a declaration -- an AppBuilder that is never
built -- and a create_app() that builds it. It writes no agents.toml: that map belongs to an
image that hosts several agents. That is what lets the same project run on its own and be hosted
alongside other agents without changing a line. See
Running one agent, or several.
pip install avs-blueprint-agentsOr with uv:
uv add avs-blueprint-agentsAlpha builds are published to TestPyPI. TestPyPI must be used for this package only -- it also
carries stale copies of common dependencies (an ancient fastapi, an apscheduler too old for
this package), and any setup that lets the resolver reach them for dependencies fails.
With uv, name the index for this one package and nothing else:
[[tool.uv.index]]
name = "test-pypi"
url = "https://test.pypi.org/simple/"
explicit = true # only for packages that name it below
[tool.uv.sources]
avs-blueprint-agents = { index = "test-pypi" }uv add "avs-blueprint-agents==0.9.0a1"explicit = true is the load-bearing part: without it uv applies its default first-index
strategy, pins every dependency to the first index that happens to contain it, and the resolution
fails on TestPyPI's old apscheduler. That is deliberate protection against dependency confusion,
not something to work around with --index-strategy unsafe-best-match.
pip has no equivalent -- no index priority and no per-package source, so --extra-index-url
merges both indexes and the broken copies stay reachable whichever order the flags are in. Fetch
the package alone, then install it with dependencies resolved from PyPI:
pip download --no-deps --index-url https://test.pypi.org/simple/ "avs-blueprint-agents==0.9.0a1" -d ./dl
pip install --index-url https://pypi.org/simple/ ./dl/avs_blueprint_agents-0.9.0a1-py3-none-any.whlPin the exact alpha version in both cases: PyPI carries the newer stable release, and an unpinned install resolves to that instead.
git clone https://github.com/2SpeakAI/blueprint-agents.git
cd blueprint-agents
pip install -e ".[dev]"Blueprint Agents uses five composable component types, wired together with the AppBuilder:
from blueprint.agents import AppBuilder
from .handlers.order_handler import OrderHandler
from .services.order_service import OrderService
from .api.routes import OrderApi
agent = (
AppBuilder()
.with_handler(OrderHandler)
.with_service(OrderService)
.with_rest_api(OrderApi)
.with_cache()
)Two things about that chain are worth knowing up front:
- Nothing is constructed yet. Every
with_*call records a declaration;build()constructs it. Leavingbuild()out is what lets the deployment decide the configuration and the namespace each component is built in. - Pass classes, not instances.
with_rest_api(OrderApi)rather thanwith_rest_api(OrderApi()). An instance created on thewith_*line exists before any namespace does, so it belongs to the process root for ever. A group refuses them, and so doagent.build(config)and thecreate_app()that calls it; only the oldAppBuilder(config)...build()shape still accepts them.
| Component | Base Class | Purpose |
|---|---|---|
| Event Handler | EventHandlerBase |
Route CloudEvents (priority chain) and translate each into one service call; returns a result, HandlerResult.skipped(reason) or None |
| Service | ServiceBase |
All business logic, including whether an event needs work; runs the agents |
| REST API | RestApiBase |
Define HTTP endpoints with @get(), @post(), @put(), @delete(), @patch() decorators |
| Agent | AgentRuntime |
Run LLM agents with structured outputs, tool calling, and prompt management |
| Scheduler | SchedulerBase |
Execute cron-based background tasks with auto-registered trigger endpoints |
All components follow a consistent lifecycle managed by the framework:
__init__() --> Register with component registry
on_startup() --> Resolve dependencies, connect to external services
[running] --> Process events, handle requests, run tasks
on_shutdown() --> Clean up resources, close connections
How many agents share a process is a deployment parameter, not an architectural commitment.
The declaration in your project's main.py is the same either way; only what starts the
process differs.
| Shape | What starts it |
|---|---|
Standalone, a module-level app you built yourself |
uvicorn src.main:app |
| Standalone, a declaration built by a factory | uvicorn src.main:create_app --factory |
| Hosted -- one agent, or twenty | python -m blueprint.agents.entrypoint |
Unchanged, and still fully supported. Build the declaration yourself and serve it:
config = Config(settings_files=["settings.toml", ".secrets.toml"])
app = agent.build(config) # the declaration from "How It Works"A standalone agent runs at the root namespace: its registry names, REST paths (/api/...),
NATS queue group, JetStream durable and OpenTelemetry service.name are exactly what they were
before this existed. Nothing reads agents.toml unless you run the entry point.
Name each agent, its directory (root, relative to the image root) and the module its
declaration lives in, in the image's agents.toml. Both keys are required:
[agents.orders]
root = "agents/orders"
module = "agents.orders.src.main:agent"
[agents.billing]
root = "agents/billing"
module = "agents.billing.src.main:agent"The image contains those agents; the deployment decides which of them a given process runs:
docker run -e BLUEPRINT_AGENTS=orders,billing my-image # both in one process
docker run -e BLUEPRINT_AGENTS=orders my-image # a group of oneEach hosted agent gets its own handlers, agent runtime, REST routes, AI client, thread pool, caches and broker connection. The port, the health endpoint and the process are shared. Agents in one process are isolated: an agent resolves its own components, caches and configuration and the root's shared ones, and the framework refuses any attempt to reach a neighbour's.
A group of one gives today's process isolation; a group of twenty gives the shared-interpreter memory profile. Moving an agent between groups changes neither its broker-side consumer identity nor its telemetry identity.
The agent's name in
agents.tomlis its identity everywhere else -- registry prefix, NATS queue group, part of the JetStream durable, cache partition, OpenTelemetryservice.nameand the REST prefix/api/<name>. Renaming it after the first deploy is a consumer migration, not a rename. Choose it before you ship.
Other group settings: BLUEPRINT_GROUP_CONFIG and BLUEPRINT_GROUP select a named group from a
mounted file, and BLUEPRINT_CRITICAL_AGENTS says which agents failing should fail the process.
See the Multi-Agent Setup guide for the full reference and the
migration path for an existing single-agent project.
Explore complete, runnable examples in the examples/ directory:
| Example | Description | Components Used |
|---|---|---|
| inventory_api | Product inventory CRUD REST API | RestApiBase, ServiceBase, Cache |
| order_event_pipeline | E-commerce order processing with Dapr pub/sub | EventHandlerBase, ServiceBase, Dapr |
| document_summarizer | LLM-powered document summarization with structured output | AgentRuntime, AgentBuilder, Tools |
| webhook_relay | Webhook ingestion and normalization pipeline with NATS | EventHandlerBase, NATS, Cache |
| health_monitor | System health monitoring with scheduled checks | SchedulerBase, ServiceBase, Cache |
Blueprint Agents uses Dynaconf for hierarchical configuration via TOML files:
settings.toml (checked into version control):
[default]
app_name = "my-agent"
app_port = 8000
event_bus = "dapr" # "dapr", "nats", or "sessions"
log_level = "INFO"
[default.runtimes.my_agent]
model_provider = "openai"
model_name = "gpt-4o-mini"
model_max_tokens = 1000
model_temperature = 0.3
[default.cache]
cache_dir = ".cache/my-agent"
default_ttl = 3600.secrets.toml (never commit this file):
[default.runtimes.my_agent]
model_api_key = "sk-your-api-key"For the sessions transport (consumes SSE jobs from service-sessions):
[default]
event_bus = "sessions"
[default.sessions_service]
base_url = "http://sessions:8000"
agent_id = "my-agent"
agent_type = "analyser"
capabilities = ["analyse_documents"]
api_key = "@format {env[SESSIONS_API_KEY]}"
# Optional per-agent HS256 bearer token. When set, sent as `Authorization: Bearer <token>`
# alongside `X-Api-Key` on the gated sessions routes (register, SSE stream, session-key fetch).
# To opt out, OMIT this line entirely (ungated sessions, X-Api-Key only). Do NOT keep the line
# with SESSIONS_AGENT_TOKEN undefined — Dynaconf fails config load on an unresolved `@format`.
agent_token = "@format {env[SESSIONS_AGENT_TOKEN]}"
max_concurrent_jobs = 5
job_timeout_seconds = 300The bearer is only sent when
agent_tokenis configured, so agents keep working against ungated sessions and can be rolled out before the gate is flipped. The token is an offline-minted HS256 JWT whosesubequals the agent'sagent_idand whosecapsclaim is its capability ceiling (seeservice-sessions#198/#326). Never commit the token value — inject it via theSESSIONS_AGENT_TOKENenvironment variable (e.g.YOUR_AGENT_TOKEN).Opting out cleanly: because the value above is a Dynaconf
@formatinterpolation, keeping theagent_tokenline whileSESSIONS_AGENT_TOKENis undefined raisesDynaconfFormatErrorat config load — the agent won't start. To run against ungated sessions, either omit the line entirely or export an emptySESSIONS_AGENT_TOKEN=(both yield X-Api-Key-only behaviour).The token is read once at startup (like
api_key) and is not hot-reloaded. If the JWT carries anexp, mint it long-lived enough to outlast the agent process and restart the agent to roll a renewed token — an expired bearer makes the gated routes return401/403, and the SSE consumer will reconnect-and-retry with the same stale token until it is restarted.
No SessionsBus, SessionsApiClient, or SessionKeyProvider references in service main.py — AppBuilder.build() wires them automatically.
See the Configuration Reference for all available settings.
The asbs CLI scaffolds projects and components:
asbs setup <project-name> # Create a new agent, in the current directory
asbs setup --group # Create an image's files (agent map, group Dockerfile)
asbs create handler <name> [--event-type <type>] # Add an event handler
asbs create service <name> # Add a business service
asbs create api <name> # Add a REST API
asbs create agent <name> # Add an AI agent
asbs create scheduler <name> [--cron <expr>] # Add a scheduler
asbs validate [--group] # Validate one agent, or a whole image
asbs dev [--name <agent>] [--port <port>] # Run development server
asbs docs [<topic>] [--cat] # Locate the bundled documentation
asbs claude # Install the Claude Code files into a projectSee the full CLI Reference.
Blueprint Agents services deploy as standard Python containers:
- Docker -- Multi-stage Dockerfile included with scaffolded projects
- Kubernetes -- Helm charts with Dapr sidecar injection, health probes, and ConfigMap/Secret management
- CI/CD -- GitHub Actions workflows for linting, testing, and publishing
See the Deployment Guide for detailed instructions.
The documentation ships with the package. Every page below is installed alongside the code, so a developer -- or an AI assistant working in your project -- can read it without network access:
asbs docs # list every page
asbs docs guides/multi-agent-setup # print the path to one page
asbs docs reference/configuration-keys --cat # print its contentsasbs claude additionally installs Claude Code skills that route to the right page for the task at
hand. The links below are the same pages on GitHub, for browsing.
- Getting Started Guide -- Installation, first project, and walkthrough
- Multi-Agent Setup -- Running several agents in one process
- Multi-Agent Migration -- Moving an existing agent into a group
- Architecture -- Component model, registry, and lifecycle
- Event Processing -- CloudEvents, handler chain, Dapr/NATS
- Configuration -- Settings, secrets, and environment variables
- Caching -- Disk and Redis caches, TTL, namespaces, and per-agent isolation
- Observability -- OpenTelemetry tracing, metrics, and logging
- Python 3.13+
- Docker (optional, for containerized deployment)
- Dapr CLI (optional, for Dapr pub/sub event processing)
- NATS Server (optional, for NATS event processing)
- API keys for AI providers (optional, only for LLM agent features)
We welcome contributions! See CONTRIBUTE.md for guidelines on:
- Setting up the development environment
- Running tests and linting
- Submitting pull requests
This project is licensed under the MIT License. See LICENSE for details.