Skip to content

feat(cli): run declarative flows on the TUI (headless terminal fallback)#6484

Open
joaomdmoura wants to merge 8 commits into
mainfrom
joaomdmoura/declarative-flow-tui
Open

feat(cli): run declarative flows on the TUI (headless terminal fallback)#6484
joaomdmoura wants to merge 8 commits into
mainfrom
joaomdmoura/declarative-flow-tui

Conversation

@joaomdmoura

@joaomdmoura joaomdmoura commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

What

Normalizes declarative flow execution onto the CrewRunApp TUI, matching how declarative (JSON) crews and conversational flows already run. Headless contexts keep a direct-terminal path for deploy/CI.

Before this PR, crewai run used four surfaces: declarative crews → TUI, conversational flows → TUI, classic Python flows → terminal, declarative flows → terminal. This is the first step toward running everything on the TUI.

Behavior

  • Interactive terminal → the TUI. A new STEPS panel shows each declarative flow method as a first-class step (driven by FlowStarted / MethodExecutionStarted/Finished/Failed events), labeled with its call type (crew/agent/expression/…) read from the flow definition. Crews/agents inside a method keep streaming in the main panel.
  • Headless (deploy/CREWAI_DMN, piped output, CI, any non-TTY) → the existing direct-terminal kickoff + printed result. Gated by is_interactive().

Changes

  • crew_run_tui.py: _run_flow_worker (runs flow.kickoff in a thread worker, reuses _on_crew_done/_on_crew_failed + _stringify_output); an _is_flow_run gate so crew rendering is byte-identical; flow-event subscriptions building _flow_steps; STEPS sidebar + flow-aware header.
  • run_declarative_flow.py: is_interactive() branch → _run_declarative_flow_tui (instantiates EventListener, derives the method-type map from flow._definition, mirrors the crew exit-code + deploy-chain contract) or the existing terminal path.

Design note

The approved plan gated on is_dmn_mode_enabled() alone; I switched to is_interactive() (which folds in the DMN check and requires a TTY) so non-TTY runs — CI, pipes, CliRunner — never launch a TUI. This is strictly more correct and keeps existing headless flow tests green.

Out of scope / follow-ups

  • Classic Python flows onto the TUI (next step toward full normalization).
  • crewai deploy validation has a separate declarative-flow-entrypoint gap (DeployValidator._check_flow_entrypoint) unrelated to the TUI Deploy button, which uses skip_validate=True.

Testing

  • New tests in test_crew_run_tui.py (flow dispatch, method-event STEPS build, completion sweep, end-to-end run_test() mount) and test_run_declarative_flow.py (TUI vs terminal routing, exit codes, deploy chaining, method-type extraction).
  • Full lib/cli suite: 659 passed; the 7 remaining failures are pre-existing and unrelated (version-string drift + a git fetch mock in create/tools tests).
  • ruff format/ruff check/mypy clean; mirrored lib/crewai/tests/cli still green.

🤖 Generated with Claude Code


Note

Medium Risk
Changes default CLI run behavior for interactive declarative flows and threads kickoff through the TUI; crew paths are gated but shared completion handlers and event subscriptions are extended.

Overview
Interactive declarative flows now launch CrewRunApp instead of printing kickoff output on the terminal, aligning them with JSON crews and conversational flows. Routing uses is_interactive() (TTY + not DMN); flows with @human_feedback still use the terminal path.

CrewRunApp gains a declarative-flow mode (_is_flow_run): _run_flow_worker runs flow.kickoff in a thread, subscribes to flow method events to build a STEPS sidebar (with call-type labels from the definition), and updates the header for active/paused methods without letting nested crew kickoffs rename the flow. Completion/failure messaging uses “flow” vs “crew” via _run_noun.

run_declarative_flow.py adds _run_declarative_flow_tui (starts EventListener, forces suppress_flow_events off for the TUI, post-run Rich summary, exit codes, quit → os._exit(130), deploy chaining like JSON crews).

ConsoleFormatter.print_panel skips Rich flow panels when is_tui_mode() is set so output does not corrupt the Textual UI.

Tests cover TUI vs terminal routing, STEPS/event behavior, and TUI-mode panel suppression.

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

…fallback

Declarative flows now run on the CrewRunApp TUI when interactive, matching
declarative crews and conversational flows. Headless contexts — CREWAI_DMN
(deploy), piped output, CI, any non-TTY — fall back to the direct-terminal
kickoff, gated by is_interactive() (folds in the CREWAI_DMN check and requires
a real TTY).

