80 lines
12 KiB
Markdown
80 lines
12 KiB
Markdown
# Cloud-to-cloud handoff PR 1 tech spec
|
||
## Problem statement
|
||
The master cloud-to-cloud handoff plan needs an initial mergeable PR that adds the scaffolding required by later follow-up orchestration and UI work without changing end-user behavior. PR 1 should make the client aware of the `HandoffCloudCloud` rollout boundary, add a typed client method for the server follow-up API, and start isolating task/run identity from execution/session-scoped data in the client model.
|
||
This PR intentionally does not add the tombstone Continue entrypoint, does not submit follow-ups from the UI, and does not attach a follow-up shared session to an existing terminal. Those behaviors belong in later PRs after the foundational APIs exist.
|
||
## Current state
|
||
The current branch already adds the UI/model layer needed to attach a new backing shared session to an existing shared-session viewer; that foundation is documented in `specs/REMOTE-1478/TECH.md`. The broader sequencing and intended user flow are documented in `specs/handoff-cloud-cloud/TECH.md`.
|
||
Feature flags are defined in `crates/warp_features/src/lib.rs`. `CloudModeSetupV2` already exists near the end of the enum at `crates/warp_features/src/lib.rs:827`, and the rollout arrays are defined at `crates/warp_features/src/lib.rs:852`, `crates/warp_features/src/lib.rs:910`, and `crates/warp_features/src/lib.rs:926`. There is currently no `HandoffCloudCloud` flag. The Cargo feature graph in `app/Cargo.toml` is the right place to encode that `handoff_cloud_cloud` depends on `cloud_mode_setup_v2`.
|
||
The public API client already has ambient run methods for spawn, list, and get. The `AIClient` trait includes `spawn_agent`, `list_ambient_agent_tasks`, and `get_ambient_agent_task` at `app/src/server/server_api/ai.rs:799`, and the `ServerApi` implementation posts to `agent/run`, lists `agent/runs`, and gets `agent/runs/{task_id}` at `app/src/server/server_api/ai.rs:1404`. Adjacent public API methods post to run-scoped subresources such as `agent/runs/{task_id}/attachments/prepare` at `app/src/server/server_api/ai.rs:1785`, which is the natural implementation pattern for the follow-up API.
|
||
The server follow-up API is `POST /api/v1/agent/runs/{runId}/followups` with a JSON body containing `message`. The server route and request type are in the server worktree at `/Users/zachbai/.warp-dev/worktrees/warp-server/cloud-agent-task-name/public_api/openapi.yaml:644`, `/Users/zachbai/.warp-dev/worktrees/warp-server/cloud-agent-task-name/router/handlers/public_api/agent_webhooks.go:181`, `/Users/zachbai/.warp-dev/worktrees/warp-server/cloud-agent-task-name/router/handlers/public_api/agent_webhooks.go:608`, and `/Users/zachbai/.warp-dev/worktrees/warp-server/cloud-agent-task-name/public_api/types/types.gen.go:1225`.
|
||
`AmbientAgentTask` currently exposes execution-scoped fields directly: `session_id`, `session_link`, `conversation_id`, `request_usage`, and `is_sandbox_running` at `app/src/ai/ambient_agents/task.rs:217`. `is_no_longer_running` already combines sandbox liveness with run state at `app/src/ai/ambient_agents/task.rs:257`. `SessionJoinInfo::from_task` reads `task.session_link` and `task.session_id` directly at `app/src/ai/ambient_agents/spawn.rs:32`. Agent management and details code also reads these flattened fields directly for session status, links, conversation dedupe, open actions, and details-panel display, including `app/src/ai/agent_conversations_model.rs:468`, `app/src/ai/agent_conversations_model.rs:515`, `app/src/ai/agent_conversations_model.rs:528`, `app/src/ai/agent_conversations_model.rs:553`, `app/src/ai/agent_conversations_model.rs:1293`, and `app/src/ai/conversation_details_panel.rs:337`.
|
||
## Goals
|
||
Add a disabled `HandoffCloudCloud` feature flag, with the app Cargo feature depending on `cloud_mode_setup_v2`.
|
||
Add typed client support for submitting a follow-up prompt to a run via the public API.
|
||
Introduce run/execution-aware accessors on `AmbientAgentTask` that preserve current behavior while giving later PRs a place to route active/latest-execution semantics.
|
||
Move the most important existing call sites from direct flattened fields to the new accessors when doing so is behavior-preserving and low-risk.
|
||
Add targeted unit tests for the new API serialization/path helper and task accessor behavior.
|
||
## Non-goals
|
||
No visible cloud conversation tombstone changes.
|
||
No terminal input submission changes.
|
||
No cloud mode setup UI changes.
|
||
No follow-up polling or shared-session hotswap orchestration.
|
||
No attempt to parse or store a full run-executions array unless the public API already returns it to the client in the current schema. PR 1 should define seams that can absorb that shape later.
|
||
No rollout enablement in `DOGFOOD_FLAGS`, `PREVIEW_FLAGS`, or `RELEASE_FLAGS`.
|
||
## Proposed changes
|
||
### Feature flag scaffolding
|
||
Add `HandoffCloudCloud` to `FeatureFlag` in `crates/warp_features/src/lib.rs`, preserving the enum's chronological ordering. Keep the description concise and product-focused, for example gating cloud-to-cloud continuation of cloud mode conversations.
|
||
Do not add the flag to any rollout arrays in PR 1. The flag should be available for local overrides and future PRs, but disabled by default.
|
||
Add `handoff_cloud_cloud = ["cloud_mode_setup_v2"]` to `app/Cargo.toml` and map the Cargo feature to `FeatureFlag::HandoffCloudCloud` in `app/src/lib.rs`. This keeps the dependency explicit in the build feature graph without adding rollout dependency tests.
|
||
### Follow-up API client
|
||
Add a request type near `SpawnAgentRequest` in `app/src/server/server_api/ai.rs`:
|
||
`RunFollowupRequest { message: String }`, serialized as `{"message":"..."}`.
|
||
Add an `AIClient` method named `submit_run_followup` with parameters `run_id: &AmbientAgentTaskId` and `request: RunFollowupRequest`, returning `anyhow::Result<(), anyhow::Error>`. The current server behavior does not expose a client-needed response payload, so implement with `post_public_api_unit(&build_run_followup_url(run_id), &request)`.
|
||
Add a small path helper, for example `build_run_followup_url(run_id: &AmbientAgentTaskId) -> String`, mirroring `build_list_agent_runs_url` at `app/src/server/server_api/ai.rs:521`. This makes unit testing the endpoint path independent from HTTP mocking.
|
||
Do not call this new method from UI or orchestration code in PR 1.
|
||
### Run/execution-aware task accessors
|
||
Add lightweight accessors to `AmbientAgentTask` in `app/src/ai/ambient_agents/task.rs`. For PR 1 these should project the existing flattened fields as the active/latest execution:
|
||
`run_id() -> AmbientAgentTaskId` returning `task_id`.
|
||
`conversation_id() -> Option<&str>` returning `conversation_id.as_deref()`.
|
||
`active_run_execution() -> RunExecution<'_>` returning a borrowed projection of the flattened execution-scoped fields.
|
||
Add `RunExecution<'a>` with borrowed `session_id`, non-empty `session_link`, `request_usage`, and `is_sandbox_running` fields. The key is that callers stop encoding the assumption that task-level fields are intrinsically task-scoped.
|
||
Update `is_no_longer_running` to use `active_run_execution().is_sandbox_running` and preserve existing behavior.
|
||
Move `SessionJoinInfo::from_task` in `app/src/ai/ambient_agents/spawn.rs:32` to use `active_run_execution()`.
|
||
Update low-risk UI/model call sites that only need the current projection:
|
||
`ConversationOrTask::session_id` should parse `active_run_execution().session_id`.
|
||
`link_preference` should use `active_run_execution().is_sandbox_running`.
|
||
`session_or_conversation_link` should use `active_run_execution().session_link` and `conversation_id()`.
|
||
`get_session_status` should use the session/link accessors.
|
||
`get_open_action` and conversation shadowing should use `run_id()` and `conversation_id()`.
|
||
`ConversationDetailsData::from_task` should use `conversation_id()` for the details-panel conversation id and `active_run_execution().request_usage` for credits.
|
||
Avoid broad mechanical churn in call sites where fields are clearly task metadata rather than execution data, such as title, prompt, state, source, creator, artifact list, and config snapshot.
|
||
### Optional naming cleanup
|
||
Do not rename `AmbientAgentTaskId` or public UI labels in PR 1. The server and much of the client still use run/task terminology interchangeably, and a broad rename would create churn without improving mergeability. The new `run_id()` accessor is enough to clarify stable identity for later PRs.
|
||
## Testing strategy
|
||
Add public API helper tests in `app/src/server/server_api/ai_test.rs` for the follow-up endpoint path. If the request type is public enough to serialize directly, add a serialization test asserting the JSON shape is exactly `{"message":"..."}`.
|
||
Add or update ambient task tests in `app/src/ai/ambient_agents/spawn_tests.rs` or a new `app/src/ai/ambient_agents/task_tests.rs` to cover:
|
||
`SessionJoinInfo::from_task` still prefers server-provided session links.
|
||
It still falls back to constructing a join link from `session_id`.
|
||
It returns `None` when neither an active link nor parseable session id exists.
|
||
The new accessors preserve current flattened-field behavior.
|
||
Run targeted checks after implementation:
|
||
`cargo nextest run -p warp server::server_api::ai::tests::build_run_followup_url_routes_to_run_followups server::server_api::ai::tests::serialize_run_followup_request ai::ambient_agents::spawn::tests`
|
||
`cargo nextest run -p warp ai::agent_conversations_model::tests ai::conversation_details_panel::tests`
|
||
`cargo check -p warp --features handoff_cloud_cloud`
|
||
The exact package names should be verified during implementation from `Cargo.toml` before running. Do not use `cargo fmt --all` or file-specific `cargo fmt`; if formatting is needed before review, use the repo’s standard `cargo fmt` per project guidance.
|
||
## Rollout and compatibility
|
||
The flag is disabled by default, so PR 1 should not change runtime behavior.
|
||
The new API client method is unused in PR 1 and should therefore be safe to merge before server rollout, as long as it compiles against existing client code.
|
||
The accessor migration should be behavior-preserving because each accessor initially projects the same flattened fields. If a direct field use is ambiguous or risky, leave it in place and document it as follow-up rather than expanding the PR scope.
|
||
## Risks and mitigations
|
||
The largest risk is accidentally changing management view link selection or session-open behavior while replacing direct field reads. Keep changes small, prefer local accessor substitutions, and rely on existing spawn and agent management tests where available.
|
||
The follow-up endpoint response shape may differ from the assumed empty response. During implementation, verify the server contract before choosing `post_public_api_unit`; if the server returns a body, add a minimal response type and test deserialization.
|
||
Runtime implication of `HandoffCloudCloud` to `CloudModeSetupV2` is intentionally not implemented in PR 1. The Cargo feature dependency covers compiled builds, while local runtime overrides can still force unusual states for targeted testing.
|
||
The accessor names may need to change once the client consumes first-class run-execution data. Keep PR 1 names descriptive but avoid adding a large abstraction that is not backed by current API data.
|
||
## Definition of done
|
||
`HandoffCloudCloud` exists and is disabled by default.
|
||
`handoff_cloud_cloud` in `app/Cargo.toml` depends on `cloud_mode_setup_v2`.
|
||
`AIClient` and `ServerApi` expose a typed follow-up submission method for `POST agent/runs/{run_id}/followups`.
|
||
`AmbientAgentTask` exposes run/execution-aware accessors and the main session/conversation call sites use them without behavior changes.
|
||
Targeted tests cover the follow-up API path/serialization and task/session accessor behavior, and a feature-gated compile check passes.
|