Skip to content

Add orchestration client telemetry: entry sources, decisions, pill-bar interactions#11162

Open
advait-m wants to merge 6 commits into
masterfrom
advait/orchestration-client-telemetry
Open

Add orchestration client telemetry: entry sources, decisions, pill-bar interactions#11162
advait-m wants to merge 6 commits into
masterfrom
advait/orchestration-client-telemetry

Conversation

@advait-m
Copy link
Copy Markdown
Member

@advait-m advait-m commented May 18, 2026

Loom: https://www.loom.com/share/9dc498c4adae4fd39bc9b0183b57575a
Resolves: https://linear.app/warpdotdev/issue/QUALITY-683/do-a-telemetry-pass-on-client-server

Description

Adds client-side telemetry to instrument every orchestration entry point and the run-wide config / pill-bar interactions that follow. Prior to this change, the only orchestration telemetry on the client was AgentMode.Orchestration.TeamAgentCommunicationFailed (error path only); we had no visibility into how often users entered orchestration, which surface they entered from, how they edited the configs, or how they used the pill bar. Server-side AgentMode.OrchestrateOutcome told us that the launched config diverged from the tool call, but not which fields, and there was no analog of is_planning_query for /orchestrate queries.

This PR is the client half. The server half is up at warpdotdev/warp-server#11236 — it adds is_orchestrate_query everywhere, the launched config fingerprint on OrchestrateOutcome, and parent_execution_location on StartAgentInvocation. The pair gives us closed-enum, UGC-free coverage across every orchestration surface.

Note: an earlier revision of this PR pair routed agent-driven config-creation telemetry through a server-side CreateConfigInvocation event. That event has been removed in favor of the client-side AgentMode.Orchestration.AgentProposedConfig event described below, so all plan-card telemetry is co-located client-side.

Design principles

  • Metadata-only. Every new payload is enums, IDs, booleans, and counts. No prompts, summaries, agent names, environment names, worker host slugs, or auth-secret labels are ever serialized.
  • Closed enums for free-form values. OrchestrationHarnessKind and OrchestrationExecutionModeKind bucket harness strings and execution modes into a closed set so the analytics columns stay low-cardinality even if the server adds a new harness.
  • Idempotent at the surface level. Each card instance emits at most one Entered and one Decision event (guarded by entered_event_emitted / decision_event_emitted) so re-renders don't double-count.
  • No UGC, period. Free-form fields that are arguably "server-controlled identifiers" (e.g. environment_id) are still collapsed to booleans (has_environment, had_previous, now_set) to avoid leaking workspace topology through analytics.

New events

All variants live on BlocklistOrchestrationTelemetryEvent in app/src/ai/blocklist/telemetry.rs.

AgentMode.Orchestration.PlanConfigApprovalToggled

Fires from OrchestrationConfigBlockView::handle_action when the user toggles the Use orchestration switch on a plan card. The user-driven counterpart to AgentProposedConfig (below), which captures agent-driven config snapshots.

Field Type Notes
conversation_id id
plan_id optional string omitted when empty
status approved | disapproved state after the toggle
execution_mode local | remote
harness oz | claude_code | codex | open_code | gemini | unknown
has_model, has_environment, has_worker_host, has_auth_secret bool presence-only

AgentMode.Orchestration.Entered

Fires once per orchestration entry, attributing the source. Three call sites:

  • BlocklistAIController::send_query — when the extracted UserQueryMode == Orchestrate (slash command). Emits with entry_source = slash_command_orchestrate.
  • OrchestrationConfigBlockView::handle_action — on the disapproved → approved toggle transition only. Emits with entry_source = plan_card_approved.
  • RunAgentsCardView::try_auto_launch_on_stream_complete — once per card instance when the card is actually rendered (i.e. not in the auto-launched path). Emits with entry_source = run_agents_card_shown.
Field Type
conversation_id id
plan_id optional string
entry_source slash_command_orchestrate | plan_card_approved | run_agents_card_shown

AgentMode.Orchestration.RunAgentsCardDecision

Fires once per RunAgentsCardView instance on Accept, Accept without orchestration, or Reject (guarded by decision_event_emitted).

