Skip to content

QUALITY-671: roll up orchestration credit usage in the agent-mode footer#11048

Merged
cephalonaut merged 7 commits into
masterfrom
matthew/rollup-orch-credit-usage
May 16, 2026
Merged

QUALITY-671: roll up orchestration credit usage in the agent-mode footer#11048
cephalonaut merged 7 commits into
masterfrom
matthew/rollup-orch-credit-usage

Conversation

@cephalonaut
Copy link
Copy Markdown
Contributor

@cephalonaut cephalonaut commented May 15, 2026

Description

Behind the new FeatureFlag::OrchestrationCreditRollup (dogfood-only) flag, the orchestrator's agent-mode footer now reports the orchestration's combined credit usage — orchestrator plus every locally-loaded descendant child — instead of just the orchestrator's own credits.

The expanded footer's "Credits spent (total)" row shows the rollup total and exposes a click-to-expand per-agent breakdown sorted by credits desc (spawn-order tiebreak). The first five rows render inline; any extra rows are revealed by a "Show N more" link. The collapsed pill uses the same rollup total for its headline number and its "has any usage" suppression check; the (+N) last-block annotation is unchanged. With the flag off, both surfaces render exactly as before.

Specs in this PR: specs/QUALITY-671/PRODUCT.md, specs/QUALITY-671/TECH.md.

Mocks: https://www.figma.com/design/AsF5uAM6L5tUmc11vm9YSi/Agent-orchestration?node-id=4636-32697&m=dev
Screenshots:
Screenshot 2026-05-15 at 7 25 36 PM
Screenshot 2026-05-15 at 7 25 46 PM
Screenshot 2026-05-15 at 7 25 57 PM

Key pieces:

  • app/src/ai/blocklist/usage/rollup.rs — pure aggregation helper. Walks descendants via BlocklistAIHistoryModel, filters zero-credit contributors, sorts the breakdown, returns None when nothing applies.
  • app/src/ai/blocklist/orchestration_topology.rs — descendant walker lifted out of orchestration_pill_bar.rs so the pill bar and the rollup share one traversal. The pill bar's relevant tests moved to orchestration_topology_tests.rs; avatar helpers (render_orchestrator_avatar_disc, render_agent_avatar_disc) promoted to pub(crate) so the footer rows can reuse them.
  • ConversationUsageView — new new_footer_with_rollup constructor subscribes to history events; renders the "View details" / "Hide details" toggle and the per-agent rows. Agent names use the same color as the "USAGE SUMMARY" header; per-agent credit values use the "Credits spent" label color. Names are clipped to the pill bar's max width with ellipsis. Toggles and "Show N more" use the theme's hyperlink color (ansi_fg_blue). A blank value-side placeholder opposite "Show N more" keeps the two-column flex aligned for the rows below.
  • BlocklistAIHistoryEvent::ConversationUsageMetadataUpdated — new conversation-scoped event emitted from update_conversation_cost_and_usage_for_request. The footer view and the AIBlock model subscribe so the orchestrator's pill and footer stay live when any descendant spends credits.
  • terminal/view.rs::handle_usage_footer_toggled — routes through the rollup-aware constructor when the flag is enabled, and uses add_typed_action_view so the view's TypedActionView handler is actually registered with the framework (without this, the toggle / show-more clicks were silently dropped).
  • output.rs::render_usage_button — collapsed pill computes the rollup behind the flag and feeds total_credits into the headline number + suppression check.

Linked Issue

QUALITY-671

Testing

  • app/src/ai/blocklist/usage/rollup_tests.rs — 8 unit tests covering the aggregation contract: no-descendants → None, sum + sort across descendants, zero-credit filter, transitive grandchildren, spawn-order tie-break, unloaded-descendant skip, six-contributor truncation precondition, all-zero short-circuit.
  • app/src/ai/blocklist/orchestration_topology_tests.rs — tree-walker correctness tests moved over from the pill bar.
  • app/src/ai/blocklist/usage/conversation_usage_view_tests.rs — handler-level tests that stand the view up via add_typed_action_view and verify ToggleDetailsExpanded / ShowAllAgentRows flip the right state (regression guard against the original "dispatched action has no handlers" bug).
  • cargo fmt --check, cargo clippy --workspace --exclude command-signatures-v2 --all-targets --all-features --tests -- -D warnings, and ./script/presubmit all clean.
  • I have manually tested my changes locally with ./script/run

Agent Mode

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

CHANGELOG-IMPROVEMENT: Roll up child-agent credit usage into the orchestrator's agent-mode footer.


Conversation

