agentwright: typed, composable primitives for building, validating, and running AI agents.
Project description
agentwright
agentwright. A small set of typed, composable primitives for building, validating, and running AI agents — and a zero-daemon runtime that executes them. You describe an agent's intent; the system selects the right primitives, configures them, validates the composition against hard invariants, and gives you a runnable, auditable agent.
No framework lock-in, no training, no always-on services. Pure Python, in-process, runs offline with no API key — real LLM and database backends drop in behind the same interfaces when you want them.
pip install agentwright # or: uv add agentwright
Quickstart — the one path that always works
from agentwright import DomainBrief, compose
from agentwright.runtime import AgentRuntime, ReasoningEngine
import asyncio
# 1. Describe intent (what, not how).
brief = DomainBrief(name="My Agent", goal="Summarize inbound leads daily.",
domain="sales", long_lived=True)
# 2. Compose -> a validated AgentDefinition (the OS picks + configures primitives).
definition = compose(brief, owner_id="usr_1", created_by="usr_1")
assert not definition.validation_errors # non-empty => you described something illegal
# 3. Boot the runtime and provision exactly this agent's stores.
rt = AgentRuntime() # zero-daemon, all in-process, no API key
rt.instantiate(definition)
# 4. Run it.
engine = ReasoningEngine(rt)
result = asyncio.run(engine.run(definition, task="Summarize today's leads."))
print(result.output, result.stop_reason, result.cost_usd)
rt.close()
If you do nothing else, do that. Everything below is detail and variation. The
full agent-facing contract is AGENTS.md.
Mental model: three layers, one direction
YOU (an app / agent / agent system)
│ express intent as a DomainBrief
▼
Layer 2 — Domain Orchestration
DomainBrief ─▶ select ─▶ configure ─▶ validate ─▶ register ─▶ AgentDefinition
│
│ an AgentDefinition references…
▼
Layer 1 — Primitives (the vocabulary)
9 typed building blocks + a discovery/registry API
│
│ …which the runtime provisions + executes
▼
Layer 0 — Runtime Infrastructure
AgentRuntime (stores, vault, bus, scheduler) + ReasoningEngine (the agentic loop)
Direction of control: the AgentDefinition drives the infrastructure.
Layer 0 provisions only the stores a definition's primitives declare — it
never assumes. You describe needs in the brief; the layers resolve them.
The boundary object is AgentDefinition. It round-trips losslessly, so you
can compose in one place and run in another:
blob = definition.model_dump_json() # persist / send anywhere
from agentwright import AgentDefinition
definition = AgentDefinition.model_validate_json(blob) # load and run elsewhere
Two kinds of material: execution code vs. agent guidance
Everything above — the three layers, the primitives, the runtime — is execution code: typed, deterministic machinery that runs an agent. It is the how.
Alongside it, guide/ holds agent guidance: prose an agent reads to
design itself before it builds. It is the judgment — open-ended best practice,
not code:
| Guide | The agent uses it to decide… |
|---|---|
agent_design_patterns.md |
which design pattern to run (planner-orchestrator, sequential pipeline, supervisor-worker, blackboard, critique-revision) |
agent_task_decomposition_guidelines.md |
how to break a goal into validated, right-sized tasks |
context_engineering_strategies.md |
what to keep in context and how to manage it over a run's lifetime |
The split: agentwright/ is the vocabulary an agent executes through; guide/
is how it decides what to build. The primitives stay fixed and typed; the guidance
lets the agent self-organize within them. A consuming agent is expected to pick this
guidance up automatically — AGENTS.md makes it part of the contract.
The 9 primitives (Layer 1 vocabulary)
Four are always present; the rest are selected only when the brief implies them.
| Primitive | Always? | Provides | Depends on |
|---|---|---|---|
identity |
✅ | Name, instructions/persona, model defaults | — |
reasoning_loop |
✅ | The agentic loop: model, iteration & cost controls | identity |
permission |
✅ | Action policy (allow / deny / require_approval) | — |
observability |
✅ | Logs / metrics / traces / export (OpenTelemetry) | — |
memory |
⬜ | document + vector + graph + universal + KV state | identity |
tool_connection |
⬜ | External tools/APIs the agent may call | permission |
compute |
⬜ | Shell / browser execution | permission |
trigger |
⬜ | schedule / webhook / email / slack / event / manual | identity |
generated_ui |
⬜ | Agent-produced UI surfaces | identity |
Discover them at runtime — every primitive exposes its JSON config schema:
from agentwright import catalogue, describe_primitive
catalogue() # [{name, version, dependencies, config_schema}, …]
describe_primitive("permission") # one primitive, full schema
Custom primitives are first-class
The OS is extensible by design: register your own primitive and it flows through the same pipeline as the built-ins — selected, configured from its own schema, version-pinned, dependency-checked, and persisted. No fork, no per-type glue.
from agentwright import PrimitiveTemplate, register_primitive, DomainBrief, compose
from pydantic import BaseModel
class SentimentConfig(BaseModel):
threshold: float = 0.5
register_primitive(PrimitiveTemplate(
name="sentiment", version="1.0.0", config_model=SentimentConfig,
dependencies=("identity",), runtime_contract=("scores text sentiment",),
))
brief = DomainBrief(name="reviews", goal="triage reviews", domain="support",
extra_primitives=["sentiment"],
custom_config={"sentiment": {"threshold": 0.8}})
defn = compose(brief, owner_id="u", created_by="u")
defn.primitives.custom["sentiment"] # SentimentConfig(threshold=0.8)
An unsatisfied dependency is a normal validation error; an unregistered name
raises. See orchestration/README.md.
Layer 0 — what's real vs. interfaced
Layer 0 is zero-daemon: everything runs in your process.
-
Real, fully tested offline: SQLite system DB, File Store, Fernet vault, Event Bus, Scheduler (cron), TinyDB document store, LMDB KV, Shell Runner, the
ReasoningEngine, and in-memory implementations of every heavy store. -
Real backends behind protocols (
runtime/interfaces.py↔runtime/real_backends.py): each heavy service has a real adapter implementing the same protocol as its in-memory default, swappable viaAgentRuntime(...). SurrealDB (universal) and Chroma (vector) test for real with no API key. Three LLM gateways — Anthropic, OpenAI, Google Gemini — plus amake_gateway(provider)router. Install only what you need:pip install "agentwright[anthropic,chroma,surreal]" # pick extras pip install "agentwright[all]" # every backend
Bring your own backend by implementing a protocol from runtime/interfaces.py
(LLMGateway, VectorStore, GraphStore, Memory, …) and passing it to
AgentRuntime. That protocol set is the OS's plugin ABI.
Install (development)
Uses uv:
uv sync # core, offline
uv sync --extra all # + every real backend
uv run pytest # run the suite
Tests
uv run pytest
164 tests pass offline; 8 live-backend tests skip unless their keys/installs are present. Coverage spans the registry and dependency resolution, the selector matrix, the configurator mapping, custom-primitive composition, every composition invariant and infra gap, the SQLite instance registry, all Layer-0 runtime services, the Layer-2→Layer-1 consumer journey, cross-layer integration, and agent-archetype scenario tests that build realistic agents and run them end to end (L2→L0) — covering all nine primitives and every pattern (each trigger type, each compute backend, generated_ui, sub-agents, the full memory stack, the permission approval flow, multi-tool reasoning, and concurrent agents).
Observability
Orchestration boundaries (orchestrate.select/configure/validate/compose/register)
emit OpenTelemetry spans, no-ops until a tracer provider is installed. Call
agentwright.telemetry.enable_console_tracing() to print spans while
debugging. Layer 0 boundaries (run start/stop, tool calls, cost) are recorded in
the system DB and audit ledger.
Docs
AGENTS.md— the full integration contract (audience: agents).guide/— design patterns, task decomposition, and context-engineering guidance an agent reads to design itself (audience: agents).agentwright/primitives/README.md— Layer 1.agentwright/orchestration/README.md— Layer 2.agentwright/runtime/README.md— Layer 0.design/— the layer designs this implements.
Acknowledgments
This project stands on the teaching and generosity of others:
- Andrew Ng, whose Agentic AI course shaped much of the thinking behind these primitives and the compose-validate-run model.
- Andrew Lee and the team at Tasklet, for generously sharing the practices behind building an agent OS — a real source of both inspiration and concrete knowledge for this work.
- Matt Dzaman (Stellic), whose Configurable Workflow Platform project idea helped inspire the configuration-driven, composable approach taken here.
Any mistakes or rough edges here are my own.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file agentwright-0.1.0.tar.gz.
File metadata
- Download URL: agentwright-0.1.0.tar.gz
- Upload date:
- Size: 353.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
44076616eca27484a8d73e8a451ebd94b21a37e05dd3a759f576ae3e904a7328
|
|
| MD5 |
cfcc3a4f79fa1f92b1fa25c823e89fb3
|
|
| BLAKE2b-256 |
e53509c507a4593fe8cc67726fe36e22bce385d6fe5280c2a4b929b73a3b4a20
|
File details
Details for the file agentwright-0.1.0-py3-none-any.whl.
File metadata
- Download URL: agentwright-0.1.0-py3-none-any.whl
- Upload date:
- Size: 81.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c2f59591820cad5604cbf02266d622fddcbe160d67031019a19155d868d070aa
|
|
| MD5 |
6b5e5cf2f2079f058b3557c76fddd974
|
|
| BLAKE2b-256 |
115ac8ecf49dbc09c5728dfe80033cddf9e535b6a008fa02ab57547ea005f39a
|