HTTP
create_app returns a FastAPI app that exposes your agents. Pass agents and a store,
or let it pick up every @agent imported in the process.
from pyrula.agents import create_appfrom pyrula.agents.llm import OpenAILLM
app = create_app( agents=[support], # or omit to use the registry store=store, llm=OpenAILLM(model="gpt-4o-mini"),)Run it with any ASGI server:
uvicorn myapp:appRoutes
Section titled “Routes”Each agent gets the same set of routes under the prefix (default /agents):
| Method | Path | Purpose |
|---|---|---|
POST | /agents/{agent} | Submit a turn. Returns a turn id and stream path. |
GET | /agents/{agent}/stream/{turn_id} | Follow the turn live over SSE. |
GET | /agents/{agent}/turns/{turn_id} | Current status. |
GET | /agents/{agent}/turns/{turn_id}/events | Stored events, paged. |
GET | /agents/{agent}/turns/{turn_id}/replay | Replay the turn’s events from the start. |
POST | /agents/{agent}/turns/{turn_id}/resume | Answer a ctx.interrupt wait. |
POST | /agents/{agent}/turns/{turn_id}/signal | Deliver a signal to ctx.wait_for_signal. |
DELETE | /agents/{agent}/stream/{turn_id} | Cancel the turn. |
GET | /agents/agents | List the agents this router serves. |
GET | /agents/turns | List turns, paged. |
POST | /agents/threads | Create a thread ({"agent_name": ...}). |
GET / DELETE | /agents/threads/{thread_id} | Read or delete a thread. |
GET | /agents/threads/{thread_id}/turns | A thread’s turns. |
POST | /agents/{agent}/threads/{thread_id} | Submit a turn on a thread. |
A thread groups turns into one conversation. A turn submitted on a thread can read
earlier turns with ctx.thread.last_result() and ctx.thread.history(limit=...).
The live stream and the replay endpoint read the same event stream, so a client that drops can reconnect and catch up from its last event id without losing anything.
/resume requires the wait_id of the run:interrupted event it answers, read off
that event, alongside the action:
curl -X POST localhost:8000/agents/support/turns/$TURN_ID/resume \ -H 'content-type: application/json' \ -d '{"action": "approve", "wait_id": "b6f2...b0"}'A wait_id that no longer names the turn’s open interrupt - already answered, or a
newer one since opened - gets a 409, not a silent no-op.
Options
Section titled “Options”Pass an AppOptions to tune the app:
from pyrula.agents import create_app, AppOptions, APIKeyAuth
app = create_app( store=store, llm=llm, options=AppOptions( prefix="/agents", auth=APIKeyAuth(...), cors_origins=["https://app.example.com"], deployment_mode="separate", ),)Useful fields: prefix, auth, deployment_mode, cors_origins, idempotency_ttl_s,
sse_keepalive_interval, cancel_on_reader_disconnect, and the health/ready probe
paths (/healthz, /readyz by default). Also:
deps: an object handed to every turn asctx.deps(a DB pool, an HTTP client). Declare its type with@agent(deps_type=...).max_queue_depth: a submit gets429(withRetry-After) while that agent has that many turns pending.
deployment_mode="separate" makes the app submit and stream only, leaving execution to
worker processes.
Mounting into an existing app
Section titled “Mounting into an existing app”create_app builds a fresh FastAPI app. To add the agent routes to an app you already
have, use AgentRouter directly, which is the APIRouter create_app mounts under the
hood:
from pyrula.agents import AgentRouter, AgentRouterConfig
router = AgentRouter(worker=worker, config=AgentRouterConfig(...))existing_app.include_router(router, prefix="/agents")