# Flows

> For the complete documentation index, see [llms.txt](https://docs.lertha.com/llms.txt).
> Any documentation page is available as raw Markdown by appending `.md` to its URL.

> A flow is a durable, multi-step program, the shape of an agent run.

A **flow** is the program around tasks. It calls one, then another, fans several out, sleeps for
a week, waits for a person, and survives every process restart along the way. A run of a flow is
addressed exactly like a run of a task: by `RunId`, through a handle.

```kotlin
object AgentRun : FlowDefinition<AgentInput, AgentOutput> {
    override val type = flowType<AgentInput, AgentOutput>("AgentRun")

    override fun execute(input: AgentInput, flow: FlowScope): AgentOutput {
        val history = mutableListOf<String>()
        val approvals = flow.signals<Approval>("approval")

        repeat(input.maxSteps) {
            val plan = flow.task(PlanStep.type, PlanInput(input.goal, history), planning)
            if (plan.tool == null) return AgentOutput(plan.answer, history)

            if (plan.needsApproval) {
                val approval = approvals.receive(30.minutes)
                if (approval?.approved != true) return@repeat
            }
            history += flow.task(RunTool.type, ToolInput(plan.tool, plan.args), tooling).observation
        }
        return AgentOutput("Stopped after ${input.maxSteps} steps.", history)
    }
}
```

## What a flow body may do

Everything goes through the `FlowScope` it is handed:

| Call | Meaning |
| --- | --- |
| `flow.task(type, input, options)` | run a task and wait for it |
| `flow.startTask(...)` then `.await()` | fan out, then collect |
| `flow.sleep(duration)` | a durable timer; the process can die and the timer still fires |
| `flow.signals<T>("name")` | the signals sent to this run under that name, buffered from the start |
| `flow.awaitUntil(timeout) { ... }` | block until a condition holds |
| `flow.now` | engine time, replay-safe |

## The one rule

The body is **deterministic** and it is **not `suspend`**. Given the same input and the same
answers from the engine (task results, signals, time), it makes the same calls in the same order.
No clock, no random, no I/O, no threads. Everything with a side effect is a task.

Why: after a crash the engine re-runs the body from the top, feeding it the recorded answers,
until it reaches the first call without an answer yet. A body that consults the wall clock
breaks that replay. [Determinism](/concepts/determinism) has the details.

## Failure

- A task that fails inside a flow surfaces as `TaskFailed`. Catch it when a fallback exists; the
  "tool failed, try another way" shape of an agent loop.
- A body that throws fails the run, with the exception's message. It is retried only if the run's
  `RetryPolicy` says so, and for flows that should almost always be `RetryPolicy.NONE`: a flow
  that fails usually has a bug, not a blip.
- Cancellation arrives as an exception thrown from a blocking scope call. Never catch `Throwable`
  around a scope call.

**Remember:** the flow is the loop and the waiting; the LLM call and the tool call are tasks.
