feat(cli): run declarative flows on the TUI (headless terminal fallback)#6484
feat(cli): run declarative flows on the TUI (headless terminal fallback)#6484joaomdmoura wants to merge 8 commits into
Conversation
…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
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds 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. ChangesDeclarative Flow TUI Support
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
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
…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
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
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
lib/cli/src/crewai_cli/crew_run_tui.pylib/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
…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
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
There was a problem hiding this comment.
🧹 Nitpick comments (1)
lib/cli/tests/test_crew_run_tui.py (1)
1644-1667: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valuePaused-event test correctly validates the
_current_methodcontract.The
on_method_pausedhandler only calls_set_flow_step_statusand does not explicitly set_current_method— it relies on the precedingMethodExecutionStartedEventhaving set it. The test correctly emits the started event first and then asserts_current_method == "ask"after the pause, validating that_current_methodpersists across the pause transition as intended by the implementation comment ("_current_method stays pointed at it").One consideration: if a
MethodExecutionPausedEventwere ever emitted without a priorMethodExecutionStartedEvent,_current_methodwould beNone. This is unlikely in practice (a method must start before it can pause), but you may want to add a defensiveself._current_method = event.method_namein 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
📒 Files selected for processing (2)
lib/cli/src/crewai_cli/crew_run_tui.pylib/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
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ 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.
…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
…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

What
Normalizes declarative flow execution onto the
CrewRunAppTUI, matching how declarative (JSON) crews and conversational flows already run. Headless contexts keep a direct-terminal path for deploy/CI.Before this PR,
crewai runused 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
FlowStarted/MethodExecutionStarted/Finished/Failedevents), labeled with its call type (crew/agent/expression/…) read from the flow definition. Crews/agents inside a method keep streaming in the main panel.CREWAI_DMN, piped output, CI, any non-TTY) → the existing direct-terminal kickoff + printed result. Gated byis_interactive().Changes
crew_run_tui.py:_run_flow_worker(runsflow.kickoffin a thread worker, reuses_on_crew_done/_on_crew_failed+_stringify_output); an_is_flow_rungate 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(instantiatesEventListener, derives the method-type map fromflow._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 tois_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
crewai deployvalidation has a separate declarative-flow-entrypoint gap (DeployValidator._check_flow_entrypoint) unrelated to the TUI Deploy button, which usesskip_validate=True.Testing
test_crew_run_tui.py(flow dispatch, method-event STEPS build, completion sweep, end-to-endrun_test()mount) andtest_run_declarative_flow.py(TUI vs terminal routing, exit codes, deploy chaining, method-type extraction).lib/clisuite: 659 passed; the 7 remaining failures are pre-existing and unrelated (version-string drift + agit fetchmock in create/tools tests).ruff format/ruff check/mypyclean; mirroredlib/crewai/tests/clistill 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
CrewRunAppinstead of printing kickoff output on the terminal, aligning them with JSON crews and conversational flows. Routing usesis_interactive()(TTY + not DMN); flows with@human_feedbackstill use the terminal path.CrewRunAppgains a declarative-flow mode (_is_flow_run):_run_flow_workerrunsflow.kickoffin 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.pyadds_run_declarative_flow_tui(startsEventListener, forcessuppress_flow_eventsoff for the TUI, post-run Rich summary, exit codes, quit →os._exit(130), deploy chaining like JSON crews).ConsoleFormatter.print_panelskips Rich flow panels whenis_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.