[multi-team] Scope multi-agent requests to a team - #15355
Merged
IsaiahWitzke merged 1 commit intoAug 26, 2026
Conversation
Contributor
Author
|
This PR was generated with Warp. Comment |
1 task
warp-agent-staging Bot
pushed a commit
that referenced
this pull request
Aug 20, 2026
…Params RequestParams::new() reverts to its original signature; the caller (BlocklistAIController, which already resolves the window's team) attaches the GEAP credential separately after construction via a new attach_geap_credentials_if_available helper. ResponseStream.team_uid now supplies a real value (via BlocklistAIController's window resolution) instead of #15355's placeholder None, so its X-Warp-Team-Uid header threading will actually fire once both land.
warp-agent-staging Bot
pushed a commit
that referenced
this pull request
Aug 20, 2026
…Params RequestParams::new() reverts to its original signature; the caller (BlocklistAIController, which already resolves the window's team) attaches the GEAP credential separately after construction via a new attach_geap_credentials_if_available helper. ResponseStream.team_uid now supplies a real value (via BlocklistAIController's window resolution) instead of #15355's placeholder None, so its X-Warp-Team-Uid header threading will actually fire once both land.
warp-agent-staging Bot
pushed a commit
that referenced
this pull request
Aug 20, 2026
Proves begin_expired_geap_refresh starts independent mints for two different teams rather than coalescing one behind the other's waiter list. Also removes a process/merge-history comment from ResponseStream that named PR #15355 and described how to resolve a future conflict; that belongs in the PR description, not production source.
warp-agent-staging Bot
pushed a commit
that referenced
this pull request
Aug 20, 2026
…Params RequestParams::new() reverts to its original signature; the caller (BlocklistAIController, which already resolves the window's team) attaches the GEAP credential separately after construction via a new attach_geap_credentials_if_available helper. ResponseStream.team_uid now supplies a real value (via BlocklistAIController's window resolution) instead of #15355's placeholder None, so its X-Warp-Team-Uid header threading will actually fire once both land.
warp-agent-staging Bot
pushed a commit
that referenced
this pull request
Aug 20, 2026
Proves begin_expired_geap_refresh starts independent mints for two different teams rather than coalescing one behind the other's waiter list. Also removes a process/merge-history comment from ResponseStream that named PR #15355 and described how to resolve a future conflict; that belongs in the PR description, not production source.
warp-agent-staging Bot
added a commit
that referenced
this pull request
Aug 20, 2026
TerminalView owned an ai_controller whose window_id was set once at construction and never updated. TerminalView had no on_window_transferred override, so dragging a terminal into another window left the controller resolving the source window's team indefinitely -- GEAP credentials would mint/attach for the wrong team, and the X-Warp-Team-Uid header (once #15355 lands) would carry the wrong team too. Adds BlocklistAIController::set_window_id and a TerminalView on_window_transferred override that keeps both window_id fields current. Adds a regression test that performs a real transfer_view_to_window and asserts the controller's resolved team follows the window.
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
Contributor
Author
|
The red Responding as wilson: Open session · View in factory |
IsaiahWitzke
force-pushed
the
factory/multi-team-pr2b-agent-creation-attribution
branch
from
August 25, 2026 04:53
33e0ca1 to
c6243fc
Compare
IsaiahWitzke
changed the base branch from
master
to
factory/multi-team-p5-remote-session-ai
August 25, 2026 04:54
Base automatically changed from
factory/multi-team-p5-remote-session-ai
to
master
August 25, 2026 04:58
IsaiahWitzke
force-pushed
the
factory/multi-team-pr2b-agent-creation-attribution
branch
from
August 25, 2026 05:08
c6243fc to
dda315c
Compare
4 tasks
`/ai/multi-agent` and `/ai/passive-suggestions` sent no team context, so the server could not tell which team an agent turn belonged to. They now send `X-Warp-Team-Uid`, resolved from the terminal surface's team. The uid is carried by `RequestTeamScope`, which only a `TeamScope` can build. A raw `Option<ServerId>` would carry no provenance -- indistinguishable from "no team" and constructible 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 mid-flight. The type lives alone in its own module so the privacy boundary matches the invariant exactly: `from_scope` is provably the only way in. 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. Behavior change: these paths previously sent no team header at all. The uid always comes from a team the user is on, and the server authenticates membership, but this is the highest-frequency request path in the product. `warp_multi_agent_client` keeps taking the raw wire value -- it sits below `app` and cannot see `TeamScope`, so the typed-to-wire extraction happens one layer up. Co-Authored-By: Warp <agent@warp.dev>
IsaiahWitzke
force-pushed
the
factory/multi-team-pr2b-agent-creation-attribution
branch
from
August 26, 2026 01:12
db3c4c2 to
81ec8a8
Compare
IsaiahWitzke
approved these changes
Aug 26, 2026
IsaiahWitzke
enabled auto-merge (squash)
August 26, 2026 01:22
IsaiahWitzke
deleted the
factory/multi-team-pr2b-agent-creation-attribution
branch
August 26, 2026 01:35
This was referenced Aug 26, 2026
This was referenced Aug 26, 2026
IsaiahWitzke
pushed a commit
that referenced
this pull request
Aug 26, 2026
## Summary `POST /ai/predict_am_queries` (Agent Mode next-query prediction) sent no team context. The `/ai` group's `SetActiveTeamFromHeader` middleware only picks up the header, so omitting it silently falls through `GetActiveTeamOrFallbackToPrincipal` → `GetTeamForPrincipal`, which resolves to whichever team happens to have the lowest id — an arbitrary, and possibly wrong, team for a member of more than one. ## Changes - `ServerApi::predict_am_queries` now takes a `RequestTeamScope` and sends it as `X-Warp-Team-Uid` when present, following the `/ai/multi-agent` pattern (#15355). - `Input::predict_am_query` resolves the scope from the live window via `UserWorkspaces::team_context_for_operation`, pinning it at send time (this call spawns an async request, same as the existing multi-agent send path). ## Verification - `./script/format` - `cargo clippy -p warp --lib --tests -- -D warnings` (clean) - No existing unit tests cover this thin network wrapper method; verified by inspection that the header is set conditionally and threading compiles against the live `ViewContext<Input>` available at the call site. <!-- warp:pr-description-artifacts start --> <!-- warp:pr-description-artifacts end --> Co-authored-by: warp-agent-staging[bot] <240773466+warp-agent-staging[bot]@users.noreply.github.com>
IsaiahWitzke
pushed a commit
that referenced
this pull request
Aug 26, 2026
## Summary Scopes the `POST /ai/generate_input_suggestions` call (next-command autosuggestions) to the window's selected team by sending `X-Warp-Team-Uid`. Server-side, this endpoint reaches workspace settings and billing metadata (`generate_next_command.go`) that are genuinely team-scoped. Without the header, the server falls through `GetActiveTeamOrFallbackToPrincipal` → `GetTeamForPrincipal`, which silently answers using whichever team happens to have the lowest team id — not necessarily the team the window is showing. ## Changes - `ServerApi::generate_ai_input_suggestions` now takes a `RequestTeamScope` and sends it via the `X-Warp-Team-Uid` header (server already applies it for `/ai/*` via `SetActiveTeamFromHeader`, so no server change is needed). - `NextCommandModel` now holds the terminal's `BlocklistAIController` handle and resolves the scope via `ai_controller.team_context(ctx)` immediately before spawning the request, mirroring the pattern used for `/ai/multi-agent` (PR #15355) and passive suggestions — this keeps the team pinned to whatever the window was scoped to when the suggestion was requested, even if the user switches teams while the request is in flight. - Threaded the resolved scope through both call sites in `generate_next_command_suggestion_with_prefix` (zero-state and prefixed suggestions). ## Verification - `cargo check -p warp --lib` and `cargo clippy -p warp --lib --tests -- -D warnings`: clean. - `./script/format`: clean (only reformatted the new lines). - `cargo nextest run -p warp` for `next_command_model`, `server_api`, and `team_scope`: 81/82 relevant tests pass. The one failure (`ambient_agent_headers_for_task_overrides_existing_cloud_agent_header`) is a pre-existing sandbox limitation (the test's `nsc` namespace-token command is denied in this environment) reproduced identically on unmodified `master`, unrelated to this change. - No UI-visible behavior changed (this is a header addition on an existing background request), so no visual proof was captured. <!-- warp:pr-description-artifacts start --> <!-- warp:pr-description-artifacts end --> --------- Co-authored-by: warp-agent-staging[bot] <240773466+warp-agent-staging[bot]@users.noreply.github.com>
IsaiahWitzke
pushed a commit
that referenced
this pull request
Aug 26, 2026
## Summary Scopes `POST /ai/transcribe` (voice-input transcription) to the window's team by sending `X-Warp-Team-Uid`, following the pattern landed for `/ai/multi-agent` in #15355. Without the header, the server falls through `GetActiveTeamOrFallbackToPrincipal` → `GetTeamForPrincipal`, which resolves to whichever team has the lowest id — not necessarily the team the user has selected in that window. Server-side, `transcribe.go` uses the resolved team for cost denial, billing, and workspace settings, so an unscoped call can silently apply the wrong team's policy. ## Changes - `Transcriber::transcribe` and its only implementation (`ServerVoiceTranscriber`) now take a `RequestTeamScope`. - `ServerApi::transcribe` sets `X-Warp-Team-Uid` from that scope (server-side handling already exists via `SetActiveTeamFromHeader` on `/ai/*`, so no server change is needed). - The one call site, `EditorView::handle_voice_session_result`, captures the scope from its `ViewContext` (`UserWorkspaces::as_ref(ctx).team_context_for_view(ctx)`) before the transcription future is spawned off the main thread, since no window is reachable once it's running. ## Verification This is a header-plumbing change through an established pattern (`RequestTeamScope`, mirroring the `/ai/multi-agent` wiring); no new business logic to unit test. Verified with: - `./script/format` - `cargo clippy -p warp --lib --tests -- -D warnings` - `cargo check -p warp --lib` I did not have a live warp-server + client environment available to manually exercise the voice-input flow end-to-end. <!-- warp:pr-description-artifacts start --> <!-- warp:pr-description-artifacts end --> --------- Co-authored-by: warp-agent-staging[bot] <240773466+warp-agent-staging[bot]@users.noreply.github.com>
IsaiahWitzke
pushed a commit
that referenced
this pull request
Aug 26, 2026
## Description `ServerApi::get_relevant_files` (backing `POST /ai/relevant_files`, used by codebase search when picking relevant files for the outline-based index) sent no team scope. The server falls through `GetActiveTeamOrFallbackToPrincipal` → `GetTeamForPrincipal`, which resolves to the member's lowest team id rather than the team the requesting window is actually on. Since this endpoint's cost is billed to the AI group, members on more than one team could have this request billed to the wrong team. This threads the live, window-derived `TeamScope` through to the request as the `X-Warp-Team-Uid` header, following the pattern established in #15355 for `/ai/multi-agent`. No server change is needed — `SetActiveTeamFromHeader` already runs on `/ai/*`. The scope comes from `BlocklistAIActionExecutor`'s existing `TeamContextResolver` (already resolved from the window via `UserWorkspaces::team_context_resolver`), the same live scope it already uses to decide whether a `SearchCodebase` action can auto-execute. It's resolved once, immediately before executing the action, and threaded as a `RequestTeamScope` through `SearchCodebaseExecutor::execute` → `GetRelevantFilesController::send_request` (local, outline-based path only — the remote path resolves relevant files through the remote host's own indexing service and isn't affected) → `ServerApi::get_relevant_files`. ## Linked Issue No linked GitHub issue — this is one of several call sites being migrated to send an explicit team scope instead of relying on the server's team-resolution fallback. ## Testing This is a plumbing change (adds a parameter, sets a header) with no new externally observable branching logic, so no new unit test was added — consistent with the reference implementation in #15355. Verified: - `cargo check -p warp --lib` - `cargo clippy -p warp --lib --tests -- -D warnings` - `cargo test -p warp --lib --features local_fs` for `get_relevant_files`, `search_codebase`, and `action_model` (192 tests, all passing) - `./script/format` - [ ] I have manually tested my changes locally with `./script/run` ## Agent Mode - [x] Warp Agent Mode - This PR was created via Warp's AI Agent Mode <!-- warp:pr-description-artifacts start --> <!-- warp:pr-description-artifacts end --> --------- Co-authored-by: warp-agent-staging[bot] <240773466+warp-agent-staging[bot]@users.noreply.github.com>
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
/ai/multi-agentand/ai/passive-suggestionssent no team context, so the server could not tell which team an agent turn belonged to. They now sendX-Warp-Team-Uid, resolved from the terminal surface's team.The agent-run half of this work (
POST /agent/run,AgentRunScope, the CLI flags) was split out into #15552, which is independent and can land on its own.The core idea: the uid is carried by
RequestTeamScope, which only aTeamScopecan build. A rawOption<ServerId>carries no provenance — it's indistinguishable from "no team" and constructible 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 mid-flight. The type lives alone inapp/src/server/team_scope.rsso the module privacy boundary matches the invariant exactly:from_scopeis provably the only way to build one.It's
Copy, andResponseStreamcaptures one at construction, so retries — and post-credential-refresh re-sends — stay on the team the request started on rather than drifting.warp_multi_agent_clientstill takes the raw wire value: it sits belowappand cannot seeTeamScope, so the typed-to-wire extraction happens one layer up inapi/impl.rs.Important
Behavior change. These paths previously sent no team header at all (hardcoded
Nonebehind TODOs). The uid always comes from a team the user is genuinely on and the server authenticates membership, but this is the highest-frequency request path in the product — every agent turn and every passive suggestion. Worth confirming the server side is ready before merging.Linked Issue
See
specs/multi-team-api-context/TECH.md.ready-to-specorready-to-implement.Testing
Added a unit test asserting a
RequestTeamScopecarries exactly the team its scope named, and that a teamless scope sends no header.Verified locally:
./script/format,cargo clippy -p warp --all-targets --tests -- -D warnings, and 257 related tests passing (response-stream recovery, passive suggestions, and the team-scope suites).No UI changes, so no screenshots.
./script/runAgent Mode