> ## Documentation Index
> Fetch the complete documentation index at: https://docs.samplehc.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Designing Workflows

> Guidelines for structuring reliable, maintainable workflows

## Overview

This page collects guidelines for structuring workflows. They are not
requirements — a workflow that ignores all of them will still run — but
following them makes workflows easier to operate, debug, and change.

## Keep Steps Small and Focused

Steps are the unit of retries, error handling, and observability. When a step
fails, the whole step re-runs — so a step that performs one external action
(one API call, one SQL write, one email) is much easier to retry safely than a
step that performs five.

Prefer:

```python theme={null}
workflow.then(
    Step("fetch-order", fetch_order)
).then(
    Step("generate-letter", generate_letter)
).then(
    Step("send-letter", send_letter, options={"num_retries": 3})
)
```

over a single step that fetches, generates, and sends. With separate steps, a
failure in `send-letter` retries only the send — it does not regenerate the
letter or refetch the order.

## Make Steps Safe to Re-run

Steps re-run in two situations: when they are retried after a failure, and when
they resume after a [suspension](/workflows/suspend-resume). Design step bodies
so running them twice does not cause duplicate side effects:

* Start steps that suspend with the resume guard:
  `if ctx.resume_data: return ctx.resume_data`
* Do reads and computation first; save external side effects (emails, faxes,
  database writes) for the end of the step — or better, their own step.
* For database writes, prefer upserts (`INSERT ... ON CONFLICT ... DO UPDATE`)
  over plain inserts so a retried step does not create duplicate rows.

## Pass Small Values Between Steps, Store Large State Elsewhere

Step outputs are stored and passed between steps as JSON (see
[Step Outputs](/workflows/workflows-and-steps#step-outputs)). Keep them small
and meaningful: IDs, statuses, and the few fields later steps actually need.

* For **documents**, pass the document `id` and `fileName` rather than file
  contents.
* For **state that outlives the run** (processing history, cross-run
  deduplication, reporting), use the [database](/guides/database) and pass row
  identifiers between steps.

## Choose the Right Boundary Between Automation and Review

Screen steps are for judgment calls; normal steps are for everything else. A
useful pattern is to compute a recommendation in an automated step, then let
the screen present it for confirmation — rather than asking the user to do the
analysis themselves. If a screen is approving the same thing every time,
consider replacing it with a condition and only routing exceptional cases to a
screen using [conditionals](/workflows/control-flow#conditionals).

## Split Large Automations into Multiple Workflows

A single workflow run works best when it represents a single unit of work — one
order, one fax, one claim. When a process spans many records or stages,
split it up:

* Use an emitter workflow on a cron schedule to fan records out to a processor
  workflow — see [Emitter Workflows](/workflows/events#emitter-workflows).
* Use [events](/workflows/events) to chain stages together, so each stage shows
  up as its own run with its own status and task list.

This keeps each run's task list short, makes failures visible at the right
granularity, and lets stages be retried independently.

## Organize Workflow Code as It Grows

The workflow template starts with everything in `src/workflow.py`. As a
workflow grows, split it into modules and keep `workflow.py` focused on the
step graph itself:

```
src/
  workflow.py          # Workflow definition (steps, control flow)
  client_manager.py    # SampleHC client setup (from the template)
  config.py            # Constants, schemas, environment-specific values
  lib/
    orders.py          # Helper functions grouped by domain
  screens/
    review-screen.tsx
```

Step body functions can live in the helper modules and be imported into
`workflow.py`, which makes the workflow definition read like an outline of the
process.

## Make Runs Identifiable

A workflow list full of runs that can only be told apart by start time is hard
to operate. Use [column values](/workflows/column-values) to surface the
identifiers an operator would search for — order numbers, patient identifiers,
statuses — and keep them populated from the first step, not just at the end.
