Skip to content

Name the model in use in the warping row (APP-5532) - #15323

Open
warp-agent-staging[bot] wants to merge 7 commits into
masterfrom
factory/app-5532-warping-model-display
Open

Name the model in use in the warping row (APP-5532)#15323
warp-agent-staging[bot] wants to merge 7 commits into
masterfrom
factory/app-5532-warping-model-display

Conversation

@warp-agent-staging

@warp-agent-staging warp-agent-staging Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Description

The server already reports which model a response is running on while that response is still streaming: LLMAgent.sendModelUsed sends a Message.ModelUsed on the first LLM attempt of every primary-agent turn, and again on each retry or fallback. The client stored it in output.model_info but only ever displayed the name when the model was a fallback, so in the ordinary case the row shimmered Warping... for the whole response even though the model was known.

The agent status row now names whatever model the current exchange reported — Warping with Claude Sonnet 4.5... — and keeps the generic copy until a model is reported, which is exactly the window where auto and custom model routers have not resolved one yet.

This generalizes the existing fallback message instead of adding a second mechanism beside it. resolve_fallback_warping_message becomes resolve_warping_model_message over a small ModelInUse / WarpingModelMessage pair, and the fallback path keeps both of its extras behind the flag it already had, FallbackModelLoadOutputMessaging: the one-exchange lookback that avoids a flicker on agent-initiated follow-ups, and the The primary model (X) failed… explanation line. Nothing outside that path borrows another exchange's model, so an exchange that has not reported yet shows the generic copy rather than a name that may not be the one running.

Which messages name the model

The name is not limited to the generic slot. Every message in the row that represents a model producing output carries it, via one helper, status_message_naming_model, which inserts the name ahead of the trailing ellipsis:

  • Warping with {model}...
  • Generating plan with {model}..., Updating plan with {model}...
  • Generating fix with {model}..., Creating diff with {model}...
  • Preparing question with {model}..., Adjusting tasks with {model}...

Messages for phases where no model is working stay unnamed, because naming one there would be a claim we cannot support: Executing command..., Writing command input..., Waiting for command to exit..., Agent waiting for instructions..., Searching codebase..., Grepping..., Finding files..., Reading files..., Calling "X" MCP tool..., Reading "X" MCP resource..., Searching the web..., and the cloud Setting up environment.

Summarizing conversation... and Summarizing command output... are excluded deliberately, even though they are LLM calls. LLMAgent.SummarizeConversation never calls sendModelUsed, and that work bills to separate categories (UsageCategoryCompaction, UsageCategoryToolSummarization); the model doing it is resolved server-side and never reported to the client, so the only name available is the primary agent's, which is not the model summarizing. Naming summarization would need a server change to report its model first.

Copy: two rules, on purpose

Named messages end in the row's trailing ellipsis. The pre-existing fallback message keeps its full stop — Warping with Claude Haiku 4.5. — because it ships today and the requester asked for it to be left alone. Both rules sit next to each other in warping_model_message with a comment saying why, and a test asserts them side by side so neither drifts into the other.

Gating

New FeatureFlag::WarpingModelName (cargo feature warping_model_name), enabled in DOGFOOD_FLAGS and not in default. Rolling it out to everyone is adding warping_model_name to default in app/Cargo.toml.

The split between the two flags follows "do we know the model": WarpingModelName decides whether the row names a model at all, and FallbackModelLoadOutputMessaging keeps only the two things specific to a fallback attempt — the explanation line and the previous-exchange lookback. So a fallback model is named whenever naming is on, rather than losing its name the day the older flag is cleaned up, and the shipped configuration (naming off, fallback messaging on) behaves exactly as it does today, down to the full stop.

Scope

Naming the model for non-primary agents is out of scope: the server still restricts ModelUsed to AgentId_PRIMARY (shouldSendModelUsedNotification, TODO(QUALITY-395)), so CLI / full-terminal-use, computer use, advice and conversation-search exchanges report nothing and correctly fall back to the generic copy. Per-turn usage and cost stay out of scope; that is DES-819's.

This is the GUI desktop app only. The TUI has its own indicator at crates/warp_tui/src/warping_indicator.rs, which shares none of these constants, so the CLI's row keeps reading Warping... unnamed. That is a decision for this PR, not an oversight; extending it there is separate work.

Linked Issue

APP-5532 — tracked in Linear, not as a GitHub issue, so the label checkbox below does not apply.

  • The linked issue is labeled ready-to-spec or ready-to-implement.
  • Where appropriate, screenshots or a short video of the implementation are included below (especially for user-visible or UI changes).

Testing

