Task
A Task is the unit of work in a Workflow
— and the unit of scheduling. It is an async function with a unique id, a
list of dependencies (the DAG edges), and optional retry behavior. It receives a
Context, does its work, and returns the
(possibly modified) context.
Mental model
Section titled “Mental model”- A task is contained by a workflow; its
dependenciesdeclare which tasks must complete first. - It reads and writes the shared context.
- It is claimed atomically and retried on failure per its retry policy. Because execution is at-least-once, a task must be idempotent.
Interfaces
Section titled “Interfaces”A task with one dependency, declared in each interface:
use cloacina::{task, Context, TaskError};
#[task(dependencies = ["fetch"])]async fn transform(context: &mut Context<serde_json::Value>) -> Result<(), TaskError> { let raw = context.get("raw").cloned().unwrap_or_default(); context.insert("transformed", raw)?; Ok(())}import cloaca
@cloaca.task(dependencies=["fetch"])def transform(context): context.set("transformed", context.get("raw")) return contextRetries
Section titled “Retries”Retry behavior is configured on the task itself. The discrete knobs:
retry_attempts, retry_backoff (fixed / linear / exponential),
retry_delay_ms, retry_max_delay_ms, retry_condition
(never / transient / all), and retry_jitter.
#[task( retry_attempts = 5, retry_backoff = "exponential", retry_delay_ms = 1000,)]async fn flaky(context: &mut Context<serde_json::Value>) -> Result<(), TaskError> { // ... Ok(())}@cloaca.task( retry_attempts=5, retry_backoff="exponential", retry_delay_ms=1000,)def flaky(context): return contextSee Conditional Retries for retry-condition patterns.
Documenting a task: what: / why:
Section titled “Documenting a task: what: / why:”A #[task] doc-comment (Rust ///) or @task docstring (Python) can carry
structured documentation that the compiler lifts into the package manifest and
surfaces on each WorkflowTaskNode (as doc_what / doc_why). The convention
is line-leading, case-insensitive what: and why: markers: text following a
what: line routes into the what field, text following a why: line routes
into why. With no markers, the whole comment becomes what and why is
left empty.
#[task(id = "validate")]/// what: validates the incoming order/// why: downstream pricing assumes a clean orderasync fn validate(context: &mut Context<serde_json::Value>) -> Result<(), TaskError> { Ok(())}@cloaca.task(id="extract")def extract(context): """ what: pulls rows from the source why: the rest of the graph needs them staged """ return contextThe parsing is best-effort and degrades gracefully — it is never required and
never fails the build. A task with no doc-comment contributes no docs; a source
file that fails to parse simply contributes nothing rather than erroring. The
extractor lives in crates/cloacina-compiler/src/doc_parse.rs and runs against
the unpacked package source at build time (CLOACI-I-0126 / T-0752).
Key facts
Section titled “Key facts”idis the task’s identity within its workflow. In Python it defaults to the function name if omitted.dependenciesare task ids (or, in Python, task functions) that must complete first.- Tasks are async; offload blocking/CPU-bound work (
tokio::task::spawn_blockingin Rust, or a blocking call in Python).