The TUI shows per-method progress: a new STEPS panel driven by flow method
events (FlowStarted / MethodExecutionStarted/Finished/Failed), each labeled
with its declarative call type (crew/agent/expression/…) read from the flow
definition. Crews/agents inside a method keep streaming in the main panel via
the existing crew/task/LLM handlers.

- crew_run_tui.py: _run_flow_worker (flow.kickoff in a thread worker; reuses
  _on_crew_done/_on_crew_failed + _stringify_output), _is_flow_run gate so crew
  rendering is byte-identical, flow-event subscriptions building _flow_steps,
  and the STEPS sidebar + flow-aware header.
- run_declarative_flow.py: is_interactive() branch → _run_declarative_flow_tui
  (EventListener, method-type map from flow._definition, crew-parity exit codes
  and deploy chaining) or the existing terminal path.

Deviation from the approved plan: gate on is_interactive() rather than
is_dmn_mode_enabled() alone, so non-TTY runs (CI/pipes/CliRunner) never launch
a TUI — this also keeps existing headless flow tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RBYGqJHC2TMC6fonFziuuh
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds declarative flow TUI execution, flow-step tracking, TTY-based routing, and TUI-mode console suppression. Interactive flow runs now use the TUI, update step status from flow events, and print a post-run Rich summary with exit handling.

Changes

Declarative Flow TUI Support

Layer / File(s) Summary
Flow state and gating in CrewRunApp
lib/cli/src/crewai_cli/crew_run_tui.py
Adds flow-specific state fields and an _is_flow_run gate, and routes mount-time execution to the flow worker for flow runs.
Flow worker execution and step status updates
lib/cli/src/crewai_cli/crew_run_tui.py
Runs declarative flow kickoff in _run_flow_worker, records the raw and display results, and marks active flow steps done or failed during overall completion handling.
Flow lifecycle event subscription
lib/cli/src/crewai_cli/crew_run_tui.py
Imports declarative-flow event types and registers handlers that create and update _flow_steps from flow-start and method execution events.
STEPS panel and flow task header rendering
lib/cli/src/crewai_cli/crew_run_tui.py
Renders a flow STEPS sidebar and flow-specific task header text instead of the standard task and agent display.
run_declarative_flow TUI routing and summary
lib/cli/src/crewai_cli/run_declarative_flow.py
Routes interactive runs through a TUI path, keeps non-interactive kickoff behavior with error handling, and adds flow method type extraction plus post-TUI Rich summary rendering.
CrewRunApp flow tests
lib/cli/tests/test_crew_run_tui.py
Adds tests for flow-run gating, flow step updates from method events, overlapping active methods, agent clearing, and an end-to-end flow completion path.
run_declarative_flow routing and helper tests
lib/cli/tests/test_run_declarative_flow.py
Adds a headless default fixture, a fake TUI app helper, and tests for routing, exit behavior, deploy chaining, flow event suppression, and flow method type extraction.
TUI flow panel suppression
lib/crewai/src/crewai/events/utils/console_formatter.py, lib/crewai/tests/utilities/test_console_formatter_tui_mode.py
Skips Rich flow panel printing in TUI mode and verifies flow and non-flow panel output behavior under TUI mode.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as run_declarative_flow
  participant App as CrewRunApp
  participant Bus as EventListener
  participant Flow
  CLI->>App: _run_declarative_flow_tui(flow, inputs)
  App->>Bus: initialize listener
  App->>Flow: kickoff(inputs)
  Flow->>Bus: emit flow and method events
  Bus->>App: update _flow_steps and status
  App->>App: _print_flow_post_tui_summary()
  App-->>CLI: return _crew_result or exit
Loading

Suggested reviewers: greysonlalonde, gabemilani

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Title check ✅ Passed The title clearly describes the main change: declarative flows now run on the TUI with a headless terminal fallback.
Description check ✅ Passed The description is detailed and directly matches the changes in routing declarative flows to the TUI with terminal fallback.
✨ 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 joaomdmoura/declarative-flow-tui

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.

Comment thread lib/cli/src/crewai_cli/crew_run_tui.py
…press_flow_events

Review follow-up: the STEPS panel and header are driven by flow method events
(FlowStarted / MethodExecution*), but the declarative runtime skips emitting
those when the flow declared config.suppress_flow_events. Interactive TUI runs
would then keep STEPS on "waiting…" and the header on "Starting flow…" while
nested crews still execute.