15 unit tests in app/src/ai/blocklist/block/status_bar_tests.rs and 2 in app/src/ai/blocklist/block/view_impl/common_tests.rs, covering: reading a reported model off the exchange output; an empty display name treated as no name (which master rendered as the literal Warping with .); nothing reported yet, i.e. the auto / custom-router pre-resolution window; a reported model; both feature flags in each combination, including today's shipped configuration; the fallback lookback and its suppression after a new user query; an ordinary model never borrowed from the previous exchange; the two copy rules side by side; and the naming helper's ellipsis handling.

All green on 490354c:

  • cargo nextest run --no-fail-fast -p warp -E 'test(/^ai::blocklist::block::status_bar::tests::/) or test(/^ai::blocklist::block::view_impl::common::tests::/)'32 tests run: 32 passed, 6446 skipped.
  • ./script/format — no diff.
  • cargo clippy -p warp -p warp_features --all-targets -- -D warnings — zero warnings.
  • cargo clippy --workspace --exclude warp_completer --all-targets --tests -- -D warnings — zero warnings. Worth running because this adds a FeatureFlag variant, which a two-package clippy would not catch against an exhaustive match elsewhere.
  • cargo check -p warp --features warping_model_name — clean, so the cargo-feature wiring compiles.

Red-green on the copy rule: making status_message_naming_model return format!("{message} with {model_display_name}") — i.e. dropping the ellipsis handling — fails four tests, including the one that pins the two rules together, and leaves all three fallback-copy tests green, which is what proves the fallback path really does bypass the helper:

left: "Warping... with Claude Sonnet 4.5"        right: "Warping with Claude Sonnet 4.5..."
left: "Generating plan... with Claude Sonnet 4.5" right: "Generating plan with Claude Sonnet 4.5..."

Red-green on the per-message naming: nothing failed, and that is worth stating rather than glossing. Reverting all six naming_model(...) call sites in render_warping_indicator to the plain constants leaves the suite at 32/32. render_warping_indicator takes an &AppContext and returns a Box<dyn Element>, so reaching it means building a GUI element tree — integration-test territory, not unit-test territory. A revert that also removed the now-unused prop would be caught by nothing at all; a partial revert is caught only by clippy's dead-code lint on the orphaned field, which is not behavioral coverage. If that is not good enough, the fix is extracting the message-selection chain into a pure function a test can drive, which this PR does not do.

Be precise about what the captures do and do not show, too. A three-turn take can only exercise the phases those turns hit: Warping with {model}... and Generating plan with {model}... are on screen and measured. Updating plan..., Generating fix..., Creating diff... and Preparing question... appear in neither a test nor a capture — two of the six naming sites have visual evidence and four rest on the reviewed list. The gate that decides whether a name is available at all is unit-tested; the per-message wiring is what is uncovered.

The flag gate on the propagated name is pinned per guard, not just in aggregate. Reverting model_display_name to the ungated value fails exactly two tests — the shipped-configuration one and the lookback one — with text and show_fallback_explanation identical on both sides, so the assertions fail purely on the leaked name. Dropping each half of the conjunction on its own fails exactly one test each: removing the naming-flag guard fails only the shipped-configuration test, removing the provenance guard fails only the lookback test. Neither guard is riding on the other.

  • I have manually tested my changes locally with ./script/run

Unticked because it was not local: the app was built from this branch and driven through real agent-mode responses on a Linux cloud runner, since ./script/run there needs the private warp-channel-config generator and silently downgrades to warp-oss without it. Everything under Screenshots / Videos is from that running build.

Screenshots / Videos

All of these are the running desktop app on a Linux runner (Xvfb, lavapipe software rendering). AFTER is this branch; BEFORE is its parent commit 27f8ee6c, i.e. master, which needed its own build because there is no runtime off-switch for the flag.

Videos

Screenshots

Not exercised visually: a custom model router (this staging account has none configured) and a genuine fallback/retry, which cannot be forced from the client. Those two paths rest on the unit tests.

Measured behavior

From the 1080p capture, read frame by frame (2430 frames decoded, status-row band cropped and thresholded, one frame transcribed per run; boundaries exact to the 0.25s frame interval):

  • Turn 1, claude sonnet 4.5 (thinking): 5.75s Warping..., then 41.00s of Warping with claude sonnet 4.5 (thinking)...
  • Turn 2, auto (genius), resolved to claude opus 5 (max): 3.75s generic, then 186.00s continuous naming the resolved model
  • Turn 3, gpt-5.5 (high) via /plan: 2.25s generic → 20.50s named → 2.75s Searching the web..., correctly unnamed → 12.25s named → 28.25s of Generating plan with gpt-5.5 (high)... → 2.50s generic → 5.25s named

