Skip to content

Multi-level orchestration: pull-based remote grandchild visibility - #14763

Open
danielpeng2 wants to merge 2 commits into
daniel/multi-level-orchestration-corefrom
daniel/remote-grandchild-visibility
Open

Multi-level orchestration: pull-based remote grandchild visibility#14763
danielpeng2 wants to merge 2 commits into
daniel/multi-level-orchestration-corefrom
daniel/remote-grandchild-visibility

Conversation

@danielpeng2

@danielpeng2 danielpeng2 commented Aug 6, 2026

Copy link
Copy Markdown
Member

Description

I'm intending on having this pull-based architecture be a temporary stopgap. It seemed easier to implement and I wanted to get a basic UI out for nested orchestration. I'll look into a push-based solution.

Stacked on the multi-level orchestration client core. Makes the subtrees of REMOTE (cloud) children visible at depth, and fixes hidden remote-child pane hydration for running runs.

Pull-based remote grandchild discovery. A cloud run that orchestrates children of its own has no push channel to this client in v1 (root-keyed SSE fan-out is deferred), so nothing surfaced its subtree. A new RemoteSubtreeModel watches the remote nodes the pill bar displays, reads the children list on their public-API run items one level at a time, and materializes local placeholder conversations for them — the same pattern the shared-session viewer uses. Placeholders join the regular parent/child topology, so the drill-down bar, group-pill badges, navigation, and root-card rollups need no remote-specific handling; clicking a discovered grandchild opens it through the existing hidden-pane machinery.

Statuses refresh on a slow 30s poll. Polling goes dormant only after a final sweep — a successful fetch issued after the watched run terminated — which guarantees children spawned by fast-completing runs (between the last fetch and termination) are still discovered; failed fetches keep retrying instead of stranding the subtree.

Hydration live-attach fix. Hidden remote-child panes are built on a deferred shared-session viewer that shows nothing until it connects to a session. The hydration path's live-attach arm only flipped view-model state and never connected, so clicking a RUNNING remote child whose pane was created after the fact showed an empty conversation view. The hydration decision now carries the attachable session id and the pane attaches it explicitly, mirroring the attach-to-running flow used by the agents panel. Note this also fixes a latent pre-existing bug: a still-RUNNING level-1 remote child opened after an app restart showed an empty pane.

Linked Issue

Part of the multi-level orchestration work (client side); pairs with the warp-server OrchestrationMaxDepth changes.

Testing

  • Unit tests: placeholder materialization + status refresh dedupe, watch() scoping to remote children, final-sweep dormancy predicate (regression test for the fast-completion hole), and the hydration decision carrying the session id (regression test for the empty-pane bug).
  • cargo clippy -p warp --all-targets --tests -- -D warnings, ./script/format, and the full targeted orchestration test suite (109 tests) are clean at the stack tip.
  • I have manually tested my changes locally with ./script/run (nested cloud children in depth-2 trees)

https://www.loom.com/share/e253b7c77bb044448fa98e4a5b2af960

Agent Mode

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

Co-Authored-By: Warp Agent agent@warp.dev

Cloud runs that orchestrate children of their own have no push channel
to this client in v1, so nothing surfaced their subtrees. This adds
pull-based discovery: a new RemoteSubtreeModel reads the children list
on watched remote runs' public-API run items, materializes local
placeholder conversations for them (the shared-session viewer's
pattern), and refreshes statuses on a slow 30s poll. Placeholders flow
through the regular parent/child topology, so the drill-down pill bar,
navigation, and rollups need no remote-specific handling.

Polling goes dormant only after a "final sweep" — a successful fetch
issued after the watched run terminated — so children spawned by
fast-completing runs (between the last fetch and termination) are
still discovered.

Also fixes hidden remote-child pane hydration for RUNNING runs: the
LiveAttach arm only flipped view-model state and never connected the
pane's deferred shared-session viewer, leaving an empty pane. It now
attaches the live session explicitly. This was a latent pre-existing
bug too: a still-running level-1 remote child opened after an app
restart showed an empty pane.

Co-Authored-By: Warp Agent <agent@warp.dev>
@cla-bot cla-bot Bot added the cla-signed label Aug 6, 2026

Copy link
Copy Markdown
Member Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@oz-for-oss

oz-for-oss Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

@danielpeng2

I'm starting a first review of this pull request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

