Skip to content
Pyrula
Pre-release: the packages are not on PyPI yet, so pip install does not resolve. Build from source to try it today.

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_app
from 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:

Terminal window
uvicorn myapp:app

Each agent gets the same set of routes under the prefix (default /agents):

MethodPathPurpose
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}/eventsStored events, paged.
GET/agents/{agent}/turns/{turn_id}/replayReplay the turn’s events from the start.
POST/agents/{agent}/turns/{turn_id}/resumeAnswer a ctx.interrupt wait.
POST/agents/{agent}/turns/{turn_id}/signalDeliver a signal to ctx.wait_for_signal.
DELETE/agents/{agent}/stream/{turn_id}Cancel the turn.
GET/agents/agentsList the agents this router serves.
GET/agents/turnsList turns, paged.
POST/agents/threadsCreate a thread ({"agent_name": ...}).
GET / DELETE/agents/threads/{thread_id}Read or delete a thread.
GET/agents/threads/{thread_id}/turnsA 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:

Terminal window
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.

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 as ctx.deps (a DB pool, an HTTP client). Declare its type with @agent(deps_type=...).
  • max_queue_depth: a submit gets 429 (with Retry-After) while that agent has that many turns pending.

deployment_mode="separate" makes the app submit and stream only, leaving execution to worker processes.

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")