Skip to content

[QUALITY-569] Stage 2 Client: OrchestrationConfig on Plan Card + Auto-Launch + Disabled Card#9927

Open
cephalonaut wants to merge 21 commits intomasterfrom
matthew/orchestration-tool-2-stage-2
Open

[QUALITY-569] Stage 2 Client: OrchestrationConfig on Plan Card + Auto-Launch + Disabled Card#9927
cephalonaut wants to merge 21 commits intomasterfrom
matthew/orchestration-tool-2-stage-2

Conversation

@cephalonaut
Copy link
Copy Markdown
Contributor

@cephalonaut cephalonaut commented May 2, 2026

Description

Adds the client-side orchestration config UI on plan cards, auto-launch for matching run_agents tool calls, and per-conversation config scoping. This builds on top of the Stage 1 confirmation card and shared controls.

Demo: https://www.loom.com/share/18e96159e91641e3b6a78924ea1b9f76

What

Plan card config block — An inline config block on plan cards with:

  • Approval toggle (pill switch) controlling whether orchestration is enabled
  • "View details" expand/collapse for Cloud/Local picker, harness, host, environment, and model dropdowns
  • Vertical full-width layout matching the Figma mock
  • Dirty-sync transport: config changes piggybacked onto the next outbound request as OrchestrationConfigUpdate

Auto-launch — When the user approves a config and the agent sends a matching run_agents tool call, the confirmation card is skipped and agents spawn immediately. Deferred to stream completion via ActionBlockedOnUserConfirmation to handle streaming timing.

Per-conversation scoping — Orchestration config moved from the AIDocumentModel singleton to per-conversation AIConversation storage (following the todo_lists pattern), preventing config from one conversation leaking into another.

Reactive config hydrationOrchestrationConfigSnapshot messages are processed inline in apply_client_action() as they arrive, rather than scanning all messages on every streaming update. Scanning is only used for conversation restore.

Architecture

  • crates/ai/src/agent/orchestration_config.rs — Rust-native types, matches_active_config() with 15 unit tests
  • app/src/ai/blocklist/inline_action/orchestration_controls.rs — Shared controls (trait-based generic pickers) used by both the confirmation card editor and the plan card config block
  • app/src/ai/document/orchestration_config_block.rs — Plan card config block view
  • Config block and document view subscribe directly to BlocklistAIHistoryEvent::OrchestrationConfigUpdated (no passthrough relay)

Testing

  • 40 unit tests for run_agents_card_view (21 new covering auto-launch/auto-deny decision logic)
  • 15 unit tests for orchestration_config (matching, proto round-trips)
  • 37 orchestration integration tests passing
  • Manual validation against the validation checklist covering plan card UI, dirty sync, per-conversation scoping, auto-launch, confirmation card, and edge cases

Server API dependencies

  • Relies on OrchestrationConfigSnapshot and OrchestrationConfigUpdate proto messages (already deployed)

Agent Mode

  • Warp Agent Mode - This PR was created via Warp's AI Agent Mode

Conversation | Plan

Co-Authored-By: Oz oz-agent@warp.dev

@cla-bot cla-bot Bot added the cla-signed label May 2, 2026
@cephalonaut cephalonaut marked this pull request as ready for review May 4, 2026 17:25
@oz-for-oss
Copy link
Copy Markdown
Contributor

oz-for-oss Bot commented May 4, 2026

@cephalonaut

I'm starting a first review of this pull request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

Copy link
Copy Markdown
Contributor

@oz-for-oss oz-for-oss Bot left a comment

Choose a reason for hiding this comment

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

Overview

This PR adds client-side orchestration configuration on plan cards, dirty-sync transport for config updates, and auto-launch/deny behavior for matching run_agents actions.

Concerns

  • The pending orchestration update is stored as a global document-model singleton and is piggybacked onto the next outbound request without checking the request's conversation, so edits in one conversation can be sent with another conversation's request.

Verdict

Found: 0 critical, 1 important, 1 suggestion

Request changes

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

Comment thread app/src/ai/blocklist/controller.rs Outdated

