#[workflow]Expand description
Marks a function as a durable workflow.
Workflows are multi-step processes that survive restarts and handle failures with compensation. Each workflow has a stable logical name, an explicit user-facing version, and a derived signature that acts as the hard runtime safety gate for resumption.
§Versioning and step-name stability
Step names (the string literals passed to ctx.step()) and wait keys (the string
literals passed to ctx.wait_for_event()) are part of the workflow’s persisted
contract. The macro hashes them together with the version string, timeout, and
input/output type names into a signature that is stored with every new run.
Renaming a step or wait key under the same version is a breaking change. Any
in-flight run that tries to resume after such a rename will be blocked with
WorkflowStatus::BlockedSignatureMismatch because the stored signature no longer
matches the binary’s signature. Use cargo expand to inspect the forge:contract
doc comment on the generated struct — it lists every key contributing to the
signature.
When you need to rename a step (or add/remove steps, change event contracts, or alter the timeout), create a new version instead:
- Annotate the old function with
deprecated— the runtime keeps it alive for draining. - Write a new function with a new
versionstring containing your changes. - Remove the old function once all its in-flight runs have completed.
The runtime derives a signature from step keys, wait keys, timeout, and type shapes. If you change the persisted contract under the same version, registration will fail.
§Authentication
By default, workflows require an authenticated user to start. Override with:
public- Can be started without authenticationrequire_role("admin")- Requires specific role to start
§Attributes
name = "logical_name"- Stable workflow name (defaults to function name)version = "2026-05"- User-facing version id (dates, semver, or labels)active- This is the active version; new runs start here (default if neither set)deprecated- Kept for draining old runs; no new runs will start on this versiontimeout = "24h"- Maximum execution time. Also becomes the default outbound HTTP timeout forctx.http()when explicitly set
§Example
// Old version kept alive for draining incomplete runs
#[forge::workflow(name = "user_onboarding", version = "2026-03", deprecated)]
pub async fn user_onboarding_v1(ctx: &WorkflowContext, input: Input) -> Result<Output> {
let user = ctx.step("create_user", || async { /* ... */ }).await?;
ctx.step("send_welcome", || async { /* ... */ }).await;
Ok(Output { user })
}
// New active version with an additional step
#[forge::workflow(name = "user_onboarding", version = "2026-05", active)]
pub async fn user_onboarding_v2(ctx: &WorkflowContext, input: Input) -> Result<Output> {
let user = ctx.step("create_user", || async { /* ... */ }).await?;
ctx.step("send_welcome", || async { /* ... */ }).await;
ctx.step("sync_crm", || async { /* ... */ }).await;
Ok(Output { user })
}