@oz-for-oss oz-for-oss Bot 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.

Overview

This PR adds pull-based discovery for remote orchestration subtrees and updates remote-child hydration so attachable running sessions connect explicitly. The implementation is directionally scoped to the attached diff, but there are lifecycle gaps in the new polling model and the PR lacks the visual evidence required for this user-facing behavior change.

Concerns

  • The remote subtree watches are only synced from later history/controller events. A restored or already-active orchestration view can mount with remote children already visible but never start pull discovery until some unrelated event fires, leaving grandchildren hidden.
  • Bulk terminal-surface clears are ignored by RemoteSubtreeModel, so entries for cleared conversations can keep issuing remote task fetches after the pane/history surface is gone.
  • For this user-facing change, please include screenshots or a screen recording demonstrating nested remote children and live attach working end to end.

Verdict

Found: 0 critical, 3 important, 0 suggestions

Request changes

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

/// Registers the conversations rendered by this bar with
/// [`RemoteSubtreeModel`] so cloud-side children of any remote node are
/// discovered via pull and rollup badges refresh on its slow poll.
fn sync_remote_subtree_watches(&self, ctx: &mut ViewContext<Self>) {

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.

⚠️ [IMPORTANT] sync_remote_subtree_watches only runs from later event handlers, so a pill bar that mounts around an already-restored orchestration tree can display remote nodes without ever starting pull discovery until another history/controller event happens. Invoke the sync once during initialization or from the render/spec path so existing remote children are watched immediately.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looked into this — I don't think the gap is reachable, so leaving as-is:

  • The pill bar is constructed eagerly in TerminalView::new alongside a fresh AgentViewController that always starts Inactive, and the controller handle isn't reachable externally until the view is built, so the bar can't miss an activation event.
  • The bar renders nothing unless the controller is fullscreen, and every path into fullscreen goes through enter_agent_view_internal, which emits EnteredAgentView — the pill bar's controller subscription runs sync_remote_subtree_watches on every controller event (the matches! gate only clears mouse states).
  • All restore paths hydrate conversations before entering agent view, and restore_conversations unconditionally emits UpdatedConversationStatus per restored conversation (another sync trigger, with no surface filter). Startup restore doesn't auto-enter agent view at all, so nothing displays until an entry that triggers the sync with the tree already in history.

So any pill bar that displays remote nodes has necessarily had a sync run after the tree was present. A construction-time sync would also be a no-op there since the controller is always Inactive at that point (and render can't do it — it takes &AppContext).

| BlocklistAIHistoryEvent::UpdatedStreamingExchange { .. }
| BlocklistAIHistoryEvent::SetActiveConversation { .. }
| BlocklistAIHistoryEvent::ClearedActiveConversation { .. }
| BlocklistAIHistoryEvent::ClearedConversationsForTerminalSurface { .. }

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.

⚠️ [IMPORTANT] Bulk-clearing a terminal surface removes these conversations without emitting per-conversation remove/delete events, but this no-op leaves their entries watched. Because missing conversations make the final-sweep predicate stay false, the model can keep polling /agent/runs/{id} every 30s after the surface is gone; remove all cleared_conversation_ids here and prune them from child maps.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5c5d632: ClearedConversationsForTerminalSurface now unwatches every id in cleared_conversation_ids and prunes child references from the remaining entries (shared unwatch helper with the remove/delete path), with a unit test covering both.

One correction on the mechanism: the bulk clear does not remove conversations from conversations_by_id, so they aren't missing from history. The actual leak is that a cleared conversation loses its surface association, so register_or_update_child bails and the watched node's status freezes — a node frozen in a non-terminal status keeps subtree_may_still_change true forever. The missing-conversation/final-sweep loop only arises downstream (e.g. a cleared conversation later deleted with no surface emits no event). Either way, dropping the entries on clear fixes it; restore/reopen re-watches via the existing sync triggers.

ClearedConversationsForTerminalSurface emits no per-conversation remove
events, and a cleared conversation loses its surface, freezing its local
status. RemoteSubtreeModel previously ignored the event, so watched
entries could keep polling the runs endpoint every 30s indefinitely.
Drop cleared conversations from the watch set and prune child
references; restore/reopen re-watches via the existing sync triggers.

Co-Authored-By: Warp Agent <agent@warp.dev>
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