Skip to content

Split flow DSL monolith into focused decorator modules#6040

Merged
vinibrsl merged 1 commit into
mainfrom
flow-dsl-refactor
Jun 4, 2026
Merged

Split flow DSL monolith into focused decorator modules#6040
vinibrsl merged 1 commit into
mainfrom
flow-dsl-refactor

Conversation

@vinibrsl
Copy link
Copy Markdown
Member

@vinibrsl vinibrsl commented Jun 4, 2026

The Flow DSL lived in one 1033-line dsl.py that mixed every decorator (@start/@listen/@router), the human_feedback decorator, condition combinators, and FlowDefinition extraction helpers in a single file.

Split it into a dsl/ package where each decorator gets its own module (start.py 68 lines, listen.py 55, router.py 164, human_feedback.py 98) and the shared extraction/condition helpers stay in utils.py. The public API is re-exported from dsl/__init__.py, so import paths are unchanged.

This is simpler because each decorator is now read and changed in isolation instead of scanning a 1000-line file to find one of them, and router-specific annotation parsing no longer sits next to unrelated start/listen logic.


Note

Medium Risk
Large structural move across Flow authoring, runtime wiring, and HITL decorator layering; behavior is intended to stay compatible via re-exports, but regressions in metadata stamping or imports would affect flow execution and visualization.

Overview
Replaces the monolithic Flow DSL module with a crewai.flow.dsl package: _conditions holds or_/and_ and condition normalization, _start, _listen, and _router each own their decorator, and _utils keeps FlowDefinition extraction and shared metadata helpers.

human_feedback is split so human_feedback.py exposes _build_human_feedback_runtime_decorator (execution only) while dsl._human_feedback applies the public decorator and stamps router/definition metadata. crewai.flow.human_feedback.human_feedback remains a thin forwarder to the DSL implementation, and crewai.flow now imports HumanFeedbackResult and human_feedback from crewai.flow.dsl.

runtime.py imports condition helpers from dsl._conditions and definition builders from dsl._utils. DSL __all__ and tests are updated to include human_feedback and HumanFeedbackResult alongside the existing decorators.

Reviewed by Cursor Bugbot for commit 366e7b9. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • New Features

    • Public DSL: added/exported start, listen, router decorators, human_feedback decorator, HumanFeedbackResult, and the or_/and_ condition combinators.
  • Refactor

    • Flow DSL internals reorganized; public decorator/combinator API remains available and compatible.
  • Tests

    • Test updated to assert HumanFeedbackResult is part of the public DSL exports.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

Establishes crewi.flow.dsl as the DSL entrypoint, moves start/listen/router/human_feedback into dsl submodules, implements condition primitives and combinators, refactors DSL utilities to use the new condition module, updates runtime imports, and adds a compatibility shim plus a test asserting exports.

Changes

Flow DSL Package Restructuring

Layer / File(s) Summary
DSL package entrypoint and re-exports
lib/crewai/src/crewai/flow/dsl/__init__.py, lib/crewai/src/crewai/flow/__init__.py
Adds crewi.flow.dsl init with docstring, re-exports (start, listen, router, human_feedback, HumanFeedbackResult, or_, and_) and updates parent package to re-export HumanFeedbackResult and human_feedback from the DSL package.
Condition primitives and combinators
lib/crewai/src/crewai/flow/dsl/_conditions.py
New condition primitives: validation/type-guards, normalization, name extraction, runtime↔definition conversion, and public or_() / and_() combinators.
Human feedback decorator (DSL)
lib/crewai/src/crewai/flow/dsl/_human_feedback.py
Adds DSL human_feedback decorator, metadata stamping, LLM assignment, and exports HumanFeedbackResult.
Router decorator and emit inference
lib/crewai/src/crewai/flow/dsl/_router.py
Adds router decorator, return-annotation parsing for emit inference, deduplication, and FlowMethodDefinition/trigger metadata wiring.
Start decorator
lib/crewai/src/crewai/flow/dsl/_start.py
Adds start decorator to mark unconditional or conditional flow starts and set trigger metadata.
Listen decorator
lib/crewai/src/crewai/flow/dsl/_listen.py
Adds listen decorator factory that converts runtime conditions into listener definitions and sets trigger metadata.
DSL utils refactor
lib/crewai/src/crewai/flow/dsl/_utils.py
Refactors imports to delegate condition handling to _conditions, introduces internal metadata constant and helper ordering, removes module-level all.
Human feedback compatibility shim
lib/crewai/src/crewai/flow/human_feedback.py
Extracts runtime decorator builder into _build_human_feedback_runtime_decorator and replaces exported human_feedback with a shim delegating to crewai.flow.dsl._human_feedback.human_feedback.
Runtime import updates
lib/crewai/src/crewai/flow/runtime.py
Adjusts imports to use crewai.flow.dsl._conditions and crewai.flow.dsl._utils for condition and flow-definition helpers.
Tests
lib/crewai/tests/test_flow_definition.py
Updates test to require HumanFeedbackResult in crewai.flow.dsl.__all__.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • greysonlalonde
  • lorenzejay

Poem

