Skip to content

QUALITY-928: Orchestration unified stack — pane path + transcript (M2) - #14480

Open
cephalonaut wants to merge 29 commits into
matthew/orch-unified-m1from
matthew/orch-unified-m2
Open

QUALITY-928: Orchestration unified stack — pane path + transcript (M2)#14480
cephalonaut wants to merge 29 commits into
matthew/orch-unified-m1from
matthew/orch-unified-m2

Conversation

@cephalonaut

@cephalonaut cephalonaut commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Description

Unifies the orchestration child pane path and moves parent/child restore onto task data (QUALITY-928).

What

  • Unified child pane materialization: the owner/viewer code split is collapsed into a single dispatch path. TaskOwnership, TaskScope, and ChildPaneOrigin are removed along with the branching they supported.
  • OrchestrationViewerModel now uses AgentConversationsModel as its fetch authority, routing child discovery through pending_task_ids_for_discovery.
  • Remote child and parent cloud agent conversations are ephemeral cloud state — not persisted to local DB. On restore they are re-seeded from the server via GET /agent/runs?ancestor_run_id=, the same query the OVM uses live.
  • The pill bar re-renders on ConversationServerTokenAssigned, ensuring correct notification after the async restore seed completes.
  • Local-parent seed trigger: when a local agent parent restores with an empty child index, restore_missing_child_agent_panes_for_parent triggers the server seed to discover remote children.
  • finish_seed_child_conversations_from_task does not re-trigger the seed on completion (trigger_seed_if_empty=false), preventing an infinite loop.

Why

The parallel owner/viewer hydration paths had accumulated code duplication and subtle behavioral differences. Consolidating around task data as the authoritative source simplifies restore and makes behavior consistent across native and web clients.

Linked Issue

QUALITY-928

Testing

  • Manual: pill bar appears after restart for a completed cloud agent run.

  • Manual: local parent with remote children restores correctly after restart.

  • Unit coverage for child pane materialization and task-driven restore seeding.

  • ./script/format and cargo clippy --workspace --exclude warp_completer --all-targets --tests -- -D warnings clean.

  • I have manually tested my changes locally with ./script/run

Agent Mode

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

@cla-bot cla-bot Bot added the cla-signed label Jul 29, 2026
@cephalonaut
cephalonaut force-pushed the matthew/orch-unified-m2 branch 4 times, most recently from db13489 to 7bb3aa3 Compare July 31, 2026 15:30
@cephalonaut
cephalonaut marked this pull request as ready for review July 31, 2026 21:49
@oz-for-oss

oz-for-oss Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

@cephalonaut

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 extends the orchestration unified stack for child pane materialization, viewer-side transcript loading, observer restoration, and completed-child continuation behavior.

Concerns

  • The unified viewer child join-failure recovery path is gated on is_viewing_shared_session(), but unified viewer children are now represented as remote-child conversations, so missing/inaccessible child live sessions can fail to recover.
  • The PR changes user-facing child-pane behavior and adds a visible unavailable-session state, but the PR description does not include screenshots or a screen recording demonstrating the end-to-end UI.
  • Security pass: no additional security-specific findings beyond the recovery/access-control-adjacent behavior above.
  • Spec check: no approved or repository spec context was provided for implementation drift validation.

Verdict

Found: 0 critical, 2 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

}
let Some(task_id) = BlocklistAIHistoryModel::as_ref(ctx)
.conversation(&child_id)
.filter(|conversation| conversation.is_viewing_shared_session())

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] Unified viewer children are created as remote-child conversations, not is_viewing_shared_session(), so this filter makes OrchestrationChildSharedSessionJoinFailed return before marking the pane unavailable or refetching for transcript recovery. Accept the unified remote-child observer shape here as well.

