Versioning & stability
Semantic Versioning. The packages (pyrula, pyrula-workflows,
pyrula-agents, pyrula-kafka, pyrula-cloud-client) release together; install matching
minors. Current gaps: Known limitations.
Releases before 1.0
Section titled “Releases before 1.0”- A minor release (
0.4to0.5) may change or remove a public name or a stored format. Changes are in the release notes, and a removed name is deprecated first. - A patch release (
0.4.1to0.4.2) holds fixes and additions only: no removed or renamed public names, changed return shapes, or changed stored or wire formats.
Pin a minor (pyrula~=0.4.0) for a stable target.
Supported surface
Section titled “Supported surface”Each runtime package, and pyrula.workflows.testing, classifies its exports in a STABILITY
map:
from pyrula.workflows import STABILITY
STABILITY["RunEvent"] # "stable"STABILITY["InlineWorkflowRunner"] # "stable""stable": what you write against: decorators, contexts, clients, exceptions, the concrete stores."extension": what you implement: the store ABCs,LLMClient,MemoryBackend,ScheduleBackend, the contract kits. Signature changes go through deprecation. See Extension points."deprecated": still works, being removed.
A name not in __all__, or with a leading underscore, is internal. There is no beta tier.
pyrula.parallel (par_map and its errors) is supported too.
Guarantees per surface: adapters, Kafka client, Kafka runtime.
Deprecations
Section titled “Deprecations”A stable or extension name is removed in two steps: one minor keeps it working and raises
DeprecationWarning naming the removing release, and the next removes it. Run tests with
-W error::DeprecationWarning to catch these early.
Stored and wire formats
Section titled “Stored and wire formats”What the current release does when it meets data in another version of a format:
| Format | Version marker | Older data | Newer data |
|---|---|---|---|
| Run journal (the event log in every store) | EVENT_SCHEMA_VERSION, currently "1", stamped on each event, plus the requires and encoding event headers | Read as-is; "1" is the only version so far | A worker hands the run back, unrun and uncounted, when a journal holds anything it cannot read (see below) |
| Postgres archive, agent projection and agent memory | A one-row *_schema_compat table per schema (generation, oldest reader generation, baseline revision), written by its migrations | Used as-is at or above the oldest generation this code supports; a lower one, or a different baseline, fails with ArchiveSchemaMismatch (recreate or migrate) | Used as-is while its oldest reader generation is at or below the code’s; otherwise ArchiveSchemaMismatch says to upgrade pyrula |
| Case bundle | format in the manifest, currently 1 | Read up to the reader’s own format | format_unsupported only when the format number is newer; unknown keys are kept and an unknown call kind is skipped and reported |
| Cloud outbox (the connector’s local SQLite spool) | OUTBOX_FORMAT_VERSION = 4, in SQLite’s user_version | Migrated forward in place when the spool opens, keeping its events | The connector refuses to start with OutboxFormatTooNew and leaves the file untouched; upgrade the SDK or move the file aside |
| Valkey key layout | None | Not checked | Not checked |
Cloud wire (/api/v1) | The path prefix and wire_version (currently 1) in the ingest and register bodies | Pyrula Cloud parses every version it still supports | A 422 wire_version_unsupported answer is retried with backoff and the events stay queued |
Within /api/v1 the event vocabulary only grows: event kinds, run statuses and error codes
are not removed or renamed.
The Valkey key layout is not versioned. When the release notes list a layout change, drain
in-flight runs before upgrading or point the new release at a fresh namespace
(PYRULA_VALKEY_NAMESPACE). Two builds can share a server during a rolling deploy.
Journals from a newer release
Section titled “Journals from a newer release”During a rolling deploy an older worker refuses a run whose journal holds:
- an unknown event kind in a framework namespace (
run:,step:,llm:,block:,tool:,agent:,context:,stream:,child:,select:,mcp:,saga:,approval:, or the bareheartbeatandpatch_marker); - an unknown
step_kindonstep:pending,step:doneorstep:error; - an unknown feature in an event’s
requiresheader; - an
encodingheader other thanjson; - a
schema_versionmajor higher than its own.
Event kinds outside those namespaces are yours (from ctx.emit) and are skipped. A writer can
mark a new kind or step_kind ignorable: "1"; features, encodings and schema majors are
never skippable.
The refused run goes back to pending with no event written, no attempt counted and no failure.
The worker logs run_requires_newer_runtime, counts it under needs_newer_runtime, and skips
that run for 60 seconds, and a newer worker claims it. Rolling back leaves such runs queued
until the newer release returns.
Resuming a run after its code changed
Section titled “Resuming a run after its code changed”A run is stamped with its workflow’s version at submit time, which decides what a worker on changed code does (versioning guide).
- No
version=: the version is a hash of the body and the policy isauto_upgrade. The worker replays history against the edited body. A compatible edit finishes the run; an incompatible one (renamed, removed or reordered step, changed inputs) ends it withrun:errorcodeincompatible_upgrade. version=changed: policypinned. Only a worker on that version claims the run. Withstranded_policy=StrandedPolicy.FAILit ends withno_compatible_workerafterstranded_timeout.- Body changed,
version=kept: unsupported. The worker trusts the label. A step with different inputs ends the run withreplay_input_changed; a renamed or removed step is not detected and its recorded result is dropped. Changeversion=whenever an edit touches a step.
@agent takes the same options. A failed resume makes result() raise DurableInvocationError.