if let Err(e) = self.send_request_input(
// Piggyback any pending orchestration config update.
if let Some(dirty_event) = AIDocumentModel::as_ref(ctx)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ [IMPORTANT] dirty_orchestration_event is a singleton, so the next request in any conversation can carry another conversation's config update; store the source conversation_id in the dirty event and only piggyback it when it matches this request's conversation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Keyed by conversation ID for now

};
let model_handle = oc::new_standard_picker_dropdown(&colors, ctx);
model_handle.update(ctx, |d, c| d.set_use_overlay_layer(true, c));
oc::populate_model_picker(&model_handle, &display_model_id, ctx);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 [SUGGESTION] The plan-card model picker is populated once and never refreshed when LLMPreferences finishes loading; mirror the LLMPreferencesEvent::UpdatedAvailableLLMs subscription added to RunAgentsCardView so the dropdown isn't stuck empty or stale.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

@cephalonaut cephalonaut force-pushed the matthew/orchestration-tool-2 branch 2 times, most recently from 6c7be3a to 4ed836b Compare May 4, 2026 19:20
@cephalonaut cephalonaut force-pushed the matthew/orchestration-tool-2-stage-2 branch from 0a50692 to 708a42e Compare May 4, 2026 19:40
@cephalonaut cephalonaut force-pushed the matthew/orchestration-tool-2 branch from 4ed836b to ff84142 Compare May 4, 2026 19:55
@cephalonaut cephalonaut force-pushed the matthew/orchestration-tool-2-stage-2 branch from 708a42e to 502c6e7 Compare May 4, 2026 19:58
@cephalonaut cephalonaut changed the title Stage 2 Client: OrchestrationConfig on Plan Card + Auto-Launch + Disabled Card [QUALITY-569] Stage 2 Client: OrchestrationConfig on Plan Card + Auto-Launch + Disabled Card May 4, 2026
@cephalonaut cephalonaut force-pushed the matthew/orchestration-tool-2-stage-2 branch from 6266d02 to 79cbf6b Compare May 4, 2026 20:16
Base automatically changed from matthew/orchestration-tool-2 to master May 4, 2026 20:16
cephalonaut and others added 18 commits May 4, 2026 16:17
…ypes

- Add OrchestrationConfig, OrchestrationExecutionMode, OrchestrationConfigStatus
  types in crates/ai/src/agent/orchestration_config.rs with matches_active_config()
  pure function and proto round-trip conversions (15 unit tests).
- Extract shared orchestration controls into
  app/src/ai/blocklist/inline_action/orchestration_controls.rs:
  OrchestrationEditState, OrchestrationControlAction trait, generic picker
  creation/population/sync/render helpers (~720 lines).
- Refactor RunAgentsCardView to delegate to shared module (1394→850 lines).
  All 19 existing card view tests pass unchanged.

Co-Authored-By: Oz <oz-agent@warp.dev>
- Add conversation-level orchestration config fields: orchestration_config,
  orchestration_status, orchestration_plan_id, dirty_orchestration_event
- Add DirtyOrchestrationEvent struct for dirty-sync transport
- Add OrchestrationConfigUpdated event variant
- Add accessors: active_orchestration_config(), orchestration_status(),
  dirty_orchestration_event(), clear_dirty_orchestration_event()
- Add mutators: set_orchestration_config(), set_orchestration_status()
- Add hydrate_orchestration_config_from_snapshot() for server snapshot ingestion
- Handle new event variant in AIDocumentView and PlanAndTodoListView matches

Co-Authored-By: Oz <oz-agent@warp.dev>
- Create OrchestrationConfigBlockView in app/src/ai/document/orchestration_config_block.rs
  with 'Use orchestration' toggle, Cloud/Local mode selector, and all picker
  dropdowns (harness, host, environment, base model) using shared controls.
- Approved state shows full controls; disapproved state hides them.
- Embed config block in AIDocumentView render above the editor content,
  conditionally shown when AIDocumentModel has an active orchestration config.
- Subscribe to OrchestrationConfigUpdated events for re-rendering.
- Field edits and toggle changes route through AIDocumentModel.set_orchestration_config()
  which sets the dirty flag for the dirty-sync transport (Phase 4).

Co-Authored-By: Oz <oz-agent@warp.dev>
- Add AIAgentInput::OrchestrationConfigUpdate variant for piggybacking
  user config edits on outbound requests.