Comment thread app/src/terminal/view.rs
Flex::column()
.with_child(
Text::new_inline(
"Live session unavailable",

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] For this user-facing change, please include screenshots or a screen recording demonstrating it working end to end.

@cephalonaut
cephalonaut force-pushed the matthew/orch-unified-m2 branch 5 times, most recently from 8dd85d8 to 6c23d40 Compare August 1, 2026 17:26
@cephalonaut
cephalonaut force-pushed the matthew/orch-unified-m1 branch from d957cd3 to cedb591 Compare August 2, 2026 20:03
…fication

Wires M1's tracker into the client pane group and conversation model:

- OrchestrationViewerModel: uses AgentConversationsModel as fetch authority;
  pending_task_ids_for_discovery drain path; drain_pending_task_discoveries.
- Unified child pane materialization: apply_child_pane_materialization,
  attach_ambient_orchestration_child_session, hydrate_child_transcript, and
  process_pending_child_hydrations replace the parallel owner/viewer paths.
  ChildPaneOrigin dropped from the pending map; stale-session guard universal.
- new_for_ambient_orchestration_child: TerminalManager constructor combining
  is_ambient_agent=true with orchestration_child_conversation_id=Some for
  FailedToJoin recovery routing on all child panes.
- TaskOwnership/TaskScope removed; simple creator.uid check used instead.
- Dead code removed: create_orchestration_child_shared_session_viewer,
  new_for_orchestration_child, ChildPaneOrigin enum.
- EnsureSharedSessionViewerChildPane handler simplified (flag-ON branch removed).
- Orchestration tracker/streamer tests updated for M2 behavioral changes:
  Started inserts TrackedChild immediately (children non-empty after observe_child).

Co-Authored-By: Oz <oz-agent@warp.dev>
@cephalonaut
cephalonaut force-pushed the matthew/orch-unified-m2 branch from 081761c to 53b8105 Compare August 2, 2026 20:12
cephalonaut and others added 14 commits August 2, 2026 16:52
write_updated_conversation_state now skips the is_viewing_shared_session
early-return when the conversation belongs to the current user (OrchestrationUnifiedStack only).

This makes the parent conversation's AIConversationId stable across restarts:
children's persisted parent_conversation_id continues to match, so the
initialize_historical_conversations indexing pass rebuilds children_by_parent
correctly on startup, restoring the pill bar without any changes to the
restore path itself.

Co-Authored-By: Oz <oz-agent@warp.dev>
…ned cloud runs

When the parent cloud agent conversation is now persisted locally (b388b3a),
resolve_entry_open_action returns RestoreOrNavigateToConversation instead of
OpenConversationTranscriptViewer because has_local_persisted_data is now true.
The previous handler for that arm created a zero-state compose pane.

Fix: fall through to transcript loading using task.conversation_id() as the
server token, matching the OpenConversationTranscriptViewer path. Also extract
restore_pane_with_transcript to reduce duplication between the two arms.

Co-Authored-By: Oz <oz-agent@warp.dev>
Document Fix 2 (seed child conversations from AmbientAgentTask.children at
restore time) alongside the existing persistence fix, plus the loading-pane
regression fix that shipped with it.

Covers the two cases the SQLite-based persistence fix cannot handle: the WASM
web client (no local_fs feature, so no cross-session agent_conversations
table) and the first restore of a run whose parent was never persisted, which
also needs an explicit parent-link reconciliation for its stale child rows.

Co-Authored-By: Oz <oz-agent@warp.dev>
Rebuild the parent to child conversation index for a restored cloud agent
parent from the server-reported `task.children` list instead of relying on
the local conversation index. This is the only pill-bar source on clients
without cross-session SQLite (web) and on the first restore of a run whose
parent was never persisted locally.

`load_data_into_restored_ambient_cloud_mode_view` now returns the parent's
local `AIConversationId` so both `&mut self` call sites can drive the new
seeding entry point without re-entering the view. Parents whose task (or a
child's task) is still being fetched are held in `pending_parent_child_seeds`
and re-driven from the existing `TasksUpdated` subscription.

All new behavior is gated behind `OrchestrationUnifiedStack` and degrades to
current behavior when `task.children` is empty.

Co-Authored-By: Oz <oz-agent@warp.dev>
1. Hoist terminal-surface lookup before the child loop in
   seed_child_conversations_from_task — logs one warn instead of N,
   and correctly leaves the entry pending when the surface isn't
   available rather than retrying per-child on every TasksUpdated.

2. Clean up pending_parent_child_seeds in remove_child_agent_panes
   so closed parent panes no longer fire the re-drive subscription.

3. Guard RestoreOrNavigateToConversation against double-open: if the
   conversation already has a terminal surface, discard the extra
   loading pane instead of loading the transcript onto a second surface.

Co-Authored-By: Oz <oz-agent@warp.dev>
Co-Authored-By: Oz <oz-agent@warp.dev>
Proposes replacing DB-persisted parent restore (TECH-parent-restore.md
Fix 1/Fix 2) with a restore path driven entirely by server task data,
addressing BUG-2 and removing the stale-parent reconciliation needed
under persistence.

Co-Authored-By: Oz <oz-agent@warp.dev>
…gent conversations

Instead of persisting the parent cloud agent conversation to give it a
stable local ID across restarts, drive restore entirely from server task
data. The pane snapshot's task_id is the stable anchor; transcript and
child conversations are seeded on restore via server APIs already wired
on the client.

Removes:
- is_owned_cloud_agent_conversation + write_updated_conversation_state carve-out (Fix 1)
- RestoreOrNavigateToConversation arm in ambient_pane_restoration (now unreachable)
- ensure_remote_child_conversation stale-parent reconciliation (BUG-1 fix, no longer needed)

Modifies:
- seed_child_conversations_from_task (Fix 2): repoints from task.children
  (server never returns it) to list_ambient_agent_tasks(ancestor_run_id=task_id),
  the same query the OVM ancestor seed uses successfully

Co-Authored-By: Oz <oz-agent@warp.dev>
The live-session rejoin path creates a fresh viewer conversation ID each
time it joins. Children persisted from primary-mode operation retain their
previous parent ID, so the pill bar finds nothing when the pill bar
queries children_by_parent[new_id]. Re-index the child under the new
parent when the recorded parent differs.

This fix is needed even under the task-driven restore approach: task-driven
eliminates stale pointers for completed-session restore (no persistence),
but not for live-session restore where children are still persisted.

Co-Authored-By: Oz <oz-agent@warp.dev>
Remote child conversations are re-created from server task data on every
restore via seed_child_conversations_from_task (ancestor_run_id query).
Persisting them produces stale parent_conversation_id pointers that
require reconciliation on each live-session rejoin — simpler not to
persist at all.

Also reverts the stale-parent reconciliation in
ensure_remote_child_conversation, which is no longer needed since no
stale DB entries can accumulate.

Co-Authored-By: Oz <oz-agent@warp.dev>
Reverts the non-persistence of remote children (previous commit): without
DB-persisted children the pill bar renders empty on restore, because the
async ancestor_run_id seed hasn't returned yet when the first render fires.

Instead, fix the root cause properly:
- index_child_conversation now removes the child from its old parent entry
  before inserting under the new parent, preventing double-listing after a
  live-session rejoin.
- ensure_remote_child_conversation re-calls set_parent_for_conversation
  when the persisted parent ID differs from the requested one (same guard
  as before, but now the underlying index stays consistent).

Co-Authored-By: Oz <oz-agent@warp.dev>
Traces the notification chain from finish_seed_child_conversations_from_task
through ensure_remote_child_conversation to OrchestrationPillBar's model
subscription, and documents why the current implicit reliance on
StartedNewConversation is fragile. Recommends adding
ConversationServerTokenAssigned to the pill bar's subscription as the
minimal, robust fix, with parallelizing the ancestor-list fetch as an
optional follow-on latency improvement.

Co-Authored-By: Oz <oz-agent@warp.dev>
cephalonaut and others added 13 commits August 3, 2026 14:46
…hildren

Two changes that together make the task-only restore approach work:

1. OrchestrationPillBar now re-renders on ConversationServerTokenAssigned.
   This is the event emitted when assign_run_id_for_conversation links a
   remote child to its run id — i.e. when seed_child_conversations_from_task
   creates the child conversation after the ancestor-list fetch returns.
   StartedNewConversation was incidentally triggering a re-render for new
   children, but fell silent for the idempotent case where the conversation
   already existed. ConversationServerTokenAssigned fires in both cases.

2. Remote child placeholder conversations are no longer persisted to DB.
   With the notification fix in place, seed_child_conversations_from_task
   always restores the children_by_parent index after restore, making DB
   persistence of child conversations unnecessary. Not persisting avoids
   stale parent_conversation_id pointers (which accumulate because the
   parent is also not persisted and gets a fresh ID on each live-session join).

Co-Authored-By: Oz <oz-agent@warp.dev>
Reverts the non-persistence of remote children — this caused problems.
Children are needed in DB for immediate pill bar rendering at startup
(before the async ancestor-list seed returns) and for child pane restore.

The pill bar ConversationServerTokenAssigned fix from the previous commit
is kept: this ensures the pill bar re-renders after the seed completes for
both the cold-start case (new conversations) and the idempotent case
(existing conversations found via conversation_id_for_agent_id).

Also restores the stale-parent reconciliation in
ensure_remote_child_conversation: needed because children are persisted
with the viewer's previous parent conversation ID, which changes on each
live-session rejoin.

Co-Authored-By: Oz <oz-agent@warp.dev>
Traces every place that currently depends on is_remote_child=true rows
being persisted to DB: initialize_historical_conversations' synchronous
bulk-load at boot (the sole source of children_by_parent + task_id for
local parents), child_conversation_ids_of callers, the
EnteredAgentView-triggered restore_missing_child_agent_panes_for_parent
path (no pending/retry, unlike the ambient path), RestoredAgentConversations
(a lazy DB-backed cache, not a discovery mechanism), and confirms the
conversation list already excludes children independent of persistence.

Root-causes the local-agent/remote-children regression: local parents
never call seed_child_conversations_from_task today, so removing child
persistence leaves no discovery mechanism for that scenario. Recommends
wiring the same ancestor-list seed into the local-parent restore trigger,
unifying local and ambient-parent restore onto one mechanism.

Co-Authored-By: Oz <oz-agent@warp.dev>
Unifies local-parent and cloud-agent-parent restore onto the same
ancestor-list-seed mechanism:

- restore_missing_child_agent_panes_for_parent now calls
  seed_child_conversations_from_task when a parent has no locally-known
  children but does have its own task_id, matching the discovery path
  already used for ambient/viewer parents.
- write_updated_conversation_state no longer persists is_remote_child
  conversations; they are always rediscovered on restore via the
  ancestor-list seed, so a persisted row only risked going stale.
- ensure_remote_child_conversation's stale-parent reconciliation branch
  is reverted to a simple early return: with no persisted child rows to
  reconcile against a prior session's parent id, the mismatch it handled
  can no longer occur.

Co-Authored-By: Oz <oz-agent@warp.dev>
…child is set

start_new_child_conversation unconditionally persisted the freshly-created
child conversation before ensure_remote_child_conversation's follow-up
calls (mark_conversation_as_remote_child, assign_run_id_for_conversation)
had a chance to set is_remote_child/run_id. This wrote a garbage DB row
with is_remote_child=false and run_id=None, which then permanently blocked
all subsequent, correct persists of that conversation via the
is_remote_child guard in write_updated_conversation_state.

On restart, initialize_historical_conversations loaded this garbage row,
and restore_missing_child_agent_panes_for_parent tried to materialize a
child pane from it with no task_id, producing an empty compose pane
instead of the real transcript.

Gate the initial persist behind OrchestrationUnifiedStack: under the
unified stack, ensure_remote_child_conversation always follows up with
assign_run_id_for_conversation (which persists correctly-tagged remote
children, and is a no-op for the ones that must stay unpersisted), so the
premature persist is unnecessary. Flag-off local child agents have no
such follow-up call and still persist immediately as before.

Co-Authored-By: Oz <oz-agent@warp.dev>
Adds an is_remote parameter to start_new_child_conversation, applied
before its first persist_conversation_state call, so a remote child's
very first potential DB write already has is_remote_child=true and is
correctly skipped by the guard in write_updated_conversation_state.
Previously is_remote_child was only set by a separate, later call
(mark_conversation_as_remote_child), so the first persist could race
ahead and write a garbage row (is_remote_child=false, run_id=None) that
then permanently blocked all later, correct persists of that
conversation via the same guard.

Updates all four call sites: ensure_remote_child_conversation and
launch_remote_child (both remote children) now pass is_remote=true;
create_hidden_child_agent_conversation and the flag-off viewer child
discovery path pass is_remote=false. launch_remote_child is the live
run_agents dispatch path and had the identical bug pattern independent
of ensure_remote_child_conversation, so it needed the same fix.

Co-Authored-By: Oz <oz-agent@warp.dev>
restore_missing_child_agent_panes_for_parent's local-parent ancestor-list
seed trigger fired unconditionally whenever child_ids was empty. Since
finish_seed_child_conversations_from_task calls this same function after
every seed completion (including ones that legitimately resolve to zero
children), a parent with no children caused it to immediately re-seed,
whose completion re-called this function, looping indefinitely.

Adds a trigger_seed_if_empty parameter: true from entry points that are
not downstream of a seed completion (EnteredAgentView,
restore_missing_child_agent_panes_for_terminal_pane_if_needed,
ensure_hidden_child_agent_pane_for_conversation), false from
finish_seed_child_conversations_from_task itself, so a zero-children
result there does not re-trigger its own seed.

Co-Authored-By: Oz <oz-agent@warp.dev>
restore_missing_child_agent_panes_for_parent's local-parent ancestor-list
seed only fired when child_ids was empty. A parent with both persisted
local children (still loaded via initialize_historical_conversations)
and not-yet-discovered remote children has a non-empty child_ids from
the local children alone, so the seed never fired and the remote
children were never discovered.

Trigger the seed whenever none of the currently-known children are
remote (child_ids empty, or all local), rather than only when child_ids
is empty outright. Once the seed discovers at least one remote child,
this naturally stops re-firing.

Co-Authored-By: Oz <oz-agent@warp.dev>
Design specs are kept at ~/src/child-agent-started-events/ outside
the repo to keep the M2 diff free of spec noise.

Co-Authored-By: Oz <oz-agent@warp.dev>
Co-Authored-By: Oz <oz-agent@warp.dev>
…sisted

Two related fixes to restore pre-flag behavior for the flag-off path:

1. write_updated_conversation_state: gate the is_remote_child early-return
   behind OrchestrationUnifiedStack so remote children ARE persisted when
   the flag is off. Previously this guard prevented assign_run_id_for_conversation
   from saving the run_id, causing task_id=None on restore -> empty transcript
   and Unknown attribution.

2. launch_remote_child: pass is_remote=false when the flag is off, so
   start_new_child_conversation does not call mark_as_remote_child before
   the first persist.

Co-Authored-By: Oz <oz-agent@warp.dev>
@cephalonaut

Copy link
Copy Markdown
Contributor Author

Demo: https://www.loom.com/share/5feec7c256044f03a5d63d2c46fe4a75

Demonstrates: pill bar restore after cloud agent restart, local parent with remote child restore, and flag-off behavior verified against pre-M1 baseline.

Master added execution_location to AmbientAgentTask after M2 branched.
CI tests the merge commit (M2 + master), so the struct literals in our
tracker and streamer tests need this field.

Co-Authored-By: Oz <oz-agent@warp.dev>
@danielpeng2

Copy link
Copy Markdown
Member

Heads-up on a merge-order interaction (posted by Daniel's Warp Agent on his behalf):

#14762 (multi-level orchestration: depth-capable client core + drill-down pill bar) is landing ahead of this PR, and it rewrites the pill-bar render region this branch touches — the visible row now renders direct children only, with deeper levels reached by drilling into a pill (replacing the flattened descendant_conversations_in_pill_order rendering).

What we did to keep your rebase trivial: we absorbed this branch's ConversationServerTokenAssigned re-render arm into #14762's pill-bar subscription (commit 95e96ed106, with a first event-driven re-render test). It was independently correct for the multi-level work too — run-id linkage for remote children arrives asynchronously — so credit for spotting it.

How to resolve when you rebase matthew/orch-unified-m2:

  • app/src/ai/blocklist/agent_view/orchestration_pill_bar.rs: take the master side wholesale. Both of this branch's edits are subsumed — the ConversationServerTokenAssigned arm already exists on master, and the descendant_conversations_in_pill_order statement split targets a line that no longer exists.
  • orchestration_viewer_model_tests.rs: the conflict you'll see there is against newer master generally, not the multi-level stack.
  • descendant_conversations_in_pill_order itself still exists (used by topology internals + TUI export), so nothing else in this branch breaks.

One re-verification note: the manual-testing assumptions here ("pill bar appears after restart" etc.) were made against the flattened-descendants rendering. Post-rebase, restored children appear under drill-down semantics — worth a quick re-check that the restore-seeded pills show up at the level you expect.

The deeper interaction points (unified-stack flag-ON pane path vs the RemoteSubtreeModel in #14763, is_remote at-creation contract, LiveAttach handling per flag path) are with the second PR in Daniel's stack — we'll sync on those separately when you're back.

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.

3 participants