Every named string ended in the ellipsis; no frame showed the fallback's single period, which is the other copy rule staying where it belongs.

How long the name is on screen tracks how long the call takes. On these long turns it is continuously readable. On very short turns it is a flicker: in the 61s clip, across six LLM calls the row named a model in two of them for about half a second each. That is the intended consequence of naming only what the current exchange reported — an agent-initiated follow-up has not reported yet, and the row will not borrow the previous exchange's model. master shows generic copy for those same stretches, so nothing regressed.

Decided: always accurate. The requester chose that with the measurements in hand — "let's opt for always accurate because the end-user's use-case is only an issue for long-running turns". Holding the last known name would have been wrong in a case these captures actually contain: one turn where the row named claude haiku 4.5 for the call that composed a command and claude opus 5 (high) for the call that composed the answer.

One sub-3-second transient, not chased: between the plan phase ending and the wrap-up generation the row dropped to Warping... for 2.50s before naming the model again, which is the next attempt starting before its ModelUsed arrives — the documented unnamed case. No phase was ever mis-named.

Decided: ship this timing as-is. Four of the six named messages — Updating plan, Generating plan, Creating diff, Preparing question — are chosen from what the output already contains rather than from an in-flight call, so Generating plan with {model}... can stay on screen after that model has finished its part. The requester was asked whether the naming should be gated on stream liveness and chose not to: "I think shipping as-is is fine". Recording it so a later reader sees a decision rather than an oversight.

Agent Mode

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

@cla-bot

cla-bot Bot commented Aug 19, 2026

Copy link
Copy Markdown

Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: Wilson Factory.
This is most likely caused by a git client misconfiguration; please make sure to:

  1. check if your git client is configured with an email to sign commits git config --list | grep email
  2. If not, set it up using git config --global user.email email@example.com
  3. Make sure that the git commit email is configured in your GitHub account settings, see https://github.com/settings/emails

@warp-agent-staging

Copy link
Copy Markdown
Contributor Author

This PR was generated with Warp.

Comment @warp-factory on this PR to send it follow-up work.

View run View conversation

The server already reports the model a response is running on mid-stream, as
a ModelUsed message on the first LLM attempt, but the client only ever showed
that name when the model was a fallback. Generalize the fallback message so
the warping row names whatever model the exchange reported, behind the new
WarpingModelName flag, and keep the fallback path's lookback and explanation
line behind the flag they already had.

APP-5532
@warp-agent-staging
warp-agent-staging Bot force-pushed the factory/app-5532-warping-model-display branch from 61a4a4d to ef4a225 Compare August 19, 2026 19:05
@cla-bot cla-bot Bot added the cla-signed label Aug 19, 2026

@Xavientois Xavientois left a comment

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.

Code looks good. Waiting for screenshots

@Xavientois
Xavientois marked this pull request as ready for review August 19, 2026 19:13

@warp-agent-staging warp-agent-staging Bot left a comment

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.

Overview

The warping row now names the model the current exchange reported via ModelUsed, keeping today's generic text until one arrives, behind the new dogfood-gated WarpingModelName. No correctness, security, or standards defect was found in the change; the two items below need a human decision rather than a code fix.

Concerns

  • Per-request flicker, and the shimmer restart that comes with it. Because nothing is borrowed from a previous exchange, the row now goes Warping... -> Warping with X. on every request, and each text change restarts the shimmer from zero (crates/warpui_core/src/elements/gui/shimmering_text.rs:193-204); across a tool-call cycle it reads Warping with X. -> Executing command... -> Warping... -> Warping with X.. Never showing a stale name is the right side of that trade, and the copy and gating are one site, so this is cheap to adjust. It is still a user-visible copy change on the live warping row that specs/CODE-1828/TECH.md section 4 deliberately kept clear, so it wants a decision on DES-819 and a design reviewer on this PR.
  • Untouched code: the ModelUsed ingest targets the last exchange, not the message's task_id. app/src/ai/agent/conversation.rs:3029-3034 resolves the target as added_exchanges_by_response[stream].last(), so once a stream has lazily appended a subagent exchange, a later primary-agent ModelUsed lands on the subagent's exchange and that block's row would name the wrong model. Pre-existing and out of scope here, but this change raises its visible cost from the fallback path alone to any conversation; the fix is to match the AddedExchange whose task_id equals the message's, and QUALITY-395 is the existing home for it.

Verdict

Checks: build pass (CI formatting + clippy green on Linux, macOS, Windows and wasm; release-flag compilation green), tests pass locally 11/11 with a red-green demonstration but CI test jobs still pending, CI green for the jobs that have run, visual proof missing - captures of the running UI are in progress and are required before merge on a user-facing change.