- Hook BlocklistAIController.send_query() to collect the dirty event from
  AIDocumentModel before send_request_input, and clear it after.
- Extend convert_input_to_user_input() to convert OrchestrationConfigUpdate
  to the proto OrchestrationConfigUpdate UserInput wire format.
- Handle new variant in all exhaustive match statements across the codebase
  (redaction, persistence, telemetry, SDK driver, view_impl, etc.).

Co-Authored-By: Oz <oz-agent@warp.dev>
Add auto-launch path: when the active orchestration config is approved
and matches the request, the card immediately dispatches
execute_run_agents() and renders in spawning state from the start
(no confirmation UI).

Add denied card: at construction, detect existing Denied results in
history and render a static disabled card with Cancelled styling.

Thread active orchestration config from AIDocumentModel through
AIBlock::ensure_run_agents_card_view() to RunAgentsCardView::new().

Co-Authored-By: Oz <oz-agent@warp.dev>
Co-Authored-By: Oz <oz-agent@warp.dev>
The Flex::column() wrapping the editor gave unbounded height to its
children. The editor's Scrollable requires a finite height constraint.
Fix by making the editor an Expanded child so it fills remaining space
after the config block with a bounded constraint.

Co-Authored-By: Oz <oz-agent@warp.dev>
- Remove unused imports OrchestrationConfig and ModelHandle in orchestration_config_block.rs
- Fix unused closure parameter ctx -> _ in controller.rs
- Replace .clone() with copy dereference for UiComponentStyles in orchestration_controls.rs

Co-Authored-By: Oz <oz-agent@warp.dev>
AIDocumentModel was missing the subscription to BlocklistAIHistoryModel
that triggers scanning for OrchestrationConfigSnapshot messages in the
conversation task list. Without this, the server-set config never
populated the model and the plan card config block never appeared.

- Subscribe to AppendedExchange, UpdatedStreamingExchange, and
  RestoredConversations events
- Scan all tasks in the conversation for the OrchestrationConfigSnapshot
  message variant
- Clone the snapshot to release the history borrow before calling
  hydrate_orchestration_config_from_snapshot with &mut ctx

Co-Authored-By: Oz <oz-agent@warp.dev>
Auto-launch was triggering unintentionally because AIDocumentModel is a
singleton that carries orchestration config across conversations. A config
set in one conversation would cause run_agents calls in unrelated
conversations to auto-launch without showing the confirmation card.

Disable auto-launch entirely (hardcode false) until the active config is
properly scoped to the owning conversation. The confirmation card flow
continues to work normally.

Co-Authored-By: Oz <oz-agent@warp.dev>
…rsation AIConversation

The orchestration config (OrchestrationConfig, OrchestrationConfigStatus,
orchestration_plan_id) was stored as singleton fields on AIDocumentModel,
a global singleton shared across all conversations. This caused stale
config from one conversation to leak into unrelated conversations,
triggering incorrect auto-launches of the RunAgents card.

Fix: move these three fields to AIConversation so each conversation owns
its own config. Update all readers (OrchestrationConfigBlockView,
AIDocumentView, ensure_run_agents_card_view in block.rs) to read from
the conversation via BlocklistAIHistoryModel instead of the singleton.

The dirty_orchestration_event field stays on AIDocumentModel since it is
about outbound sync, not conversation state.

Re-enable auto-launch in RunAgentsCardView (previously hardcoded to
false as a workaround) with an additional guard against empty configs.

Co-Authored-By: Oz <oz-agent@warp.dev>
- Add 1px horizontal divider between description and controls section
- Initialize pickers in constructor when config is already approved
- Use accent_overlay_2 for active Cloud/Local segment on plan card
- Replace text On/Off toggle with pill-shaped switch widget (36x18)

Co-Authored-By: Oz <oz-agent@warp.dev>
The model picker dropdown on the RunAgentsCardView confirmation card
was not populating because LLMPreferences loads model choices
asynchronously from the server. The picker was populated once in
ensure_pickers() but never refreshed when new models arrived.

Subscribe to LLMPreferencesEvent::UpdatedAvailableLLMs to repopulate
the model picker when available models change, matching the pattern
used by ModelSelector, AISettingsPage, and other model picker
consumers in the codebase.