Aggregate credits spent by an orchestrator's descendant child agents into
the parent's agent-mode footer and collapsed pill, behind the new
`FeatureFlag::OrchestrationCreditRollup` dogfood flag.

Highlights:
- New `compute_orchestration_rollup` helper produces per-agent breakdown
  rows (orchestrator first, then descendants by spawn order) and a total
  credits figure aggregated across the locally-loaded conversation tree.
- Lift the existing descendant walker into shared
  `app/src/ai/blocklist/orchestration_topology.rs` reused by the rollup
  and the pill bar.
- Extend `ConversationUsageView` with a "View details" / "Hide details"
  toggle and per-agent breakdown rows (first 5 + "Show N more"). Reuse
  the pill bar's avatar helpers for row icons.
- Emit `BlocklistAIHistoryEvent::ConversationUsageMetadataUpdated` from
  the usage-metadata update path so the footer and parent collapsed pill
  re-render live when any descendant spends credits.
- Wire the rollup into the expanded footer constructor and the collapsed
  pill `render_usage_button` (rollup total drives the headline credit
  number + suppression check; the existing `(+N)` annotation is
  unchanged).

Includes specs at `specs/QUALITY-671/`.

Co-Authored-By: Oz <oz-agent@warp.dev>
@cla-bot cla-bot Bot added the cla-signed label May 15, 2026
cephalonaut and others added 3 commits May 15, 2026 18:18
The usage footer was created via ctx.add_view in handle_usage_footer_toggled,
which does not register the view's TypedActionView handler with the framework.
Clicks on "View details" / "Show N more" dispatched
ConversationUsageViewAction::* via ctx.dispatch_typed_action, but the framework
logged "Dispatched action has no handlers" and silently dropped the action.

Switch to ctx.add_typed_action_view so ConversationUsageView::handle_action
runs as expected and the toggle/expand state flips on click.

Add conversation_usage_view_tests.rs to lock in the handler logic — exercises
ToggleDetailsExpanded and ShowAllAgentRows directly through view.update so a
future regression in either handler arm (or the TypedActionView impl itself)
fails at test time.

Co-Authored-By: Oz <oz-agent@warp.dev>
…toggle as a hyperlink

Two visual fixes for the orchestration credit rollup:

1. The per-agent breakdown rows were being appended at the end of the
   usage card (after "LAST RESPONSE TIME"), so they read as a disconnected
   section instead of as a drill-down of "Credits spent (total)".
   Refactor the rollup-row insertion into a small helper and call it
   immediately after pushing the Credits spent (last response) / total
   pair, so the rows sit directly beneath the value they elaborate on.

2. The "View details" / "Hide details" toggle and the "Show N more" link
   were rendered in the sub-text color, which reads as a passive label
   rather than a clickable affordance. Switch both to theme.ansi_fg_blue()
   - the same color FormattedTextElement uses for in-line hyperlinks
   throughout Agent Mode (see PromptAlertView). The Hoverable's
   PointingHand cursor remains the on-hover affordance; we don't flip the
   color or weight on hover because Text doesn't expose an underline knob
   and changing color here tends to push the link into the accent space.

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

Visual tweaks for the orchestration credit rollup breakdown:

1. Agent names now render in the same color as the "USAGE SUMMARY" header
   (text_disabled) so the rollup rows read as a sub-list of that section
   rather than competing with primary labels.

2. Per-agent credit values now render in the "Credits spent" label color
   (text_sub) instead of the primary value color, visually echoing the
   label they elaborate on.

3. Agent names are clipped at PER_AGENT_LABEL_MAX_WIDTH (= the pill bar's
   PILL_LABEL_MAX_WIDTH) with ellipsis, so long child-agent names can't
   push the credit-value column off-screen.

4. "Show N more" was breaking the two-column flex alignment because the
   value-side slot was an Empty (0 height) while the label side carried
   a real Text element. Subsequent rows then drifted out of step with
   their labels. Push a single-space Text placeholder opposite the link
   so the columns stay row-aligned through the rest of the card.

Co-Authored-By: Oz <oz-agent@warp.dev>
@cephalonaut cephalonaut changed the title QUALITY-671: roll up orchestration credit usage in agent-mode footer QUALITY-671: roll up orchestration credit usage in the agent-mode footer May 15, 2026
@cephalonaut cephalonaut requested a review from advait-m May 15, 2026 23:27
@cephalonaut cephalonaut marked this pull request as ready for review May 15, 2026 23:27
@oz-for-oss
Copy link
Copy Markdown
Contributor

oz-for-oss Bot commented May 15, 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

