Agent Skills: Duroxide Orchestration Versioning (Toygres)

Guidance for safely versioning Duroxide orchestrations — file structure, naming conventions, workflow, and registry registration.

UncategorizedID: affandar/toygres/duroxide-orchestration-versioning

Install this agent skill to your local

pnpm dlx add-skill https://github.com/affandar/toygres/tree/HEAD/.agents/skills/duroxide-orchestration-versioning

Skill Files

Browse the full folder contents for duroxide-orchestration-versioning.

Download Skill

Loading file tree…

.agents/skills/duroxide-orchestration-versioning/SKILL.md

Skill Metadata

Name
duroxide-orchestration-versioning
Description
Guidance for safely versioning Duroxide orchestrations — file structure, naming conventions, workflow, and registry registration.

Duroxide Orchestration Versioning (Toygres)

Non‑Negotiable Rule

Orchestration code is immutable once deployed.

Running instances replay historical events. If you change orchestration logic (or helpers it calls), replay can break in subtle ways.

This applies to:

  • Orchestration functions (anything taking OrchestrationContext)
  • Helper functions called by orchestrations (also orchestration code)
  • Any change in activity scheduling order, parameters, retry policy, timers, branching logic, etc.

You MUST NOT cause ANY side effect on existing frozen orchestrations. This includes changes to shared types (e.g., activity input/output structs) that alter serialization behavior. If you add a field to a shared struct:

  • The serialized JSON must remain byte-for-byte identical for existing call sites
  • Use #[serde(default, skip_serializing_if = "Option::is_none")] for new Option<T> fields so None is omitted (not serialized as null)
  • Verify by checking that serde_json::to_string(&OldInput { field: None }) produces the same JSON as the original struct without the field
  • If you cannot make the change backward-compatible, create a new activity (e.g., test-connection-v2) instead of modifying the shared type

When You MUST Create a New Version

Create a new orchestration version for any logic change, including "small fixes":

  • Bug fixes, error handling tweaks, logging changes that affect control flow
  • Changing activity calls (order/inputs/retry policies)
  • Adding/removing timers
  • Changing helper functions invoked by orchestrations

File & Folder Structure

Each orchestration lives in its own folder under toygres-orchestrations/src/orchestrations/:

instance_actor/
  mod.rs                                    # Wiring only: NAME const, pub mod, pub use
  instance_actor_orchestration.rs           # Latest version code (currently v1.0.2)
  instance_actor_1_0_1_orchestration.rs     # Frozen v1.0.1
  instance_actor_1_0_0_orchestration.rs     # Frozen v1.0.0

Naming Conventions

| Item | Pattern | Example | |---|---|---| | Function name (all versions) | {name}_{version}_orchestration | instance_actor_1_0_2_orchestration | | Latest file | {name}_orchestration.rs | instance_actor_orchestration.rs | | Frozen file | {name}_{version}_orchestration.rs | instance_actor_1_0_1_orchestration.rs |

mod.rs Structure (Wiring Only)

mod.rs contains no orchestration logic — only wiring:

/// Orchestration name for registration and scheduling
pub const NAME: &str = "toygres-orchestrations::orchestration::instance-actor";

pub mod instance_actor_1_0_0_orchestration;
pub mod instance_actor_1_0_1_orchestration;
mod instance_actor_orchestration;

pub use instance_actor_1_0_0_orchestration::instance_actor_1_0_0_orchestration;
pub use instance_actor_1_0_1_orchestration::instance_actor_1_0_1_orchestration;
pub use instance_actor_orchestration::instance_actor_1_0_2_orchestration;

Note: frozen versions are pub mod (needed by registry), latest is mod (re-exported via pub use).

Shared helper functions (e.g., update_cms_state in delete_instance) may live in mod.rs since they are shared utilities, not orchestration logic.

Workflow: Adding a New Version

Assume the current latest is v1.0.1 and you want to add v1.0.2.

Step 1: Freeze the current latest

cp instance_actor_orchestration.rs instance_actor_1_0_1_orchestration.rs

This frozen copy is byte-for-byte identical. Git shows it as all + lines (a new file) — reviewers can ignore it.

Step 2: Modify the latest file in place

In instance_actor_orchestration.rs:

  • Rename the function: instance_actor_1_0_1_orchestrationinstance_actor_1_0_2_orchestration
  • Update version prefixes in log messages: [v1.0.1][v1.0.2]
  • Make your actual code changes

Git diff shows clean -/+ pairs for every real change — exactly what reviewers need.

Step 3: Update mod.rs wiring

Add the new frozen module and update re-exports:

pub mod instance_actor_1_0_0_orchestration;
pub mod instance_actor_1_0_1_orchestration;  // ← new frozen module
mod instance_actor_orchestration;

pub use instance_actor_1_0_0_orchestration::instance_actor_1_0_0_orchestration;
pub use instance_actor_1_0_1_orchestration::instance_actor_1_0_1_orchestration;  // ← new
pub use instance_actor_orchestration::instance_actor_1_0_2_orchestration;        // ← updated

Step 4: Register in registry.rs

.register_versioned_typed(
    instance_actor::NAME,
    "1.0.2",
    instance_actor::instance_actor_1_0_2_orchestration,
)

Step 5: Build and verify

cargo build --workspace

Logging Convention

Prefix all orchestration logs with the version for debugging:

  • ctx.trace_info("[v1.0.2] ...")

Version Selection + Rollout Behavior

  • start_orchestration(...) uses the latest registered version.
  • Existing running instances stay on their current version until they naturally transition (often at continue_as_new boundaries, depending on the workflow design and Duroxide policy).

Safe Refactors

If you want to "refactor" orchestration code:

  • Do it by adding a new version with the refactor.
  • Do not modify earlier versions.

Checklist

Before shipping:

  • [ ] Copied latest to frozen file ({name}_{old_version}_orchestration.rs)
  • [ ] Renamed function in latest file to new version
  • [ ] Updated [vX.Y.Z] log prefixes
  • [ ] Updated mod.rs (added frozen module, updated re-export)
  • [ ] Registered via .register_versioned_typed(NAME, "X.Y.Z", ...)
  • [ ] No changes to any frozen orchestration files
  • [ ] Shared struct changes use skip_serializing_if to preserve JSON compatibility
  • [ ] Verified serialization: None/default values produce identical JSON to the old struct
  • [ ] cargo build --workspace passes