Field Type Notes
conversation_id, plan_id id / optional string
decision accept | accept_without_orchestration | reject
agent_count usize
harness, execution_mode enums above
modified_fields_from_tool_call Vec<&'static str> names of run-wide fields that diverged from the LLM's original RunAgentsRequest. Values are stable identifiers in orchestration_modified_field::{MODEL_ID, HARNESS, EXECUTION_MODE, ENVIRONMENT_ID, WORKER_HOST, AUTH_SECRET} — the same strings the server's RunAgentsOutcome.modified_fields (RunAgentsModifiedField* in the server PR) uses so the two streams join 1:1.
modified_fields_from_active_config Vec<&'static str> same shape, compared against the approved orchestration config snapshot when one exists. Empty when no approved config exists or the dispatched config matches.
had_active_config, active_config_status bool / optional approved | disapproved

This event answers questions like:

  • How often do users accept orchestration as the LLM proposed it vs. tweak the config first?
  • Which fields do users most commonly edit before accepting?
  • How often do users hit "Accept without orchestration" to opt out of an approved plan?

To compute the per-edit diff we snapshot original_tool_call_request on every update_request call (so it always reflects the final streamed request) and diff against state.orch at decision time. See diverged_orch_fields / diverged_orch_fields_against_config in run_agents_card_view.rs.

AgentMode.Orchestration.AgentProposedConfig

Fires once per OrchestrationConfigBlockView instance when an agent-authored OrchestrationConfigSnapshot first becomes visible to the user on a plan card. Replaces the server-side CreateConfigInvocation event so all plan-card telemetry sits on the client. Server-only fields from the earlier design (validation_outcome, request_id, tool_call_id, task_id, orchestration_version, is_planning_query, is_orchestrate_query) are dropped — they require plumbing the client doesn't naturally have, and invalid configs never surface to the client anyway (no snapshot is produced).

Field Type Notes
conversation_id id
plan_id optional string omitted when empty
harness oz | claude_code | codex | open_code | gemini | unknown
execution_mode local | remote
has_model, has_environment, has_worker_host bool presence-only

AgentMode.Orchestration.PillBarInteraction

Fires from OrchestrationPillBar::handle_action via emit_pill_bar_interaction. Captures every meaningful pill-bar interaction; hover events are intentionally not tracked.

Field Type Notes
action enum switch | open_in_new_pane | open_in_new_tab | focus_opened_conversation | stop | kill | toggle_pin_on | toggle_pin_off | open_menu. switch covers all pill-body clicks (slice by switch_outcome); focus_opened_conversation is reserved for the menu-driven "Focus pane" gesture.
pill_kind orchestrator | child
total_pills, total_pinned usize resolved from the current pill_specs at emit time
source_conversation_id id the orchestrator at the root of this bar
target_conversation_id id the pill being acted on
switch_outcome optional switched_in_place | focused_existing_pane present only when action = switch; closed enum so future outcomes don't require a new action variant

To make Switch (pill click) telemeterable, the body click handler now routes through a new OrchestrationPillBarAction::PillClicked { conversation_id, pill_kind } rather than dispatching the navigation TerminalAction directly from the click closure. handle_action emits the telemetry, then reproduces the original "focus opened pane vs. switch in place" logic. Net behavior is unchanged.

All body clicks always emit a single switch event, with switch_outcome capturing the resulting navigation — "how often do users click pills?" is WHERE action = 'switch', no UNION needed. The menu-driven "Focus pane" gesture stays its own focus_opened_conversation event so the two user intents remain separable. The shared focus-existing-pane navigation lives in a private navigate_to_owner_pane helper reused by both code paths.

No labels (which can be user/agent-generated agent names) are ever sent.

Pre-existing telemetry that stays as-is

  • AgentMode.Orchestration.TeamAgentCommunicationFailed — unchanged. Still the only client-side error-path telemetry.

Cross-side coverage map

