[multi-team P3d] Scope LLM lookups and context-window limits to the window's team - #15561
Open
warp-agent-staging[bot] wants to merge 12 commits into
Open
[multi-team P3d] Scope LLM lookups and context-window limits to the window's team#15561warp-agent-staging[bot] wants to merge 12 commits into
warp-agent-staging[bot] wants to merge 12 commits into
Conversation
2 tasks
Adds Team.feature_model_choice and Workspace.feature_model_choice, sourced from the server's per-team/workspace Team.featureModelChoice (new teams[].featureModelChoice query field, schema mirror line) and the existing Workspace.featureModelChoice, via gql_convert's new feature_model_choice_from_gql. Persisted through new feature_model_choice_json sqlite columns (migration included) so the warm-start cache round-trips it too. Nothing reads this yet -- LLMPreferences keeps master's flat models_by_feature and behaves exactly as today. This PR is inert by design: dead data plus a conversion test (team_feature_model_choices_conversion_keeps_each_teams_choice_distinct) proving each team keeps its own payload through the fold.
warp-agent-staging
Bot
force-pushed
the
factory/multi-team-pr3a-catalog-team-scope
branch
from
August 26, 2026 06:02
1d8e82a to
fc3d9d1
Compare
Seeds Team/Workspace.feature_model_choice from the legacy, pre-team-keyed MODELS_BY_FEATURE_CACHE_KEY cache as a one-release migration in UserWorkspaces::new: a Workspace restored from the SQLite cache predates the feature_model_choice column (or the app hasn't fetched since upgrading), so its catalog is still the bare default, and this seeds it from the last-known model list instead so an offline launch right after upgrading isn't stuck on just the auto default for the rest of the offline session. migrate_legacy_feature_model_choices_cache understands both real shapes an older client could have written: the (more recent) single ModelsByFeature, and (older still) a bare AvailableLLMs, which becomes the agent_mode field. Still nothing reads Team/Workspace.feature_model_choice for model selection -- LLMPreferences keeps master's flat models_by_feature. legacy_cache_migration_yields_a_usable_teamless_catalog proves the older bare-AvailableLLMs shape migrates to a catalog whose agent_mode bucket resolves the cached model.
Deletes LLMPreferences's flat, non-team-aware models_by_feature field and its MODELS_BY_FEATURE_CACHE_KEY read/write, and the old WorkspacesMetadataResponse.feature_model_choices field PR 1 left alone. Every accessor now reads through UserWorkspaces instead, resolved against a caller-supplied TeamScope (or a raw team uid): - UserWorkspaces gains feature_model_choice_for_scope/_for_team_uid (reads, via the same scoped_or_workspace_setting pattern every other team-scoped setting uses; the absent/no-workspace-at-all case falls back to the resolved-teamless `pre_login_models_by_feature` seeded by PR 1/PR 2's migration or a pre-login fetch) and set_feature_model_choice_for_team_uid (the narrow one-off authed-refresh write path). - get_llm_info/_for_team_uid/_for_scope, get_active_base_model, and the various choice/default getters gained an app/ctx parameter and a TeamScope parameter accordingly, with the ~85 call sites across the app updated: terminal/, settings_view/, ai/blocklist/, ai/agent_sdk/, pane_group/, search/, workspace/, and crates/warp_tui/. - Preserves the earlier review-round behavioral fixes carried since PR 1's first draft: teamless eager fetch for agent_mode_evals builds, refresh-through-workspace-metadata (not the unscoped get_feature_model_choices) for blocklist triggers, cross-team get_llm_info display lookups, and stale-bucket eviction (implied by the wholesale Team/Workspace replacement on every response). This is the bulk of the three-PR stack, but every design decision here -- the storage shape, the legacy-cache conversion -- was already reviewed in PR 1 (#15463) and PR 2 (#15565); this should read as fan-out from those two, not new design.
Adds LLMPreferences::get_llm_info_for_scope/_for_team_uid for callers that already have a team scope and need policy-affecting metadata (disable_reason, host_configs), and migrates the Bedrock login banner (terminal/view.rs), Grok/GEAP routing checks (response_stream.rs), and the model picker (profile_model_selector.rs) to it. Threads the caller's live TeamScope through AIExecutionProfileAppExt's context-window helpers (configurable_context_window, context_window_display_value, context_window_limit_for_request, should_show_long_context_pricing_warning) and their execution_profiles/settings_view call sites, and adds RequestParams::team_uid so ai/agent/api.rs's request-limit clamp reads the same team's context-window range the request was built against. Builds on PR1's Team/Workspace-based catalog placement (rather than the earlier team-keyed LLMPreferences cache); the scoped lookups added here read through the same UserWorkspaces-backed accessors. Comes with its own tests: the two-team context-window clamp regression, and the scoped-lookup assertions in the catalog distinctness/pruning test.
warp-agent-staging
Bot
force-pushed
the
factory/multi-team-pr3b-scoped-llm-lookups
branch
from
August 26, 2026 07:07
68bf503 to
a2e36c1
Compare
warp-agent-staging
Bot
changed the base branch from
factory/multi-team-pr3a-catalog-team-scope
to
factory/multi-team-pr3c-move-llm-preferences
August 26, 2026 07:07
… home (#15565) ## Description Stacked on #15463 (PR 1 of the three-PR stack — "Parse and store the team catalog into UserWorkspaces"). PR 1 added `Team`/`Workspace.feature_model_choice` but nothing populates it for a user restored from a pre-upgrade SQLite cache. PR 3 (to follow, stacked on this branch) moves `LLMPreferences` off its flat catalog onto these fields; this PR makes sure a warm-started, pre-upgrade cache has something real to read once that happens. Seeds `Team`/`Workspace.feature_model_choice` from the legacy, pre-team-keyed `MODELS_BY_FEATURE_CACHE_KEY` cache as a one-release migration in `UserWorkspaces::new`: a `Workspace` restored from the SQLite cache predates the `feature_model_choice` column (or the app hasn't fetched since upgrading), so its catalog is still the bare default. This seeds it from the last-known model list instead, so an offline launch right after upgrading isn't stuck on just the `auto` default for the rest of the offline session. `migrate_legacy_feature_model_choices_cache` understands both real shapes an older client could have written: the (more recent) single `ModelsByFeature`, and (older still) a bare `AvailableLLMs`, which becomes the `agent_mode` field. Still nothing reads `Team`/`Workspace.feature_model_choice` for model selection — `LLMPreferences` keeps master's flat `models_by_feature`. `legacy_cache_migration_yields_a_usable_teamless_catalog` proves the older bare-`AvailableLLMs` shape migrates to a catalog whose `agent_mode` bucket resolves the cached model. ## Testing - `cargo check --workspace --all-targets` — clean. - `cargo clippy -p warp --lib --tests -- -D warnings` — clean. - `./script/format` — clean. - `./script/bundle --channel oss --nouniversal --check-only` — clean. - `cargo clippy --locked --target wasm32-unknown-unknown --profile release-wasm-debug_assertions -- -D warnings` — clean. - `cargo nextest run -p warp -E 'test(user_workspaces) or test(legacy_cache) or test(gql_convert)'` — 107 passed. - No UI surface changed; this is a data-layer change, so no screenshots. - This branch is stacked on a non-default base, so it does not get a real CI matrix in this repo; the above was run locally on this exact branch tip. ## Linked Issue - [x] N/A — stacked continuation of #15463. ## Agent Mode - [x] Warp Agent Mode - This PR was created via Warp's AI Agent Mode --------- Co-authored-by: warp-agent-staging[bot] <240773466+warp-agent-staging[bot]@users.noreply.github.com> Co-authored-by: Oz <oz-agent@warp.dev>
…team-scope' into pr3-work # Conflicts: # app/src/workspaces/gql_convert.rs # app/src/workspaces/user_workspaces/mod.rs
…-preferences' into pr4-work
# Conflicts: # app/src/workspaces/user_workspaces/mod.rs # app/src/workspaces/user_workspaces/user_workspaces_tests.rs
…-preferences' into pr4-work
Base automatically changed from
factory/multi-team-pr3c-move-llm-preferences
to
master
August 27, 2026 03:53
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Stacked on #15567 (PR 3), which is stacked on #15565 (PR 2) and #15463 (PR 1). PR 1 partitions the model catalog by team (as
Team.feature_model_choice/Workspace.feature_model_choice, read throughUserWorkspaces) but leavesLLMInfoid-lookups and context-window limits resolved against either the unscoped, search-every-teamget_llm_info, or aninherited_or_default_team_uid-resolved fallback, for callers that don't yet thread a liveTeamScopethrough. This PR closes that gap: every caller that already has a liveTeamScopein hand now resolves policy-affectingLLMInfometadata and context-window limits against that exact team, not an arbitrary or inherited one.get_llm_info_for_scope/get_llm_info_for_team_uidLLMPreferences::get_llm_inforesolves an id by scanning every team's bucket and returning the first match, so a model with team-specificdisable_reason/host_configscould resolve to the wrong team's metadata. Addsget_llm_info_for_team_uid(resolves against one team's own catalog) andget_llm_info_for_scope(theTeamScope-taking convenience wrapper), and migrates every policy- or consistency-affecting call site that already has a scope in hand:profile_model_selector.rs'srefresh_model_menuandget_selected_llm_info).terminal/view.rs'smaybe_insert_aws_bedrock_login_banner, which readshost_configsfor this terminal's own team — a team that disables Bedrock must not show the banner because another team's model happens to support it, and vice versa).response_stream.rs'sspawn_request), via a newRequestParams::team_uidfield so the check reads the same team the request was built for.settings_view/execution_profile_view.rs), scoped for consistency with theteam_uidalready resolved alongside them.The unscoped
get_llm_infostays inexecution_profiles/profiles.rs(a model-edit telemetry call site with no window to mint a scope from) and anywhere else display-only metadata is read with no scope in hand.Context-window helpers now use the caller's live scope
effective_base_modeland the wholeAIExecutionProfileAppExttrait (configurable_context_window,context_window_display_value,context_window_limit_for_request,should_show_long_context_pricing_warning) previously resolved the base model via an inherited/default team scope rather than the caller's actual one. On a window viewing a non-default team, this could show/hide the context-window control using another team's model metadata, and — more seriously — clamp the persisted context-window override (context_window_limit_for_request, used byRequestParams::new) against another team's[min, max]before sending the actual request.Threads
&impl TeamScopethrougheffective_base_modeland the trait, and passes the already-resolved scope from every caller that has one:ExecutionProfileEditorView,settings_view/agent_profiles_page.rs's context-window call sites, andRequestParams::new(ai/agent/api.rs, via the newteam_uidfield piggybacked onto the same scope read).Tests
workspaces::update_manager::tests::context_window_limit_for_request_clamps_against_the_scoped_teams_own_range— two teams share a model id but advertise different configurable ranges; the same requested limit clamps differently per team, and a caller passing team A's scope never sees team B's clamp (or vice versa).workspaces::update_manager::tests::on_workspaces_updated_keeps_teams_distinct_and_prunes_a_team_the_response_omits(carried over from PR 1) asserts throughget_llm_info_for_team_uiddirectly instead of the coarser choices-iterator check PR 1 used as a placeholder, since the precise accessor exists as of this PR.Note on re-targeting
This PR replaces #15553 and has now been rebased onto #15567, the current third PR in the catalog-placement stack. Its diff is again only the scoped lookup and context-window work: 14 files, +311/-84.
Visual proof
Not attempted here. Demonstrating two teams with genuinely different catalogs needs a staging account whose two teams have distinct
featureModelChoiceadmin settings — that's account provisioning, not something a capture pass in this environment can produce. Flagging for a human decision on whether it's needed before merge.Testing
Since this branch is stacked four deep, it gets no real CI matrix; all checks ran locally on this exact tip:
cargo check --workspace --all-targets— clean.cargo clippy -p warp --lib --tests -- -D warnings— clean../script/format— clean../script/bundle --channel oss --nouniversal --check-only— clean.cargo clippy --locked --target wasm32-unknown-unknown --profile release-wasm-debug_assertions -- -D warnings— clean.cargo nextest run -p warp ...— 1099 passed.Linked Issue
Agent Mode