Skip to main content
Cloacina Documentation
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

01 — Your First Workflow

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

A workflow named greeting with one task that writes a message into the Context, executed by a Runner against SQLite.

Step 1 — Add Cloacina

[dependencies]
cloacina = "0.7"
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 + PostgreSQL

Pre-built wheels for Linux and macOS on Python 3.9–3.12.

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 context

Step 3 — Run it

Create a Runner against SQLite and execute the workflow by name.

use cloacina::runner::{DefaultRunner, DefaultRunnerConfig};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let runner = DefaultRunner::with_config(
        "sqlite://app.db?mode=rwc",
        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

  • 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.

Next