@cephalonaut cephalonaut removed the request for review from advait-m May 15, 2026 23:28
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 a feature-flagged orchestration credit rollup for the agent-mode footer and collapsed usage pill, with shared topology traversal, aggregation helpers, and footer UI state for the breakdown.

Concerns

  • The rollup currently activates when only the orchestrator has credits and all descendants are idle, which contradicts the intended descendant-credit gating and adds a misleading one-row breakdown.
  • The new link-style controls are implemented as mouse-only hover/click elements, so the required keyboard activation path for View details and Show N more is missing.

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

}
}

if entries.is_empty() {
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] This only checks whether any agent has credits, so an orchestrator with credits plus idle children returns a rollup and renders a "View details" list containing only the orchestrator. The feature is supposed to stay in the existing self-only UI until at least one loaded descendant has spent credits; track descendant credit contributors separately and return None when that count is zero.

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.

Children don't really remain idle, so not really a big deal.

} else {
("View details", Icon::ChevronDown)
};
Hoverable::new(
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] Hoverable plus .on_click makes the View/Hide details control mouse-only, and the Show N more link below uses the same pattern. Use a focusable/button-style element or add keyboard handling so both controls are reachable in tab order and activatable with Enter/Space as required.

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.

The rest of the usage box is not accessible by keyboard, so going to defer this one.

The rollup is self-gating: compute_orchestration_rollup returns None when
the orchestrator has no locally-loaded descendants or no credits, so the
expanded footer and collapsed pill naturally fall through to today's
exact behavior whenever the data isn't there. The dedicated flag was
redundant on top of FeatureFlag::OrchestrationV2 (which gates whether a
user can create child agents in the first place) and FeatureFlag::AgentView
(which gates the expanded footer surface), so remove it and update
PRODUCT.md / TECH.md to reflect the self-gating contract.

Co-Authored-By: Oz <oz-agent@warp.dev>
// (invariant 9), or a new descendant was spawned
// (invariant 8). Trigger a re-render; the actual rollup
// is recomputed from the history model in `render`.
ctx.notify();
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.

Wondering if it's possible to restrict this to certain conversation events rather than any conversation event across the app?

i.e. this type of thing

let parent_id = parent_conversation_id;
ctx.subscribe_to_model(&history_model, move |_, history, event, ctx| {
    let touched_id = match event {
        BlocklistAIHistoryEvent::ConversationUsageMetadataUpdated { conversation_id }
        | BlocklistAIHistoryEvent::RemoveConversation { conversation_id, .. }
        | BlocklistAIHistoryEvent::DeletedConversation { conversation_id, .. } => *conversation_id,
        _ => return,
    };
    // history.read takes (&AppContext, F) — collect the bool before notifying
    // so the immutable borrow ends before ctx.notify() needs &mut.
    let should_notify = history.read(ctx, |h, _| {
        touched_id == parent_id
            || descendant_conversation_ids_in_spawn_order(h, parent_id).contains(&touched_id)
    });
    if should_notify {
        ctx.notify();
    }
});

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.

That's good idea - fixed.

@advait-m
Copy link
Copy Markdown
Member

this looks sweet!

cephalonaut and others added 2 commits May 15, 2026 21:08
The footer's history subscription previously called ctx.notify() on
every ConversationUsageMetadataUpdated, RemoveConversation,
DeletedConversation, and StartedNewConversation event in the app — one
terminal view's typing storm would fan out and re-render every other
orchestrator's footer. Walk the orchestrator's descendant index and
notify only when the touched conversation is the orchestrator itself or
one of its locally-known descendants. Drop the StartedNewConversation
arm entirely: a freshly spawned descendant always has zero credits, so
the rollup result is unchanged until its first
ConversationUsageMetadataUpdated event (which this filter then picks
up).

The walk reads children_by_parent, which is not cleaned up on
RemoveConversation/DeletedConversation; just-pruned descendants are
still listed, so notify still fires and the render-time rollup correctly
drops their row via the loaded-descendants filter.

Addresses code review comment from @advait-m on PR #11048.

Co-Authored-By: Oz <oz-agent@warp.dev>
- Replace fully-qualified compute_orchestration_rollup() call in
  render_usage_button with a file-level import.
- Add the missing blank line between fn rollup and
  fn collect_models_by_category in ConversationUsageView.
- Extract PER_AGENT_BREAKDOWN_TRUNCATION_CAP for the truncation cap
  in append_per_agent_rows so the magic 5 isn't repeated.

Co-Authored-By: Oz <oz-agent@warp.dev>
@cephalonaut cephalonaut merged commit 131762b into master May 16, 2026
25 checks passed
@cephalonaut cephalonaut deleted the matthew/rollup-orch-credit-usage branch May 16, 2026 03:37
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