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.

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.

  • A minor release (0.4 to 0.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.1 to 0.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.

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.

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.

What the current release does when it meets data in another version of a format:

FormatVersion markerOlder dataNewer data
Run journal (the event log in every store)EVENT_SCHEMA_VERSION, currently "1", stamped on each event, plus the requires and encoding event headersRead as-is; "1" is the only version so farA worker hands the run back, unrun and uncounted, when a journal holds anything it cannot read (see below)
Postgres archive, agent projection and agent memoryA one-row *_schema_compat table per schema (generation, oldest reader generation, baseline revision), written by its migrationsUsed 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 bundleformat in the manifest, currently 1Read up to the reader’s own formatformat_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_versionMigrated forward in place when the spool opens, keeping its eventsThe connector refuses to start with OutboxFormatTooNew and leaves the file untouched; upgrade the SDK or move the file aside
Valkey key layoutNoneNot checkedNot checked
Cloud wire (/api/v1)The path prefix and wire_version (currently 1) in the ingest and register bodiesPyrula Cloud parses every version it still supportsA 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.

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 bare heartbeat and patch_marker);
  • an unknown step_kind on step:pending, step:done or step:error;
  • an unknown feature in an event’s requires header;
  • an encoding header other than json;
  • a schema_version major 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.

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 is auto_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 with run:error code incompatible_upgrade.
  • version= changed: policy pinned. Only a worker on that version claims the run. With stranded_policy=StrandedPolicy.FAIL it ends with no_compatible_worker after stranded_timeout.
  • Body changed, version= kept: unsupported. The worker trusts the label. A step with different inputs ends the run with replay_input_changed; a renamed or removed step is not detected and its recorded result is dropped. Change version= whenever an edit touches a step.

@agent takes the same options. A failed resume makes result() raise DurableInvocationError.

View this page as Markdown