Also removes the unused active_config parameter from
RunAgentsCardView::new() (auto-launch is currently disabled) and
cleans up the associated dead code at the call site.

Co-Authored-By: Oz <oz-agent@warp.dev>
…irty-sync

- Move orchestration config from AIDocumentModel singleton to per-conversation
  AIConversation storage (follows todo_lists pattern)
- Plan card UI: pill toggle, View details link, vertical pickers, full-width layout
- Confirmation card: LLMPreferences subscription for async model loading
- Model picker: use set_selected_by_name instead of set_selected_by_index
- Picker dropdowns: remove 190px max width cap, overlay layer for plan card
- Dirty-sync: preserve plan_id on user edits, default empty worker_host to warp
- Auto-launch: deferred to stream completion via ActionBlockedOnUserConfirmation
- matches_active_config: clean up after debug log removal

Co-Authored-By: Oz <oz-agent@warp.dev>
- Replace scan-all-messages with reactive OrchestrationConfigSnapshot
  handling in apply_client_action(), keeping scan only for restore path
  (now finds last snapshot, not first)
- New BlocklistAIHistoryEvent::OrchestrationConfigUpdated for reactive path
- Only clear dirty_orchestration_event on successful send (N2)
- Add OrchestrationConfigUpdate telemetry variant instead of reusing
  PassiveSuggestionResult (N1)
- Move pill toggle construction inside Hoverable closure (N6)
- Simplify matches_active_config match to tail expression (T2)
- Remove redundant function-scoped import (T3)
- Fix _app parameter name in AIDocumentView::render (T1)

Co-Authored-By: Oz <oz-agent@warp.dev>
Extract should_auto_launch() and compute_is_denied() as testable pure
functions. Tests cover: happy paths, guard conditions (already launched,
denied, spawning, editor open, empty configs), config matching (model,
harness, execution mode, field inheritance), and denied-flag computation
(history result, disapproved config, combined states).

Co-Authored-By: Oz <oz-agent@warp.dev>
- Config block view and document view subscribe directly to
  BlocklistAIHistoryEvent::OrchestrationConfigUpdated, removing the
  AIDocumentModel passthrough relay
- Remove AIDocumentModelEvent::OrchestrationConfigUpdated variant (no
  subscribers remain)
- Replace FQN paths with top-level imports in agent/mod.rs
- Remove process-internal 'Stage 1'/'Stage 2' references from comments

Co-Authored-By: Oz <oz-agent@warp.dev>
…g Figma mock

Co-Authored-By: Oz <oz-agent@warp.dev>
cephalonaut and others added 2 commits May 4, 2026 16:17
- Replace orchestration_event_streamer.rs with master's version, add OrchestrationConfigUpdated to ignore list
- Add active_config parameter back to RunAgentsCardView::new() and pass it from block.rs
- Fix output.rs: use singular open_skill_button_handle/read_from_skill_button_handle (not HashMap)
- Add missing orchestration_config imports to run_agents_card_view.rs

Co-Authored-By: Oz <oz-agent@warp.dev>
- Refactor dirty_orchestration_event from a single Option to a
  HashMap<AIConversationId, DirtyOrchestrationEvent> so switching
  conversations doesn't clobber pending config updates.
- Controller takes and clears only the matching conversation's entry;
  re-inserts on send failure to avoid silent loss.
- Remove conversation_id from DirtyOrchestrationEvent (the HashMap
  key provides the scoping).
- Subscribe OrchestrationConfigBlockView to
  LLMPreferencesEvent::UpdatedAvailableLLMs so the model picker
  refreshes when LLM preferences load asynchronously.

Co-Authored-By: Oz <oz-agent@warp.dev>
@cephalonaut cephalonaut force-pushed the matthew/orchestration-tool-2-stage-2 branch from 79cbf6b to b563051 Compare May 4, 2026 20:18
@cephalonaut cephalonaut requested a review from advait-m May 4, 2026 20:21
@cephalonaut
Copy link
Copy Markdown
Contributor Author

Advait - this is dependent on https://github.com/warpdotdev/warp-server/pull/10854 which Suraj is still reviewing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant