* Add single agent action to Flow definitions
Lets a flow method build and run a single CrewAI agent directly, without
wrapping it in a crew. Same idea as the existing `crew` action, but for
one agent.
methods:
answer:
do:
call: agent
with:
role: Analyst
goal: Answer questions
backstory: Knows things.
input: "${state.question}"
start: true
* `input` is required and interpolated from flow state, like
`${state.question}` or `${item}` inside an `each` loop
* optional `response_format` points at a Pydantic model (`{"python":
"models.AnswerModel"}`) to get structured output
* `input` must be a string and its CEL is validated at load time, so bad
expressions like `${state.}` fail early
* Simplify test code
* Validate flow CEL expressions at definition load time
Promote CEL expression handling to a public Expression API and validate expressions when a FlowDefinition is built instead of when it executes.
Invalid CEL syntax or unknown roots now raise ValidationError from FlowDefinition.from_yaml() and FlowDefinition.from_dict(). Expressions may reference state and outputs, plus item inside each.do; bare identifiers are rejected as unknown roots.
For with values, the CEL contract is intentionally simple: after trimming whitespace, a string is evaluated as CEL only if it starts with ${ and ends with }. Anything else is treated as a literal value, so partial interpolation is not supported. If the content inside the wrapper is not valid CEL, validation fails.
Examples:
```text
"${state.topic}" -> evaluated, returns state.topic
"topic is ${state.topic}" -> literal string
"${state.topic} suffix" -> literal string
"${'a'}${'b'}" -> invalid CEL
```
* Honor explicit empty-context overrides in evaluate() / render_template()
* Use explicit name/action shape for each.do steps
* Add optional `if` expression to `each.do` steps
Lets a step inside an `each` action run conditionally based on a CEL
expression evaluated against `item` and prior step `outputs`.
* Add script/code blocks to FlowDefinition
Let a Flow method run trusted inline Python with `call: script`. The code
is compiled once into a generated function and receives the runtime
values as arguments.
```yaml
methods:
normalize:
start: true
do:
call: script
code: |
import math
state["rounded"] = math.ceil(state["raw_score"])
return f"rounded:{state['rounded']}"
```
Even though this shares the same surface of tools (custom code), I
decided to make it opt-in for now, using
`CREWAI_ALLOW_FLOW_SCRIPT_EXECUTION=1`.
* Address code review comments
A `do:` step can now say `call: tool` and name a CrewAI tool to run,
passing its inputs under `with:`. Before this, a definition could only
point at Python code to run.
```yaml
methods:
search:
start: true
do:
call: tool
ref: crewai_tools:ExaSearchTool
with:
search_query: ai agents
```
* Drive human feedback from the flow definition
@human_feedback previously wrapped methods with the full HITL runtime (feedback
request, outcome collapse, learn loop), so flows built from a YAML definition —
which carry no decorated callables — could not pause for or route on human
feedback.
# Conflicts:
# lib/crewai/src/crewai/flow/persistence/decorators.py
# lib/crewai/src/crewai/flow/runtime/__init__.py
* Address code review comments
* Wire config and persistence from FlowDefinition into the runtime
`from_definition` was silently dropping all config fields; it now passes
`config.model_dump()` so suppress_flow_events, max_method_calls, etc.
actually apply.
Persistence is now engine-driven: `_persist_method_completion` fires
after every method using the definition's persist metadata, so
`@persist` no longer needs to wrap methods — it just stamps them.
* Address code review comments
* Read flow dispatch from FlowDefinition
Store the definition in a `_definition` PrivateAttr at post-init and
convert the dispatch helpers (`_start_method_names`, `_listener_methods`,
`_start_condition`, `_listen_condition`, `_is_router`) from classmethods
to instance methods that read it. Event names now fall back to
`self._definition.name` instead of `self.__class__.__name__`.
Behavior is identical for decorator subclasses, but the engine no longer
assumes the definition comes from the class. This is the seam for
`Flow.from_definition`, where an instance runs a definition that was
loaded rather than built from a Python subclass.
* Add Flow.from_definition to run flows without a subclass
A FlowDefinition (e.g. loaded from YAML) was only usable for dispatch on
decorator-authored subclasses. Now each method definition records an
importable `module:qualname` handler ref, and `Flow.from_definition`
resolves and binds those handlers to build a runnable flow directly.
* Build flow state from FlowDefinition
Definition-driven flows previously always started with a bare dict
state.
* Replace handler string with structured FlowActionDefinition
`handler: str | None` was optional and opaque — missing handlers only
surfaced at kickoff time. `do: FlowActionDefinition` is required, so
Pydantic rejects invalid definitions at parse time.
The `call: "code"` discriminator prepares the schema for future
non-Python action types (e.g. MCP tool, crew) without touching
`FlowMethodDefinition`. Resolution logic is extracted to
`runtime/_action_resolvers.py` to keep the dispatch point isolated.
* Fix conversational start router missing required do field
FlowMethodDefinition.do became required when the handler string was
replaced with FlowActionDefinition, but _conversation_start_router still
built its fragment without it, breaking crewai import entirely.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Add event scoping to flow test
* Change lib/crewai/tests/test_flow_from_definition.py
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>