For AI agents: the complete documentation index is available at https://a3s-lab.github.io/Flow/v0.12.0/en/llms.txt, the full documentation bundle is available at https://a3s-lab.github.io/Flow/v0.12.0/en/llms-full.txt, and this page is available as Markdown at https://a3s-lab.github.io/Flow/v0.12.0/en/guide/index.md.

Run a first workflow on 0.12.0

Cargo.toml
[dependencies]
a3s-flow = "=0.12.0"
async-trait = "0.1"
serde_json = "1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

Complete example

src/main.rs
use a3s_flow::{
    FlowEngine, FlowError, FlowRuntime, RuntimeCommand,
    StepInvocation, WorkflowInvocation, WorkflowSpec,
};
use async_trait::async_trait;
use serde_json::json;
use std::sync::Arc;

struct GreetingRuntime;

#[async_trait]
impl FlowRuntime for GreetingRuntime {
    async fn run_workflow(
        &self,
        invocation: WorkflowInvocation,
    ) -> a3s_flow::Result<RuntimeCommand> {
        let ctx = invocation.context();
        if let Some(output) = ctx.step_output("greet") {
            return Ok(ctx.complete(output.clone()));
        }

        Ok(ctx.schedule_step(
            "greet",
            "greet_user",
            json!({ "name": ctx.input()["name"] }),
        ))
    }

    async fn run_step(
        &self,
        invocation: StepInvocation,
    ) -> a3s_flow::Result<serde_json::Value> {
        match invocation.step_name.as_str() {
            "greet_user" => Ok(json!({
                "message": format!(
                    "hello {}",
                    invocation.input["name"]
                        .as_str()
                        .unwrap_or("unknown"),
                ),
            })),
            name => Err(FlowError::Runtime(
                format!("unknown step: {name}"),
            )),
        }
    }
}

#[tokio::main]
async fn main() -> a3s_flow::Result<()> {
    let engine = FlowEngine::in_memory(Arc::new(GreetingRuntime));
    let spec = WorkflowSpec::rust_embedded(
        "demo.greeting",
        "0.1.0",
        "demo",
        "main",
    );

    let run_id = engine
        .start_with_id(
            "greeting-ada",
            spec,
            json!({ "name": "Ada" }),
        )
        .await?;
    let snapshot = engine.snapshot(&run_id).await?;

    println!("status={:?}", snapshot.status);
    println!("output={:?}", snapshot.output);
    Ok(())
}
cargo run

The first replay schedules greet. After the completion event commits, the second replay reads output and completes. The workflow function performs no network or file access.

Idempotent start

start_with_id() lets a caller retry run creation. An identical run ID, WorkflowSpec, and initial input returns the original run. Any changed value returns RunConflict.

A business service may derive run ID from an order or job identity, but one ID must retain one durable meaning.

Inspect raw history

let history = engine.history(&run_id).await?;
for event in history {
    println!("{} {}", event.sequence, event.event.event_key());
}

Step output is visible only after it enters history. If a process exits after the external action but before event commit, the step runs again. Include a stable idempotency key such as greeting-ada:greet in step input.

Optional SQL features

0.12.0 uses ORM 0.3.

a3s-flow = { version = "=0.12.0", features = ["sqlite"] }

When the application uses ORM or Boot directly, align those dependencies with 0.3.0 and 0.2.0 so two incompatible public type families are not resolved together.

Optional TypeScript adapter

NativeTsRuntime requires a host-supplied compiler path.

let runtime = NativeTsRuntime::new(NativeTsRuntimeConfig::new(
    "/opt/a3s/bin/a3s-flow-native-compiler",
    ".a3s/flow/native-ts",
    ".",
));

The compiler implements compile <entrypoint.ts> -o <artifact>. 0.12.0 has no dependency-manifest scan, so bump WorkflowSpec.version when imports or configuration change.

Next steps