[multi-team PR 2B] Agent creation and cost attribution (slice 2) - #15359
Open
warp-agent-staging[bot] wants to merge 7 commits into
Open
[multi-team PR 2B] Agent creation and cost attribution (slice 2)#15359warp-agent-staging[bot] wants to merge 7 commits into
warp-agent-staging[bot] wants to merge 7 commits into
Conversation
Contributor
Author
|
This PR was generated with Warp. Comment |
warp-agent-staging
Bot
force-pushed
the
factory/multi-team-pr2b-slice2-agent-creation
branch
from
August 20, 2026 10:43
17a53aa to
36e1a32
Compare
warp-agent-staging Bot
added a commit
that referenced
this pull request
Aug 20, 2026
Threads a resolved team_uid through the model-picker credential-source chain (byo_key_source_for_model, should_show_key_icon_for_model, and their UserWorkspaces-backed helpers), so the key icon and inference label shown for a model reflect the window's actual current team_byo policy and team-provided key/endpoint configuration, instead of the old ambient/ first-team read. Added UserWorkspaces::has_team_first_party_key_for_team and has_team_byo_endpoint_for_model_for_team (by team UID), mirroring the are_member_byo_*_allowed_for_team pattern already established for request-time enforcement. agent_settings_has_team_first_party_key now delegates to the new by-uid method. is_using_first_party_key_for_provider (and the disable-reason-clearing path in is_usable_llm that depends on it) is deliberately left on its existing ambient read: re-scoping which RequiresUpgrade models stay selectable to a specific window's team is a larger, separate change with a much wider blast radius (it touches every model-resolution method in LLMPreferences), tracked as follow-up, not part of the model-picker credential-source display this covers. Updated all call sites that render model-picker/menu items with a resolved team_uid: ModelSelectorDataSource (GUI inline picker, already had window_id), ProfileModelSelector (added a window_id field), model_menu_items::available_model_menu_items and its ~6 callers (execution profile editor, agent profiles settings page, custom model router editor, orchestration controls), the buy-credits-banner check in terminal/input/common.rs, and the TUI model menu (TuiModelMenuModel, now also given a window_id). This is unblocked by, and independent of, #15359: team_byo policy and team-provided keys/endpoints are already genuinely team-differentiated today (this is exactly what the request-time enforcement work already landed proves). Only the model *catalog* itself (which models exist) remains account-wide pending #15359, which is why the model list is not touched here.
warp-agent-staging Bot
added a commit
that referenced
this pull request
Aug 20, 2026
Threads a resolved team_uid through the model-picker credential-source chain (byo_key_source_for_model, should_show_key_icon_for_model, and their UserWorkspaces-backed helpers), so the key icon and inference label shown for a model reflect the window's actual current team_byo policy and team-provided key/endpoint configuration, instead of the old ambient/ first-team read. Added UserWorkspaces::has_team_first_party_key_for_team and has_team_byo_endpoint_for_model_for_team (by team UID), mirroring the are_member_byo_*_allowed_for_team pattern already established for request-time enforcement. agent_settings_has_team_first_party_key now delegates to the new by-uid method. is_using_first_party_key_for_provider (and the disable-reason-clearing path in is_usable_llm that depends on it) is deliberately left on its existing ambient read: re-scoping which RequiresUpgrade models stay selectable to a specific window's team is a larger, separate change with a much wider blast radius (it touches every model-resolution method in LLMPreferences), tracked as follow-up, not part of the model-picker credential-source display this covers. Updated all call sites that render model-picker/menu items with a resolved team_uid: ModelSelectorDataSource (GUI inline picker, already had window_id), ProfileModelSelector (added a window_id field), model_menu_items::available_model_menu_items and its ~6 callers (execution profile editor, agent profiles settings page, custom model router editor, orchestration controls), the buy-credits-banner check in terminal/input/common.rs, and the TUI model menu (TuiModelMenuModel, now also given a window_id). This is unblocked by, and independent of, #15359: team_byo policy and team-provided keys/endpoints are already genuinely team-differentiated today (this is exactly what the request-time enforcement work already landed proves). Only the model *catalog* itself (which models exist) remains account-wide pending #15359, which is why the model list is not touched here.
warp-agent-staging Bot
added a commit
that referenced
this pull request
Aug 21, 2026
Threads a resolved team_uid through the model-picker credential-source chain (byo_key_source_for_model, should_show_key_icon_for_model, and their UserWorkspaces-backed helpers), so the key icon and inference label shown for a model reflect the window's actual current team_byo policy and team-provided key/endpoint configuration, instead of the old ambient/ first-team read. Added UserWorkspaces::has_team_first_party_key_for_team and has_team_byo_endpoint_for_model_for_team (by team UID), mirroring the are_member_byo_*_allowed_for_team pattern already established for request-time enforcement. agent_settings_has_team_first_party_key now delegates to the new by-uid method. is_using_first_party_key_for_provider (and the disable-reason-clearing path in is_usable_llm that depends on it) is deliberately left on its existing ambient read: re-scoping which RequiresUpgrade models stay selectable to a specific window's team is a larger, separate change with a much wider blast radius (it touches every model-resolution method in LLMPreferences), tracked as follow-up, not part of the model-picker credential-source display this covers. Updated all call sites that render model-picker/menu items with a resolved team_uid: ModelSelectorDataSource (GUI inline picker, already had window_id), ProfileModelSelector (added a window_id field), model_menu_items::available_model_menu_items and its ~6 callers (execution profile editor, agent profiles settings page, custom model router editor, orchestration controls), the buy-credits-banner check in terminal/input/common.rs, and the TUI model menu (TuiModelMenuModel, now also given a window_id). This is unblocked by, and independent of, #15359: team_byo policy and team-provided keys/endpoints are already genuinely team-differentiated today (this is exactly what the request-time enforcement work already landed proves). Only the model *catalog* itself (which models exist) remains account-wide pending #15359, which is why the model list is not touched here.
IsaiahWitzke
pushed a commit
that referenced
this pull request
Aug 21, 2026
Threads a resolved team_uid through the model-picker credential-source chain (byo_key_source_for_model, should_show_key_icon_for_model, and their UserWorkspaces-backed helpers), so the key icon and inference label shown for a model reflect the window's actual current team_byo policy and team-provided key/endpoint configuration, instead of the old ambient/ first-team read. Added UserWorkspaces::has_team_first_party_key_for_team and has_team_byo_endpoint_for_model_for_team (by team UID), mirroring the are_member_byo_*_allowed_for_team pattern already established for request-time enforcement. agent_settings_has_team_first_party_key now delegates to the new by-uid method. is_using_first_party_key_for_provider (and the disable-reason-clearing path in is_usable_llm that depends on it) is deliberately left on its existing ambient read: re-scoping which RequiresUpgrade models stay selectable to a specific window's team is a larger, separate change with a much wider blast radius (it touches every model-resolution method in LLMPreferences), tracked as follow-up, not part of the model-picker credential-source display this covers. Updated all call sites that render model-picker/menu items with a resolved team_uid: ModelSelectorDataSource (GUI inline picker, already had window_id), ProfileModelSelector (added a window_id field), model_menu_items::available_model_menu_items and its ~6 callers (execution profile editor, agent profiles settings page, custom model router editor, orchestration controls), the buy-credits-banner check in terminal/input/common.rs, and the TUI model menu (TuiModelMenuModel, now also given a window_id). This is unblocked by, and independent of, #15359: team_byo policy and team-provided keys/endpoints are already genuinely team-differentiated today (this is exactly what the request-time enforcement work already landed proves). Only the model *catalog* itself (which models exist) remains account-wide pending #15359, which is why the model list is not touched here.
warp-agent-staging
Bot
force-pushed
the
factory/multi-team-pr2b-agent-creation-attribution
branch
from
August 24, 2026 06:00
4700414 to
5d784f0
Compare
4 tasks
warp-agent-staging
Bot
force-pushed
the
factory/multi-team-pr2b-slice2-agent-creation
branch
from
August 24, 2026 06:24
36e1a32 to
381249d
Compare
warp-agent-staging
Bot
force-pushed
the
factory/multi-team-pr2b-slice2-agent-creation
branch
from
August 24, 2026 06:54
381249d to
df42e5e
Compare
IsaiahWitzke
force-pushed
the
factory/multi-team-pr2b-agent-creation-attribution
branch
from
August 25, 2026 04:53
33e0ca1 to
c6243fc
Compare
Adds request-local team-scope transport plumbing for the multi-team
context migration:
- X-Warp-Team-Uid header constant plus REST header-passing helpers on
ServerApi/BaseClient (get/post *_with_headers, team_uid_header).
- SpawnAgentRequest.team: Option<bool> replaced with an explicit
AgentRunScope { Personal, Team(ServerId) }, wired to send the header
on POST /agent/run.
- /ai/multi-agent and /ai/passive-suggestions transport threaded
end-to-end (ResponseStream -> impl.rs -> warp_multi_agent_client)
per the RequestParams-separation requirement; currently fed None
pending view-level TeamContext capture (documented TODOs).
See specs/multi-team-api-context/TECH.md and the PR description for
the full breakdown, deviations, and deferred work.
…onal/Team) Review found that Personal serialized by omission, making the server treat --personal the same as no flag at all (team ownership by default for a single-team account). Personal now serializes as explicit team: false, Unspecified omits the field, and call sites are corrected to match their pre-existing wire behavior. Also adds header/body coverage for spawn_agent via a mock public API request.
The `/ai/multi-agent` and `/ai/passive-suggestions` boundary took a raw `Option<ServerId>`, which carries no provenance: it is indistinguishable from "no team" and can be produced anywhere, including by re-reading live window state after a request has already started. That admits a race where work begun on team A is attributed to team B if the window switches while the request is in flight. Introduce `RequestTeamScope`, constructible only from a `TeamScope`, and take it at that boundary instead. There is deliberately no constructor from a bare uid, so the value sent as `X-Warp-Team-Uid` can only have come from a deliberately resolved team. It is `Copy`, and `ResponseStream` captures one at construction, so retries and post-credential-refresh re-sends stay on the team the request started on. This also completes the threading the previous commits deferred: the controller and passive suggestions now resolve the terminal surface's `TeamContext` before spawning rather than passing `None`, so these requests begin sending the team header. Co-Authored-By: Warp <agent@warp.dev>
IsaiahWitzke
force-pushed
the
factory/multi-team-pr2b-agent-creation-attribution
branch
from
August 25, 2026 05:08
c6243fc to
dda315c
Compare
Adds request-local team scope to the remaining bucket-2 endpoints: GraphQL CreateAgentTask, agent identity listing, connected self-hosted workers, local-to-cloud handoff snapshot upload, GetFeatureModelChoices, and CreateFileArtifactUploadTarget. Fixes a real bug in GetFeatureModelChoices that took workspaces[0] unconditionally regardless of which team was requested.
warp-agent-staging
Bot
force-pushed
the
factory/multi-team-pr2b-slice2-agent-creation
branch
from
August 25, 2026 06:25
df42e5e to
5b6dded
Compare
IsaiahWitzke
force-pushed
the
factory/multi-team-pr2b-agent-creation-attribution
branch
from
August 26, 2026 01:12
db3c4c2 to
81ec8a8
Compare
Base automatically changed from
factory/multi-team-pr2b-agent-creation-attribution
to
master
August 26, 2026 01:35
…pr2b-slice2-agent-creation # Conflicts: # app/src/ai/agent/api_tests.rs # app/src/ai/blocklist/controller.rs # app/src/ai/blocklist/controller/response_stream.rs # app/src/server/server_api.rs # crates/warp_server_client/src/graphql_helpers.rs
…full workspace check
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
Slice 2 of
#### PR 2B: Agent creation and cost attributionfromspecs/multi-team-api-context/TECH.md(Bucket 2: Request-local team identity). Builds on slice 1 (#15355) and PR 0 (#15348, merged).Covers the remaining bucket-2 endpoints deferred from slice 1: GraphQL
CreateAgentTask, agent identity listing, connected self-hosted workers, local-to-cloud handoff snapshot upload (both local and remote-SSH paths), GraphQLGetFeatureModelChoices, and GraphQLCreateFileArtifactUploadTarget.Rebased onto the revived #15355 (which has since landed the
TeamScopetrait,TeamContextForOperation/TeamContext/TeamScopeForCli, and theuser_workspacesmodule split). The only real conflict was ateam_uid_for_transporthelper this PR originally added, typed to the old pre-splitTeamContext { team_uid: ServerId }shape and returning a non-optionalServerId. That shape no longer exists:TeamContextnow already implements the sharedTeamScopetrait withteam_uid(&self) -> Option<ServerId>, so the helper was redundant — call sites now read.team_uid()directly off theTeamContextreturned byteam_context_for_view, and a teamless scope naturally omits the header instead of needing special-casing.What's implemented
AIClienttrait: addedteam_uid: Option<ServerId>tocreate_agent_task,list_connected_self_hosted_workers,upload_local_handoff_snapshot,get_feature_model_choices, andcreate_file_artifact_upload_target.AuthClient::list_agent_identitiesgained the same parameter (asOption<String>, since that trait lives inwarp_server_clientand doesn't depend on the app crate'sServerId).ViewContextto mint aTeamContextfrom:create_api_key_modal.rs::fetch_agents— captures the window'sTeamContextbefore listing named agent identities for the "Agent" API-key type.handoff::snapshot::resolve_upload_target(ViewContext<Workspace>) — captures it once, before branching, and threads it through both the local path (upload_handoff_snapshot→upload_snapshot_for_handoff→AIClient::upload_local_handoff_snapshot) and the remote-SSH path (SnapshotUploadTarget::Remote→ theUploadHandoffSnapshotRPC, now carrying an addedteam_uidproto field → the daemon'shandle_upload_handoff_snapshot→gather_and_upload_handoff_snapshot→ the sameupload_snapshot_for_handoff). Both paths land on the identical client-side capture and the identical server call.GetFeatureModelChoicesheader threading, plus a defensive selection fix. The client previously tookworkspaces[0]unconditionally from the response array; it now selects the entry matching the requesteduidinstead of trusting position, and fails the request outright (rather than substituting another entry) when the requested team has no matching entry. Read this carefully: as of today the server'sFeatureModelChoiceresolver ignores the*Workspacerow it's attached to and recomputes from the caller's active team (set via the newX-Warp-Team-Uidheader) or a fallback — so every element of the array currently carries the same catalog, and this selection logic does not itself change which catalog comes back. What it does do is (a) send the header so the server's notion of "active team" for this request is genuinely the one the caller asked about, and (b) make the client fail loudly instead of reading a wrong-but-present row, which matters once the resolver's shape changes.warp-server#15900is now open addingTeam.featureModelChoice, which will make a genuine per-team map available server-side; this selection-by-uidlogic is exactly what will make that map's entries actually distinguishable once the client reads from it. Do not read the array as a real per-team map yet.ConnectedSelfHostedWorkersModelis a process-global singleton with no window to capture a realTeamContextfrom, so it resolves the account's default team viaUserWorkspaces::inherited_or_default_team_uid(None)(the same window-less fallbackadmin_billing_link_for_default_teamalready uses) and refreshes onUserWorkspacesEvent::TeamsChanged.create_agent_task's child/task-scoped launches,LLMPreferences::refresh_authed_models,FileArtifactUploader::create_upload_target) passNone, documented in-line with why each one doesn't have a window to capture aTeamContextfrom.What is and is not wired end-to-end
CreateAgentTask(window-initiated launches only — child/task-scoped launches passNone),GET /api/v1/agent/identities(fromcreate_api_key_modal.rs),POST /api/v1/agent/handoff/upload-snapshot(both local and remote-SSH handoff paths),GET /api/v1/agent/connected-self-hosted-workers(via the account's default team), GraphQLGetFeatureModelChoices(currently alwaysNone— see below), GraphQLCreateFileArtifactUploadTarget(currently alwaysNone— see below).team_uid: None):LLMPreferences::refresh_authed_models(no per-team storage yet — that's PR 3A's scope),FileArtifactUploader::create_upload_target(always resolves an existingconversation_id/run_idassociation first, so the server validates ownership against that resource instead), allcreate_agent_taskchild/task-scoped launches (scope is meant to be inherited fromparent_run_idserver-side).POST /graphql/v2and/ai/*resolveX-Warp-Team-Uidtoday;/api/v1/*(including/agent/handoff/upload-snapshotand/agent/connected-self-hosted-workers) currently only captures the header, it does not yet resolve/authorize it.Deviations from the spec / deferred work
GetFeatureModelChoicesstill returns one process-wideModelsByFeaturevalue, not per-team storage. Genuinely partitioning the cache (so a window showing Team A's models doesn't leak into a window showing Team B's) requires restructuringLLMPreferences's ~30 read methods and every call site that reaches them — that's PR 3A's explicitly-scoped charter, not something safe to fold into a header-plumbing PR.create_agent_task's client call sites are all child/task-scoped, not window-scoped: local Claude/Codex/OpenCode child launches (local_harness_launch.rs), local Oz child launches (child_agent_launch.rs), and the headless CLI driver's fresh top-level task creation (agent_sdk/mod.rs::initialize_new_task, which has no--teamselector on this CLI surface yet). All passNone.FileArtifactUploader::create_upload_targetpassesNone. It always resolves an existingconversation_id/run_idassociation first, so the server can validate ownership against that resource; there's no window in this path to capture a freshTeamContextfrom.Testing
./script/format— clean.cargo check -p warp --lib— clean, no new warnings.cargo clippy -p warp --all-targets --tests -- -D warnings— clean.cargo clippy -p warp_server_client -p remote_server --all-targets --tests -- -D warnings— clean.cargo nextest run -p remote_server— 99/99 pass (covers the proto/manager changes toUploadHandoffSnapshot).ai_tests.rs:select_feature_model_choice_workspace_index_matches_requested_team— selection matches by UID, not position; falls back to index 0 only for no-team-scope.select_feature_model_choice_workspace_index_returns_none_for_missing_team— a team absent from the response returnsNone(fails the request) rather than substituting another entry.select_feature_model_choice_workspace_index_returns_none_for_empty_response— an empty response also returnsNone.cargo nextest run -p warpfiltered to the touched modules —local_harness_launch,pipeline_tests,connected_self_hosted_workers,server_api::ai,create_api_key_modal,llms,host_response,server_model, plus the selection tests): 127/129 pass. The 2 failures (ambient_agent_headers_for_task_overrides_existing_cloud_agent_header,spawn_agent_sends_matching_team_uid_header_and_body_flag_only_when_team_scoped) are a pre-existing sandbox limitation — both call out to an externalnscisolation-platform binary unavailable in this environment, and reproduce identically against an unmodifiedmastercheckout.CI coverage
This PR targets
#15355's branch (a stacked PR), notmaster. GitHub's build/test/clippy workflow only runs for pull requests into the default branch, so on this branch only the approval and base-verification checks run here — no compile, no tests, no clippy from CI, no matter how long the checks are left pending. A green check mark on this PR does not mean the build/tests ran. The local verification listed above (format, clippy acrosswarp/warp_server_client/remote_server, focused and fullremote_servertest runs) stands in for that until this PR retargets tomasterand gets the real CI matrix, which happens automatically once #15355 merges.Server-side
Per the orchestrator: server-side request-local scope, membership authorization, and mismatch rejection for
X-Warp-Team-Uidis separate, tracked work. This client change must not ship before that server-side validation exists on the surfaces it depends on.Linked Issue
N/A — this PR implements a section of the multi-team-context/multi-team-api-context tech specs from #15347 (Linear REV-2205), not a tracked GitHub issue.
Agent Mode
CHANGELOG-NONE