Skip to content
Pyrula

CLI

Installing pyrula-agents puts a pyrula command on your path. It covers the operational side: running agents locally, running the worker tier, and managing the store and in-flight turns.

Terminal window
pyrula --help # dev | worker | db | turns | schedules | reproject

pyrula dev accepts file targets such as module.py:agent_name. Worker commands use Python import refs such as your_module:agent_name; configured LLM or deps objects use the same import-ref shape, for example your_module:llm.

pyrula dev is the local cockpit. One command, two modes — the input picks which:

Terminal window
pyrula dev # auto-discover app.py/agents.py here, run it, open the UI
pyrula dev app.py # find the @agent in the module and run it
pyrula dev app.py:support # explicit target
pyrula dev --store valkey://… # attach to a running system, browse every run
ModeWhat it doesNeeds
Embedded (a target, or auto-discovered)runs the agent in-process on an in-memory store so its runs show up in the UInothing — zero infra
Attach (--store …)browses every agent and workflow run in a shared storea shared store (Valkey) your workers already use

The UI is read-only either way: it shows your agents and workflows and their runs. You start runs the way you always do (your worker, your code, the API); pyrula dev is the window onto them.

With no --valkey-url, embedded mode uses an in-memory store, so there’s nothing to set up. If you give a bare module (or run pyrula dev with no arguments), it discovers the @agent targets for you; with several, it lists them so you can pick one.

Terminal window
pyrula dev app.py:support --llm-ref app:llm --port 8000

Pick the tier you need — each is one command, and your code never changes between them:

  1. Zero infra (instant): pyrula dev app.py:agent --open. In-memory store, embedded worker, live UI. Nothing to install or run.
  2. Full multi-process visibility (one container): run Valkey (docker run -p 6379:6379 valkey/valkey), point your workers and pyrula dev --store valkey://localhost:6379 at it. Now the cockpit sees every run, no matter which process produced it.
  3. Your real deployment: pyrula dev --store <your valkey> — read-only attach to a staging-like store during local work.

pyrula dev --store valkey://localhost:6379 opens the UI with no agent loaded and no worker. It reads from the store your system already writes to and lists every name — agents and plain @workflows alike — with their runs; open any run for its live timeline. Because the event renderers are pure functions on the event payload, agent transcripts (LLM/tool/block) render without importing your agent code. It requires a shared store — in-memory is per-process and can’t see another process’s runs.

pyrula dev mounts a small web UI at /ui (on by default). Open http://127.0.0.1:8000/ui and you see your agents and workflows, each with its runs. Click a run for a live timeline that streams its events as they happen — tool calls, LLM steps, and errors each render as a row, with the payload inline.

Terminal window
pyrula dev app.py:support --llm-ref app:llm --open # open the browser on startup
pyrula dev app.py:support --llm-ref app:llm --no-ui # API only, no UI

The UI is local-only and unauthenticated by design: it binds to the dev server, holds no state of its own, and is meant for the inner-loop of building an agent. It is not a hosted console.

pyrula dev is a convenience wrapper. If you already run your own FastAPI app — or a separate-worker fleet — you can mount the same UI yourself. It attaches to any FastAPI app alongside your agent routes:

from pyrula.agents.web.router import AgentRouter
from pyrula.agents.web.ui_ext import register_agent_renderers
from pyrula.workflows.web.ui import mount_run_ui
router = AgentRouter(worker=worker) # InlineWorker, or a WorkerProxy for separate workers
router.attach(app, prefix="/agents") # your existing FastAPI app
register_agent_renderers() # llm/tool/block transcript renderers
mount_run_ui(app, prefix="/ui", control=router)

The UI reads runs from the shared store, so with a separate-worker deployment it needs a real store (Valkey/Postgres), not the in-memory one. The same no-auth caveat applies — keep it behind localhost or a private network; it is not a drop-in production console (that is Pyrula Cloud).

Run the worker tier for separate-mode deployments. Point it at the same store the API uses.

Terminal window
pyrula worker \
--agent app:support \
--valkey-url redis://localhost:6379 \
--llm-ref app:llm \
--deps-ref app:services \
--max-concurrent-turns 50 \
--poison-max-retries 3

--deps-ref injects a shared dependencies object. The poison, heartbeat, and orphan-scan flags map to the same WorkerOptions knobs covered in Workers.

Valkey self-bootstraps, so it needs no setup. Postgres archival needs its schema applied once. Use check to validate connectivity before starting workers.

Terminal window
pyrula db check --valkey-url redis://localhost:6379
pyrula db migrate --postgres-dsn "$PYRULA_POSTGRES_DSN"

Inspect and recover individual turns.

Terminal window
pyrula turns list-quarantined --agent support --limit 50
pyrula turns inspect --turn-id <id> --agent support
pyrula turns requeue --turn-id <id> --agent support
pyrula turns gc --older-than 30d --postgres-dsn "$PYRULA_POSTGRES_DSN" --dry-run

requeue takes a turn the worker quarantined as poison and puts it back in the queue, once you’ve fixed whatever broke it. gc deletes archived turns past a cutoff; --dry-run counts them first.

Scheduled runs have their own subcommand, covered in Schedules.

Rebuild the conversation, usage, and cost read tables from the archived event log. Use it to backfill runs archived before you enabled projection, or after editing the price table. Needs Postgres.

Terminal window
pyrula reproject --agent support # one agent
pyrula reproject --agent support --run-id <id> # one run (--run-id requires --agent)
pyrula reproject # every archived run, every agent

Projection itself is opt-in on the store (PostgresStore(..., project=True)) and is covered in Observability.