Surface Action Where it's tracked
Plan card Agent calls create_orchestration_config Client (this PR) — AgentProposedConfig (fires when the snapshot first becomes visible)
Plan card User edits / toggles approval Client (this PR) — PlanConfigApprovalToggled
Plan card Card rendered & approved Client (this PR) — Entered { entry_source: plan_card_approved }
run_agents card Tool call lands & is decided ServerOrchestrateOutcome (this PR's paired server change adds launched fingerprint + is_orchestrate_query)
run_agents card Card rendered Client (this PR) — Entered { entry_source: run_agents_card_shown }
run_agents card User accepts / rejects Client (this PR) — RunAgentsCardDecision
/orchestrate slash User submits Client (this PR) — Entered { entry_source: slash_command_orchestrate }
/orchestrate downstream Tool calls inherit attribution Serveris_orchestrate_query now present on OrchestrateOutcome and StartAgentInvocation
Pill bar User navigates between agents Client (this PR) — PillBarInteraction
start_agent Lead invokes single-agent ServerStartAgentInvocation (paired PR adds parent_execution_location + is_orchestrate_query)

Companion server PR

warpdotdev/warp-server#11236 — additive, can ship independently in either order.

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

…ill bar

Adds four new client-side BlocklistOrchestrationTelemetryEvent variants
to instrument orchestration entry points and user interactions:

* AgentMode.Orchestration.PlanConfigApprovalToggled - fires when the
  user toggles Use orchestration on a plan card. Captures the
  approval transition plus a fingerprint of the current run-wide
  config (execution mode, harness, has_model/env/host/auth booleans).

* AgentMode.Orchestration.Entered - fires once per entry into
  orchestration, attributing the source: slash_command_orchestrate,
  plan_card_approved, or run_agents_card_shown. Dispatched from the
  blocklist controller (slash command path), the plan-card config
  block (on disapproved -> approved transition), and the run_agents
  confirmation card (once per card when shown, guarded by
  entered_event_emitted).

* AgentMode.Orchestration.RunAgentsCardDecision - fires once on
  Accept, Accept-without-orchestration, or Reject. Reports
  modified_fields_from_tool_call and modified_fields_from_active_config
  using stable identifiers in orchestration_modified_field that match
  the server-side RunAgentsOutcome.modified_fields constants so the
  two streams can be joined 1:1.

* AgentMode.Orchestration.PillBarInteraction - fires on Switch
  (pill click), Open in new pane/tab, Focus opened conversation,
  Stop, Kill, TogglePinOn/Off, and OpenMenu. Pill body clicks now
  route through a new PillClicked typed action so telemetry runs
  before the navigation. Hover events are intentionally not tracked.

All payloads are metadata-only (enums, IDs, booleans, counts). No
prompts, summaries, agent names, environment names, worker host
slugs, or auth secret labels are ever serialized. Harness and
execution mode are bucketed into closed enums
(OrchestrationHarnessKind, OrchestrationExecutionModeKind) so the
analytics columns stay low-cardinality.

Co-Authored-By: Oz <oz-agent@warp.dev>
@cla-bot cla-bot Bot added the cla-signed label May 18, 2026
No behavior change. Drops doc references to sibling symbols that
would rot (e.g. "Mirrors RunAgentsExecutionMode", "see WarpUI
mouse-state guidance", "reserved badge slot when one is shown")
and collapses repetitive multi-paragraph doc comments into single-
line summaries. Verified with cargo fmt, cargo check -p warp --lib,
and cargo clippy -p warp --lib --tests --all-features -- -D warnings.

Co-Authored-By: Oz <oz-agent@warp.dev>
@advait-m advait-m marked this pull request as ready for review May 18, 2026 00:36
@oz-for-oss
Copy link
Copy Markdown
Contributor

oz-for-oss Bot commented May 18, 2026

@advait-m

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 telemetry for orchestration entry points, run_agents decisions, plan-card approval toggles, and pill-bar interactions. The payloads are metadata-only and the security pass did not find secret or UGC leakage in the changed telemetry fields.

Concerns

  • Plan-card OrchestrationEntered is not idempotent per card instance; repeated off→on toggles can overcount plan_card_approved entries.
  • Pill body clicks on conversations already open elsewhere emit both switch and focus_opened_conversation, double-counting one interaction and misclassifying the action.

Verdict

Found: 0 critical, 2 important, 0 suggestions

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/document/orchestration_config_block.rs
Comment thread app/src/ai/blocklist/agent_view/orchestration_pill_bar.rs Outdated
@advait-m advait-m requested a review from cephalonaut May 18, 2026 00:45
* PlanCardApproved Entered event was unguarded — toggling the
  approval switch off and back on re-emitted it. Add a per-view
  entered_event_emitted flag matching the RunAgentsCardView pattern
  so the first off→on transition counts; later re-toggles don't.

* Pill click on a conversation that's already open elsewhere
  emitted Switch and then redispatched FocusOpenedConversation,
  yielding two events per click. Check is_open_elsewhere first;
  only emit Switch on the in-place branch.

Co-Authored-By: Oz <oz-agent@warp.dev>
@advait-m advait-m requested a review from danielpeng2 May 18, 2026 00:58
@advait-m
Copy link
Copy Markdown
Member Author

Spoke w Daniel on slack wrt these PRs - moving AgentMode.Orchestration.CreateConfigInvocation over to client-side to have it alongside other plan card telemetry 👍

New BlocklistOrchestrationTelemetryEvent variant fired once per
OrchestrationConfigBlockView instance when an agent-authored
OrchestrationConfigSnapshot first becomes visible on a plan card.
Replaces the server-side AgentMode.Orchestration.CreateConfigInvocation
event (removed in warp-server PR #11236) to co-locate config-snapshot
telemetry with the other plan-card user-driven events.

Payload (cheap to derive client-side):
  conversation_id, plan_id, harness, execution_mode,
  has_model, has_environment, has_worker_host

Server-side-only fields (validation_outcome, request_id,
tool_call_id, task_id, orchestration_version, is_planning_query,
is_orchestrate_query) are dropped — they require plumbing the
client doesn't naturally have, and validation_outcome is moot
client-side since invalid configs never produce a visible snapshot.

Co-Authored-By: Oz <oz-agent@warp.dev>
Copy link
Copy Markdown
Member

@danielpeng2 danielpeng2 left a comment

Choose a reason for hiding this comment

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

approving to unblock. my main question is around the right places to track in the Entered event.

Comment thread app/src/ai/blocklist/telemetry.rs Outdated
Comment on lines +187 to +188
/// Plan-card `Use orchestration` switch toggled to approved.
PlanCardApproved,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is PlanCardApproved the right event to track here? What if the user toggles the state to disapproved then back to approved, would we track an Entered event again?

Feels like here we'd want to track the plan card being shown (basically the AgentProposedConfig event we're also adding).

Comment on lines +836 to +843
if is_open_elsewhere {
// FocusOpenedConversation's handler emits its own
// telemetry; skip the Switch event so one click =
// one event.
ctx.dispatch_typed_action(
&OrchestrationPillBarAction::FocusOpenedConversation(id),
);
} else {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

thoughts on whether this makes the telemetry harder to work with? might not be clear that to track all switches, we have to join FocusOpenedConversation and the Switch events

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

yeah, I originally did have it emitting both but Oz comment made me consider switching it to this 😅. I agree emitting both makes telem easier to query tho.

Let me actually simplify this and make switch have switched_in_place, focused_existing_pane as an enum field (only 2 for now, but in case we need to add more in the future - and enum is a bit easier to understand than a bool here). This better matches semantically too.

Note that this does require some logic to be extracted out to a helper function (for navigate_to_owner_pane) - doing that now.

advait-m and others added 2 commits May 17, 2026 21:28
Per @danielpeng2 review: for a binary toggle, new_status uniquely
determines previous_status (always the opposite), so carrying both
is redundant. Drops previous_status and renames new_status -> status.

The off->on transition gate that fires OrchestrationEntered still
uses local was_approved + self.is_approved booleans, so the entry
attribution logic is unchanged.

Co-Authored-By: Oz <oz-agent@warp.dev>
Per @danielpeng2 review: counting 'how often users click a pill to
navigate' previously required UNIONing 'switch' and
'focus_opened_conversation' action values, since a body click would
emit one or the other depending on whether the target was already
open elsewhere. Non-obvious for analysts and easy to undercount.

Pill-body clicks now always emit a single 'switch' action, with a
new optional 'switch_outcome' field (closed enum) capturing whether
the click navigated in place or focused an existing pane:
  - switched_in_place
  - focused_existing_pane

The 'focus_opened_conversation' action is preserved for the
distinct menu-driven 'Focus pane' gesture (3-dot menu pick) so the
two user intents stay separable.

Plumbing: extracted the existing focus-existing-pane navigation
into a private 'navigate_to_owner_pane' helper so the PillClicked
body-click path can reuse the nav without re-emitting menu-driven
telemetry. Added 'emit_pill_switch' helper for the Switch+outcome
emission so the call site stays one line.

Co-Authored-By: Oz <oz-agent@warp.dev>
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.

2 participants