01 — Your First Workflow
Build and run a single-task workflow embedded in your own process. The engine is identical in both languages; pick your tab.
What you’ll build
Section titled “What you’ll build”A workflow named greeting with one task that writes a message into the
Context, executed by a
Runner against SQLite.
Step 1 — Add Cloacina
Section titled “Step 1 — Add Cloacina”[dependencies]cloacina = "0.12"tokio = { version = "1", features = ["full"] }serde_json = "1"The backend is chosen at runtime from the database URL — no feature flags.
pip install cloaca # SQLite + PostgreSQLPre-built wheels for Linux and macOS on Python 3.9–3.12.
Step 2 — Define a task and a workflow
Section titled “Step 2 — Define a task and a workflow”A Task is an async function with an id;
a Workflow names a set of tasks.
The #[workflow] module attribute names the workflow; #[task] functions inside
it are its tasks:
use cloacina::{task, workflow, Context, TaskError};
#[workflow(name = "greeting", description = "Say hello")]pub mod greeting { use super::*;
#[task] pub async fn hello(ctx: &mut Context<serde_json::Value>) -> Result<(), TaskError> { ctx.insert("message", serde_json::json!("Hello World!"))?; Ok(()) }}The WorkflowBuilder context manager assembles @cloaca.task functions declared
inside it (registered automatically on exit):
import cloaca
with cloaca.WorkflowBuilder("greeting") as builder: builder.description("Say hello")
@cloaca.task() def hello(context): context.set("message", "Hello World!") return contextStep 3 — Run it
Section titled “Step 3 — Run it”Create a Runner against SQLite and execute the workflow by name.
use cloacina::executor::WorkflowExecutor; // brings `execute` into scopeuse cloacina::runner::{DefaultRunner, DefaultRunnerConfig};
#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let runner = DefaultRunner::with_config( "sqlite://app.db", DefaultRunnerConfig::default(), ).await?;
let result = runner.execute("greeting", Context::new()).await?; println!("status: {:?}", result.status); println!("context: {:?}", result.final_context);
runner.shutdown().await?; Ok(())}runner = cloaca.DefaultRunner("sqlite:///app.db")result = runner.execute("greeting", cloaca.Context())print("status:", result.status)print("context:", result.final_context)runner.shutdown()You’ll see a completed status and the message value in the final context.
What you learned
Section titled “What you learned”- A task is an async function with an
id; a workflow names a set of tasks. - A runner executes a workflow against a database (SQLite here) and persists state — so execution survives restarts.
- 02 — Passing data with Context
- Reference: Workflow · Task · Context · Runner