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

# Workflows and Steps

> What are Sample Workflows?

## Normal Steps

You can use `Step` to define a normal step. Normal steps takes a body function that is called when the step is reached.

```python theme={null}
from workflows_py.workflow import Step, Workflow

def add_five(ctx):
    return ctx.get_step_result("multiply_by_two") + 5

workflow = Workflow()
workflow.then(
    Step("multiply_by_two", lambda ctx: ctx.get_start_data()["input_number"] * 2),
).then(
    Step("add_five", add_five)
)
```

This example shows a workflow with two automated steps. The body function of each step can either be defined inline (using the `lambda` keyword) or as a separate function.

Body functions can use the context (`ctx`) to access data from previous steps or external systems. Read more in the [context](/workflows/context) section.
The return value of each function can be accessed by subsequent steps using `ctx.get_step_result("step-id")`.

Normal steps also accept an `options={...}` argument for step-level behavior such
as retries, `on_failure`, and `next_task` routing overrides. See
[Control Flow](/workflows/control-flow) for those patterns.

## Screen Steps

Screen steps are steps that present a UI to the user for input or review. To define a screen step, you need to both define

* a React component (a `.tsx` file)
* a `ScreenStep` in the workflow that points to the React component by file path

<CodeGroup>
  ```python ./src/workflow.py theme={null}
  from workflows_py.workflow import ScreenStep, Step, Workflow

  workflow = Workflow()
  workflow.then(
      Step("prepare-greeting", lambda ctx: {"user_name": "John"})
  ).then(
      ScreenStep("greeting", screen_path="./screens/greeting-screen.tsx", get_props=lambda ctx: {"name": ctx.get_step_result("prepare-greeting")["user_name"]})
  )
  ```

  ```tsx ./src/screens/greeting-screen.tsx theme={null}
  import ActionBar from "@/components/ui/action-bar";
  import { useScreenFunctions } from "@samplehc/ui/contexts";

  const GreetingScreen = ({ name }: { name: string }) => {
    const { handleComplete } = useScreenFunctions();
    
    return (
      <div>
        <h2>Hello, {name}!</h2>
        <p>Ready to continue with the workflow?</p>
        <ActionBar onConfirm={() => handleComplete({ result: "confirmed" })} />
      </div>
    );
  };

  export default GreetingScreen;
  ```
</CodeGroup>

The return value of screen steps is the value passed into the `result` field of the `handleComplete` function. In the example above (see `greeting-screen.tsx`), the return value will be the string `"confirmed"`.

When a screen submits an object, downstream workflow steps receive that object directly. Do not unwrap an extra `result` field when reading the step output:

```tsx ./src/screens/review-screen.tsx theme={null}
handleComplete({
  result: {
    approved: true,
    memberId: "ABC123",
  },
});
```

```python ./src/workflow.py theme={null}
def use_review_result(ctx):
    review = ctx.get_step_result("review-screen")
    return {
        "approved": review["approved"],
        "member_id": review["memberId"],
    }
```

See the [screens](/screens) section for more information on how to create and customize screens.

### Validating Screen Submissions

Use the `before_complete` option to run backend logic when the user completes a
screen, before the workflow moves on. The handler receives the context and the
screen's submitted result, and its return value becomes the step's result.

Raising an `ApiError` from `before_complete` rejects the submission: the error
is returned to the screen's `handleComplete` call with the given HTTP status
code, and the task stays open so the user can correct their input and resubmit.

```python theme={null}
from workflows_py.workflow import ApiError, ScreenStep

def validate_review(ctx, result):
    if not result.get("memberId"):
        raise ApiError("Member ID is required", 400)

    # The return value becomes the step result
    return {**result, "validated": True}

workflow.then(
    ScreenStep(
        "review-submission",
        screen_path="./screens/review-screen.tsx",
        before_complete=validate_review,
    )
)
```

<Tip>
  `before_complete` is the right place for validation that needs backend data —
  for example, checking a submitted value against an external system before
  accepting it. Validation that only depends on the form itself is better done
  in the screen component.
</Tip>

<Note>Available in `workflows-py >= 0.1.10`.</Note>

### Screen Task Priority

When several screen tasks are open at the same time — for example, from
parallel branches or loop iterations — Sample routes the user to the
highest-priority task after they complete their current one. Set a screen
step's priority with the `priority` option, either as a number or as a
function of the context:

```python theme={null}
workflow.then(
    ScreenStep(
        "review-urgent-cases",
        screen_path="./screens/review-screen.tsx",
        options={
            "priority": lambda ctx: 10 if ctx.scope["item"]["is_urgent"] else 0,
        },
    )
)
```

Higher numbers are routed to first. The default priority is `0`, and tasks with
equal priority fall back to Sample's standard routing order.

<Note>Available in `workflows-py >= 0.1.22`.</Note>

## Step Outputs

Every step in a workflow can return a value. This value can be accessed by subsequent steps using `ctx.get_step_result("step-id")`.

**IMPORTANT: All outputs from workflow steps must be JSON serializable.** This is critical for Sample Workflows to function properly, as step outputs are stored and passed between steps in a serialized format.

### JSON Serializable Types

Step outputs can include:

* **Primitive types**: `string`, `number`, `boolean`, `null`
* **Arrays**: Lists of JSON-serializable values
* **Objects**: Plain objects with JSON-serializable properties
* **Nested structures**: Any combination of the above

### Example: Correct Step Output

```python theme={null}
# ✅ Good - JSON serializable
def process_data(ctx):
    return {
        "user_id": 123,
        "name": "John Doe",
        "scores": [85, 92, 78],
        "metadata": {
            "processed_at": "2024-01-15T10:30:00Z",
            "status": "completed"
        }
    }
```

### Example: Incorrect Step Output

```python theme={null}
# ❌ Bad - Not JSON serializable
def process_data(ctx):
    return {
        "user": User(id=123, name="John"),  # Class instance
        "callback": lambda x: x * 2,        # Function
        "created_at": datetime.now()        # Date object
    }
```

If you need to work with non-serializable data, convert it to a serializable format before returning from the step.

## Version Control and Deployment

Sample Workflows are **version controlled through Git**, making it easy to track changes, collaborate with team members, and maintain different versions of your workflows.

### Git-Based Version Control

* **Workflow definitions** (Python files) and **screen components** (TypeScript files) are stored in a Git repository
* This provides full history tracking and the ability to revert to previous versions if needed

### Automatic Deployment

When changes are made to workflow files:

1. **Automatic Detection**: Sample automatically detects changes to workflow files
2. **Deployment**: Updated workflows are deployed and become available for use
3. **Version Management**: Previous versions remain accessible, ensuring running workflows can complete even after updates