Found: 0 critical, 0 important, 0 suggestions, 0 nits, 2 questions for human decision. The review's remaining findings (1 important, 2 suggestions, 2 nits) are being addressed by the author and are not listed here.

A fallback attempt is still the model in use, so naming it no longer depends
on FallbackModelLoadOutputMessaging surviving; that flag now only controls the
explanation line and the previous-exchange lookback. Renames the message's
is_fallback to show_fallback_explanation, which is what it actually drives.

Also covers the OutputModelInfo -> ModelInUse conversion, including the empty
display name that master rendered as "Warping with .", drops the routing
sequence test whose cases its siblings already reach, and trims the unused
Clone/Eq derives.
describes_an_unnamed_fallback_model_generically now builds its input through
ModelInUse::from instead of by hand, so the empty-name normalization is covered
by what it renders ("Warping with ." without it) rather than only by the
conversion's output.
…ellipsis

The row's named copy now ends in the same trailing ellipsis as its generic
copy, and the name reaches the other messages that are a model working:
Generating plan, Updating plan, Generating fix, Creating diff, Preparing
question, Adjusting tasks. One helper owns that copy, so the generic slot and
the rest read the same way.

Phases that are not a model working stay unnamed - executing or monitoring a
command, searching the codebase, grep, file glob, MCP calls, web search,
waiting for input. Summarization is excluded too: it is an LLM call, but the
server resolves it to a separately chosen model and never reports that model
to the client, so naming the exchange's model there would attribute the work
to a model that is not doing it.
The fallback copy ships today and the requester wants it left alone, so the
two rules now sit side by side at the one site that owns the copy, with a
comment saying why, and a test asserting both so neither drifts into the
other. An unnamed model with no fallback message to fall back on keeps the
generic copy rather than borrowing the fallback's wording.
The lookback test borrows the previous exchange's fallback model, so it is the
fallback message and ends in a period. Its two siblings were changed back and
this one was not, which left the branch head red.

@warp-agent-staging warp-agent-staging Bot left a comment

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.

Overview

Second pass over the copy change and the extension of model naming to the other status messages. One defect went back to the author; the item below is a product decision rather than a code fix, and it is the last thing outstanding from review.

Concerns

  • The named messages are accurate about whose output it is, not about what is happening this instant. Four of the six named messages key off properties of accumulated output rather than stream liveness — contains_create_document_action / contains_update_document_action match any message in the output, so once a plan action appears the message latches for the rest of the exchange, and is_last_message_requesting_file_edits stays true while the client turns the action into a diff. So Generating plan with gpt-5.5 (high)... can remain on screen while that model is idle and the client is applying the plan. Under the reading the copy makes — that model produced this content — all six are correct, and this over-persistence is pre-existing on master for these messages and for the generic row, so the change does not introduce it.
  • Tightening it is available but not free. props.model.status(app).is_streaming() is already used a few lines below in the same function and would confine every name to a live stream. It would also strip the name from the shipped fallback message during post-stream windows where master shows it today, which is a change to existing behaviour rather than only to the new naming. Worth an explicit decision either way; the current behaviour is defensible and shipping it as-is is a valid answer.

Verdict

Checks: build pass, tests pass 32/32 with format, both clippy lines and the feature check green on a6c3663, CI green, visual proof present — 293s of named row across three long turns, including 28.25s of Generating plan with gpt-5.5 (high).... Note that two of the six naming sites have visual evidence and four have neither test nor capture; the approved named/unnamed list rests on review, and pinning it means extracting the message-selection chain from the render path, which belongs in its own PR.

Found: 0 critical, 0 important, 0 suggestions, 0 nits, 1 question for decision. The remaining findings from this pass (1 important, 1 suggestion, 3 nits) went to the author and are not listed here.

…hange

Review caught a leak: the name handed to the other status messages was lifted
from the message struct with no flag check, so on the shipped configuration -
naming off, fallback messaging in default - a fallback exchange would have
rendered "Generating plan with Claude Haiku 4.5..." where master renders
"Generating plan...". The lookback's borrowed name reached them the same way.

The name now travels only when WarpingModelName is on and the model came from
the exchange being rendered. Two tests pin it, one per path, and the shipped
configuration test no longer encodes the leak it was meant to catch.

Also derives the fallback copy's stem from LOAD_OUTPUT_MESSAGE so a change to
the row's verb cannot leave it behind, renames the local to is_fallback_message
now that it decides three things, and says what to do with the fallback branch
when the legacy flag is cleaned up.
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