_run_declarative_flow_tui now forces flow.suppress_flow_events = False for the
interactive run (mirroring how the conversational path mutates the flow for the
TUI). The headless/terminal path never reaches this and keeps the flow's
declared setting. Regression test: test_run_declarative_flow_tui_enables_flow_events.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RBYGqJHC2TMC6fonFziuuh
Comment thread lib/cli/src/crewai_cli/crew_run_tui.py
Review follow-up: the flow header keys off _current_method, which was set on
MethodExecutionStarted but never cleared on Finished/Failed. Between steps (or
after a failed method before kickoff exits) the header kept spinning the old
method name while the STEPS sidebar already showed it done/failed.

_clear_current_method now drops the header's active method when it ends,
falling back to another still-active step (methods can overlap) or none. The
header's idle fallback shows "Working…" once a step has run and "Starting
flow…" only before the first method.

Tests: test_current_method_clears_and_falls_back_across_overlap, plus a
_current_method assertion in test_flow_method_events_build_steps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RBYGqJHC2TMC6fonFziuuh

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@lib/cli/src/crewai_cli/crew_run_tui.py`:
- Around line 735-739: The flow header is reusing the previous method’s agent
because _current_agent is not reset when _current_method changes in
crew_run_tui.py. Update the method-switching logic around the block that
compares finished_name and selects the next active step so that whenever
_current_method is reassigned to a different step, _current_agent is also
cleared. Apply the same behavior in the other affected location tied to the
active-step transition so the header does not briefly show a stale agent until a
new agent event arrives.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1aec9379-2d5c-462f-8c83-291abea76eb7

📥 Commits

Reviewing files that changed from the base of the PR and between 6b7529f and ec55c31.

📒 Files selected for processing (2)
  • lib/cli/src/crewai_cli/crew_run_tui.py
  • lib/cli/tests/test_crew_run_tui.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/cli/tests/test_crew_run_tui.py

Comment thread lib/cli/src/crewai_cli/crew_run_tui.py
Comment thread lib/cli/src/crewai_cli/run_declarative_flow.py Outdated
…method change

Two review follow-ups:

1) Method panels break Textual TUI (Cursor): forcing suppress_flow_events off
   so the STEPS panel receives events also un-gated the EventListener's Rich
   flow/method panels (ConsoleFormatter.print_panel prints is_flow=True panels
   regardless of verbose), which interleave with Textual and corrupt the TUI.
   print_panel now skips is_flow panels when is_tui_mode() is set (the same
   context the TUI worker already establishes and the tracing listeners already
   honor). Non-TUI/headless flow runs are unaffected. Test:
   test_console_formatter_tui_mode.

2) Flow header showed a stale agent (CodeRabbit): _current_agent persisted
   across methods. It's now cleared when a method starts and when the active
   method changes, so the header never shows the previous method's agent until
   a new agent event arrives. Test: test_flow_method_transitions_clear_current_agent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RBYGqJHC2TMC6fonFziuuh
Comment thread lib/cli/src/crewai_cli/crew_run_tui.py
Comment thread lib/cli/src/crewai_cli/crew_run_tui.py
Two review follow-ups on the flow TUI:

1) Crew kickoff renamed the flow (Cursor): CrewKickoffStartedEvent overwrote
   _crew_name / the app title with a nested `call: crew` step's crew name, so
   the post-run summary could be labeled with a child crew. The rename is now
   gated on `not _is_flow_run`, preserving the flow's name; crew runs still
   adopt the crew name. Tests: test_crew_kickoff_does_not_rename_flow_run,
   test_crew_kickoff_renames_in_crew_mode.

2) Paused methods showed active (Cursor): the TUI didn't handle
   MethodExecutionPausedEvent, so a @human_feedback pause left the STEPS
   spinner running (flow status panels are suppressed in TUI mode). It now
   marks the step "paused" (⏸, teal) and the header shows "waiting for
   feedback" instead of a spinner. Test: test_method_paused_marks_step_paused.

Note: interactively *providing* human feedback from the flow TUI is a separate
follow-up; this only makes the pause visible instead of a silent stuck spinner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RBYGqJHC2TMC6fonFziuuh

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
lib/cli/tests/test_crew_run_tui.py (1)

1644-1667: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Paused-event test correctly validates the _current_method contract.

The on_method_paused handler only calls _set_flow_step_status and does not explicitly set _current_method — it relies on the preceding MethodExecutionStartedEvent having set it. The test correctly emits the started event first and then asserts _current_method == "ask" after the pause, validating that _current_method persists across the pause transition as intended by the implementation comment ("_current_method stays pointed at it").

One consideration: if a MethodExecutionPausedEvent were ever emitted without a prior MethodExecutionStartedEvent, _current_method would be None. This is unlikely in practice (a method must start before it can pause), but you may want to add a defensive self._current_method = event.method_name in the paused handler if you want the contract to be self-contained. This is optional.

🤖 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/cli/tests/test_crew_run_tui.py` around lines 1644 - 1667, The
paused-event flow in CrewRunApp is currently only covered by a test that relies
on MethodExecutionStartedEvent having already set _current_method; if you want
the paused handler to be self-contained, update on_method_paused to also assign
_current_method from the event before calling _set_flow_step_status. Keep the
existing test in test_method_paused_marks_step_paused, and add or adjust
coverage to verify _current_method still points to the paused method even if the
paused event is handled directly.
🤖 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/cli/tests/test_crew_run_tui.py`:
- Around line 1644-1667: The paused-event flow in CrewRunApp is currently only
covered by a test that relies on MethodExecutionStartedEvent having already set
_current_method; if you want the paused handler to be self-contained, update
on_method_paused to also assign _current_method from the event before calling
_set_flow_step_status. Keep the existing test in
test_method_paused_marks_step_paused, and add or adjust coverage to verify
_current_method still points to the paused method even if the paused event is
handled directly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0cfa35bc-3fee-41c0-be07-da7453b83826

📥 Commits

Reviewing files that changed from the base of the PR and between 46cae1a and 3a7196f.

📒 Files selected for processing (2)
  • lib/cli/src/crewai_cli/crew_run_tui.py
  • lib/cli/tests/test_crew_run_tui.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/cli/src/crewai_cli/crew_run_tui.py

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3a7196f. Configure here.

Comment thread lib/cli/src/crewai_cli/crew_run_tui.py
Comment thread lib/cli/src/crewai_cli/run_declarative_flow.py
…he TUI

Two review follow-ups, both rooted in @human_feedback methods:

- Paused flow marked complete (Cursor): async human feedback makes kickoff
  RETURN a HumanFeedbackPending marker (not raise), which _run_flow_worker
  would stringify and report as a successful completion with exit 0.
- Sync feedback breaks TUI (Cursor): default (sync) @human_feedback collects
  input via the flow runtime's Rich console.print + blocking input(), which
  interleaves with Textual and leaves the user unable to review output or
  submit feedback.

run_declarative_flow now routes any flow whose declarative definition declares
human feedback (_flow_uses_human_feedback) to the terminal path, where blocking
input and Rich prompts work natively — regardless of interactivity. Non-feedback
flows still get the TUI. Tests: test_flow_uses_human_feedback_detection,
test_human_feedback_flow_uses_terminal_even_when_interactive.

Fully interactive human feedback inside the TUI remains a separate follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RBYGqJHC2TMC6fonFziuuh
Comment thread lib/cli/src/crewai_cli/run_declarative_flow.py Outdated
Comment thread lib/cli/src/crewai_cli/crew_run_tui.py
Comment thread lib/cli/src/crewai_cli/crew_run_tui.py
Comment thread lib/cli/src/crewai_cli/run_declarative_flow.py
Comment thread lib/cli/src/crewai_cli/crew_run_tui.py Outdated
Comment thread lib/cli/src/crewai_cli/run_declarative_flow.py Outdated
Comment thread lib/cli/src/crewai_cli/run_declarative_flow.py Outdated
…rew naming

Review follow-ups from @lucasgomide:

- Type flow helpers as Flow[Any] (via TYPE_CHECKING import) instead of Any and
  drop the defensive getattr chains — _definition is a typed PrivateAttr and
  name/suppress_flow_events are typed fields, so attribute access is safe.
- Replace the silent `except Exception: pass` blocks with logger.debug(...,
  exc_info=True) so unexpected failures are diagnosable in the field
  (_flow_method_types, _flow_uses_human_feedback, suppress_flow_events toggle).
- Flow-vs-crew naming: the flow worker now uses group="flow" (was the
  misleading "crew"), and the shared completion/failure handlers report the
  run with an entity-aware noun ("flow" vs "crew") via _run_noun.

Deferred (separate PR): the os._exit(130) hard-kill on user quit is kept as-is
to match the existing crew convention (run_crew._run_json_crew).

Tests: test_flow_done_uses_flow_wording_for_unfinished_tool; existing crew
wording tests unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RBYGqJHC2TMC6fonFziuuh
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