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.

Observability

Every durable step and stream operation opens a span on the global OTel tracer:

SpanAttributes
pyrula.steppyrula.step.id, pyrula.step.kind
pyrula.stream.batchpyrula.stream.source, pyrula.stream.partition, pyrula.stream.offset
pyrula.stream.commitpyrula.stream.source, pyrula.stream.partition, pyrula.stream.offset

No opentelemetry package, no spans. The engine doesn’t own the provider: register one with the OTel SDK, or install "pyrula[agents,otel]" and call configure() once at startup (reads OTEL_EXPORTER_OTLP_ENDPOINT / OTEL_EXPORTER_OTLP_HEADERS). Agents observability has the full setup and trace-context propagation across Kafka.

The Kafka runner merges its counters with the Kafka client’s metrics(). Pull-based, separate from OTel:

stats = runner.stats()
# {
# "processed": 12043, "dlq": 2, "errors": 0, "in_flight": 8, "held": 0,
# "last_latency_ms": 4.1,
# "latency_p50_ms": 3.7, "latency_p95_ms": 9.2, "latency_p99_ms": 14.0,
# "consumer": {...}, "producer": {...}, # raw pyrula-kafka client metrics
# }

WorkflowWorker emits OTel instruments for claims, run lifecycle, latency and queue state. Install the [otel] extra and register a MeterProvider before the worker starts, else they’re no-ops.

pip install "pyrula[workflows,otel]"

All instruments carry pyrula.workflow.name. outcome is success or error.

InstrumentTypeAttributesNotes
pyrula.workflows.runs.claimedcounterworkfloweach claim
pyrula.workflows.runs.activeupdowncounterworkflowin-flight runs
pyrula.workflows.runs.completedcounterworkflow, outcometerminal completions
pyrula.workflows.run.attempt.durationhistogram (s)workflow, outcomeclaim to terminal event
pyrula.workflows.claim.latencyhistogram (s)workflowtime to acquire the claim lock
pyrula.workflows.run.schedule_to_start.durationhistogram (s)workflowsubmit to first claim (queue wait)
pyrula.workflows.queue.depthgaugeworkflowpending run count
pyrula.workflows.orphans.recoveredcounterworkflowruns reclaimed after a worker crash
pyrula.workflows.runs.strandedcounterworkflowruns with no compatible worker version
pyrula.workflows.runs.requeuedcounterworkflow, reasonruns the store put back on the queue
pyrula.workflows.schedules.skippedcounterworkflow, reasonschedule occurrences not submitted
pyrula.workflows.loop.restartscounterpyrula.worker.loop, workflow on claim loopsbackground-loop restarts after a store or network error; the loop kind is claim, timer, orphan, lease, sweep, schedule, heartbeat and so on

schedule_to_start.duration and queue.depth suit queue-pressure autoscaling (KEDA etc.). They need a store that records submit time and counts pending runs (MemoryStore, ValkeyStore, or an ArchivingStore over either); otherwise omitted. queue.depth is polled each collection cycle and store errors are swallowed.

pyrula dev serves a read-only run browser at /ui. It reads the event log and sends no commands.

Terminal window
pyrula dev --attach --store valkey://localhost:6379 # attach read-only to a shared store
pyrula dev path/to/module.py:my_agent # run a worker in-process, plus the UI

--attach mounts only the UI. A workflow target runs inline with the same read-only UI. An agent target also mounts the full agent HTTP API, so keep it on your machine. Both bind 127.0.0.1 (--host + --insecure to override); the port is --port or PYRULA_DEV_PORT.

The UI shows every event except heartbeats, including step:* and run:init with the run’s params, more than the SSE wire exposes. The index filters by status and run id over the loaded runs (showing 3 of 50 loaded); “Load more” raises the window to 500.

View this page as Markdown