Skip to content
Pyrula

Workers

In separate mode the API submits and streams, and worker processes do the actual execution. PyrulaWorker is that worker tier. It claims turns from the shared store, runs them, and handles timers, signals, orphan recovery, and poison turns.

from pyrula.agents import PyrulaWorker
from pyrula.agents.llm import OpenAILLM
worker = PyrulaWorker(
agents=[support], # or omit for the registry
store="redis://localhost:6379",
llm=OpenAILLM(model="gpt-4o-mini"),
concurrency=50,
)
worker.run()

store takes a Valkey or Redis URL (or a store instance). The worker and the API must point at the same store. run() blocks; run_async() is the awaitable version for an existing event loop.

Pass a WorkerOptions for the runtime knobs:

from pyrula.agents import WorkerOptions, PoisonOptions
WorkerOptions(
max_concurrent_turns=50,
orphan_scan_interval=60.0,
timer_poll_interval_s=5.0,
schedule_poll_interval_s=30.0,
poison=PoisonOptions(max_retries=3, retry_ttl_s=3600.0),
)

A turn that keeps crashing the worker is retried up to PoisonOptions.max_retries. After that it’s quarantined: the worker writes run:error with a poison reason and moves on, so one bad turn can’t wedge the tier. Quarantined turns show up as RunStatus.quarantined.

If a worker dies mid-turn, another worker claims the turn during its orphan scan and resumes it from the first unfinished step. Sleeping and signal-waiting turns aren’t treated as orphans; they wake on their own schedule. This is the engine’s replay and recovery in production.