🐰 I hopped into code,
DSL paths neat and new,
Decorators moved home, conditions true,
Shims whisper backward-compat cheer,
Flow hops onward — lint and test near.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main objective: refactoring the Flow DSL from a monolithic module into separate, focused decorator modules.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch flow-dsl-refactor

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
lib/crewai/src/crewai/flow/dsl/__init__.py (1)

1-8: ⚡ Quick win

Update docstring to document human_feedback decorator and HumanFeedbackResult.

The module docstring describes @start, @listen, @router decorators and the or_/and_ condition combinators, but omits @human_feedback and HumanFeedbackResult even though both are exported in __all__ (lines 34, 32). Users consulting the package docstring won't discover these public exports.

📝 Suggested docstring enhancement
 """Flow DSL: the Python authoring layer for Flows.
 
-Provides the ``@start`` / ``@listen`` / ``@router`` decorators and the
-``or_`` / ``and_`` condition combinators used to write Flow classes in
-Python. The DSL is one way to produce a Flow Structure: this package
-extracts a :class:`~crewai.flow.flow_definition.FlowDefinition` from a
-Python Flow class. Execution is handled by ``runtime``.
+Provides the ``@start`` / ``@listen`` / ``@router`` / ``@human_feedback``
+decorators and the ``or_`` / ``and_`` condition combinators used to write
+Flow classes in Python. The DSL is one way to produce a Flow Structure:
+this package extracts a :class:`~crewai.flow.flow_definition.FlowDefinition`
+from a Python Flow class. Execution is handled by ``runtime``.
+
+The ``HumanFeedbackResult`` dataclass captures the outcome of human-in-the-loop
+feedback interactions.
 """
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/src/crewai/flow/dsl/__init__.py` around lines 1 - 8, The module
docstring is missing documentation for the public exports `@human_feedback` and
HumanFeedbackResult (they're listed in __all__), so update the top docstring in
__init__.py to mention the human_feedback decorator and the HumanFeedbackResult
type: briefly state that `@human_feedback` marks steps requiring human
input/annotation and that HumanFeedbackResult represents the structured result
returned by such steps (how it integrates with Flow execution and where to look
for examples); keep the style consistent with the existing descriptions of
`@start/`@listen/@router and the or_/and_ combinators.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@lib/crewai/src/crewai/flow/dsl/__init__.py`:
- Around line 1-8: The module docstring is missing documentation for the public
exports `@human_feedback` and HumanFeedbackResult (they're listed in __all__), so
update the top docstring in __init__.py to mention the human_feedback decorator
and the HumanFeedbackResult type: briefly state that `@human_feedback` marks steps
requiring human input/annotation and that HumanFeedbackResult represents the
structured result returned by such steps (how it integrates with Flow execution
and where to look for examples); keep the style consistent with the existing
descriptions of `@start/`@listen/@router and the or_/and_ combinators.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 20a766ca-6a72-4a5e-8934-86575f3d5967

📥 Commits

Reviewing files that changed from the base of the PR and between aed6923 and 4c63f01.

📒 Files selected for processing (9)
  • lib/crewai/src/crewai/flow/__init__.py
  • lib/crewai/src/crewai/flow/dsl/__init__.py
  • lib/crewai/src/crewai/flow/dsl/human_feedback.py
  • lib/crewai/src/crewai/flow/dsl/listen.py
  • lib/crewai/src/crewai/flow/dsl/router.py
  • lib/crewai/src/crewai/flow/dsl/start.py
  • lib/crewai/src/crewai/flow/dsl/utils.py
  • lib/crewai/src/crewai/flow/human_feedback.py
  • lib/crewai/tests/test_flow_definition.py

@vinibrsl vinibrsl force-pushed the flow-dsl-refactor branch from 4c63f01 to fa590b2 Compare June 4, 2026 16:17
The Flow DSL lived in one 1033-line `dsl.py` that mixed every decorator
(`@start`/`@listen`/`@router`), the `human_feedback` decorator,
condition combinators, and FlowDefinition extraction helpers in a single
file.

Split it into a `dsl/` package where each decorator gets its own module
(`start.py` 68 lines, `listen.py` 55, `router.py` 164,
`human_feedback.py` 98) and the shared extraction/condition helpers stay
in `utils.py`. The public API is re-exported from `dsl/__init__.py`, so
import paths are unchanged.

This is simpler because each decorator is now read and changed in
isolation instead of scanning a 1000-line file to find one of them, and
router-specific annotation parsing no longer sits next to unrelated
start/listen logic.
@vinibrsl vinibrsl force-pushed the flow-dsl-refactor branch from fa590b2 to 366e7b9 Compare June 4, 2026 16:48
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
lib/crewai/src/crewai/flow/dsl/_conditions.py (2)

182-184: 💤 Low value

Silent fallback may mask unexpected input types.

The fallback return str(condition) at line 184 silently converts any unhandled type to a string. If an unexpected type reaches this branch (e.g., a malformed nested condition or wrong object type), it will produce potentially invalid output rather than failing fast.

Consider raising a ValueError for truly unexpected types to surface bugs early.

🛡️ Suggested defensive handling
     if isinstance(condition, list):
         return {"or": [_definition_condition_from_runtime(item) for item in condition]}
-    return str(condition)
+    raise ValueError(f"Unexpected condition type: {type(condition).__name__}")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/src/crewai/flow/dsl/_conditions.py` around lines 182 - 184, The
current fallback in _definition_condition_from_runtime silently converts
unexpected types to strings (return str(condition)), which can mask bugs;
instead, validate the input type and raise a ValueError for truly unexpected
types. Update _definition_condition_from_runtime to only accept the expected
types (e.g., dict, list, str — match the function's handled branches) and
replace the final return str(condition) with raising ValueError(f"Unsupported
condition type: {type(condition)!r}, value={condition!r}") so callers fail fast
and errors include the offending value and type.

38-67: 💤 Low value

Type guard allows incomplete condition dicts.

is_flow_condition_dict validates "conditions" and "methods" keys independently but doesn't require at least one of them. A dict like {"type": "AND"} passes validation, which will cause the downstream consumer in _set_trigger_metadata (context snippet 1) to raise ValueError("Condition dict must contain 'conditions' or 'methods'").

While the consumer handles this defensively, stricter validation at the type-guard level would catch invalid shapes earlier and provide clearer semantics.

🛡️ Suggested stricter validation
     allowed_keys = {"type", "conditions", "methods"}
     if not set(obj).issubset(allowed_keys):
         return False

+    # Require at least one of "conditions" or "methods"
+    if "conditions" not in obj and "methods" not in obj:
+        return False
+
     return True
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/src/crewai/flow/dsl/_conditions.py` around lines 38 - 67,
is_flow_condition_dict currently allows a dict like {"type":"AND"} because it
validates "conditions" and "methods" independently; update
is_flow_condition_dict to require that at least one of "conditions" or "methods"
is present and valid (i.e., after the existing validation for "conditions" being
a list of str/dict and "methods" being a list of str, add a final check that obj
contains "conditions" or "methods" and that the corresponding validated value is
present/non-empty), so the function rejects incomplete condition dicts and
matches the expectation of the downstream _set_trigger_metadata consumer.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@lib/crewai/src/crewai/flow/dsl/_conditions.py`:
- Around line 182-184: The current fallback in
_definition_condition_from_runtime silently converts unexpected types to strings
(return str(condition)), which can mask bugs; instead, validate the input type
and raise a ValueError for truly unexpected types. Update
_definition_condition_from_runtime to only accept the expected types (e.g.,
dict, list, str — match the function's handled branches) and replace the final
return str(condition) with raising ValueError(f"Unsupported condition type:
{type(condition)!r}, value={condition!r}") so callers fail fast and errors
include the offending value and type.
- Around line 38-67: is_flow_condition_dict currently allows a dict like
{"type":"AND"} because it validates "conditions" and "methods" independently;
update is_flow_condition_dict to require that at least one of "conditions" or
"methods" is present and valid (i.e., after the existing validation for
"conditions" being a list of str/dict and "methods" being a list of str, add a
final check that obj contains "conditions" or "methods" and that the
corresponding validated value is present/non-empty), so the function rejects
incomplete condition dicts and matches the expectation of the downstream
_set_trigger_metadata consumer.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 17fa3a8f-0ec2-4b75-950f-dd2e6e0c2a1a

📥 Commits

Reviewing files that changed from the base of the PR and between fa590b2 and 366e7b9.

📒 Files selected for processing (11)
  • lib/crewai/src/crewai/flow/__init__.py
  • lib/crewai/src/crewai/flow/dsl/__init__.py
  • lib/crewai/src/crewai/flow/dsl/_conditions.py
  • lib/crewai/src/crewai/flow/dsl/_human_feedback.py
  • lib/crewai/src/crewai/flow/dsl/_listen.py
  • lib/crewai/src/crewai/flow/dsl/_router.py
  • lib/crewai/src/crewai/flow/dsl/_start.py
  • lib/crewai/src/crewai/flow/dsl/_utils.py
  • lib/crewai/src/crewai/flow/human_feedback.py
  • lib/crewai/src/crewai/flow/runtime.py
  • lib/crewai/tests/test_flow_definition.py
🚧 Files skipped from review as they are similar to previous changes (8)
  • lib/crewai/tests/test_flow_definition.py
  • lib/crewai/src/crewai/flow/dsl/_listen.py
  • lib/crewai/src/crewai/flow/dsl/init.py
  • lib/crewai/src/crewai/flow/dsl/_router.py
  • lib/crewai/src/crewai/flow/init.py
  • lib/crewai/src/crewai/flow/dsl/_human_feedback.py
  • lib/crewai/src/crewai/flow/human_feedback.py
  • lib/crewai/src/crewai/flow/dsl/_utils.py

@vinibrsl vinibrsl merged commit 75dad21 into main Jun 4, 2026
56 of 57 checks passed
@vinibrsl vinibrsl deleted the flow-dsl-refactor branch June 4, 2026 18:02
@coderabbitai coderabbitai Bot mentioned this pull request Jun 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants