# Workflow functions run once and work under arun()

> Agno calls a function passed as Workflow(steps=...) exactly once with the parameters it declares, so a TypeError from its body surfaces without a second run, and sync generators now run under arun().

- Published: 2026-10-22
- Author: Kaustubh Shukla
- Categories: Changelog
- Canonical: https://www.agno.com/blog/workflow-functions-run-once-and-work-under-arun
- Markdown: https://www.agno.com/blog/workflow-functions-run-once-and-work-under-arun.md

Agno's `Workflow` dropped the `except TypeError` fallback around a function passed as `steps`, which in earlier releases called the function a second time. Python can't tell a bad argument binding from a bug like `"Total: " + 42` in the body, so that fallback fired on ordinary bugs. A sync function with default parameters ran again with no input, and an async function ran again with the same input. A function that charges a card would have charged twice.

A counter on both releases shows it:

```python
calls = []

def checkout(workflow=None, execution_input=None):
    calls.append(execution_input.input if execution_input else None)
    return "Total: " + 42

Workflow(name="checkout", steps=checkout).run("order-1")
# The same body as async def acheckout(workflow, execution_input) runs under arun("order-2")
```

```text
# Before (real output)
ERROR   Function signature inspection failed. Falling back to original calling convention.
TypeError: can only concatenate str (not "int") to str
checkout ran 2 time(s): ['order-1', None]
async checkout ran 2 time(s): ['order-2', 'order-2']

# Now (real output)
TypeError: can only concatenate str (not "int") to str
checkout ran 1 time(s): ['order-1']
async checkout ran 1 time(s): ['order-2']
```

### Sync functions and generators under arun()

Agno now routes sync generators and sync functions through the same signature check on every run path. Earlier releases called them positionally with every internal keyword argument, so a sync generator failed under `arun()` whatever its signature, a sync function failed under `arun(stream=True)`, and a narrow sync generator failed under `run()`:

```python
def summarize(execution_input):
    return f"done {execution_input.input}"

def stream_summary(execution_input):
    yield f"done {execution_input.input}"
```

```text
# Before (real output)
summarize, arun(stream=True): TypeError: summarize() got an unexpected keyword argument 'websocket'
stream_summary, arun(): TypeError: stream_summary() got an unexpected keyword argument 'websocket'
stream_summary, arun(stream=True): TypeError: stream_summary() got an unexpected keyword argument 'websocket'
stream_summary, run(): TypeError: stream_summary() takes 1 positional argument but 2 were given

# Now (real output)
summarize, arun(stream=True): done order-3
stream_summary, arun(): done order-4
stream_summary, arun(stream=True): done order-4
stream_summary, run(): done order-5
```

One signature behaves differently now. A positional-only `workflow` parameter with a default, `def steps(workflow=None, /)`, used to run with `workflow=None` and now raises `TypeError`.

See the [cookbook](https://github.com/agno-agi/agno/blob/main/cookbook/04_workflows/01_basic_workflows/03_function_workflows/function_workflow.py), and learn more about the [fully Python workflow](https://docs.agno.com/workflows/workflow-patterns/fully-python-workflow) in the documentation.
