Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
9efbdef
QUALITY-928: add unified north-star tech spec
cephalonaut Jul 29, 2026
5afd76d
QUALITY-928: update TECH spec to reflect unified north-star approach
cephalonaut Jul 29, 2026
36aa996
QUALITY-928 M1T1: OrchestrationChildTracker + OrchestrationUnifiedSta…
cephalonaut Jul 29, 2026
9716a2e
QUALITY-928 M1T2: FamilyEvent classification + unified family drain
cephalonaut Jul 29, 2026
1612fd1
QUALITY-928 M1T3: unified is_remote_child placeholder + flag/polling …
cephalonaut Jul 29, 2026
265c53b
orch: create remote-child placeholder on child_agent_started in drain…
cephalonaut Jul 29, 2026
800df5f
Fix pill status badges and child pane hydration for M1 orchestration
cephalonaut Jul 29, 2026
a731680
Merge m1t6 bug fixes: pill status badges and child pane hydration
cephalonaut Jul 29, 2026
6bf5da4
Rename ChildTrackingMode to OrchestrationEventConsumer
cephalonaut Jul 30, 2026
421afdd
Clean up M1 comments: remove spec/milestone refs, tighten wording
cephalonaut Jul 31, 2026
c1f6ea3
Fix metadata-fetch guard not cleared after async cache miss
cephalonaut Jul 31, 2026
8fa8604
Fix: forward child lifecycle events to handle_event_batch in Primary …
cephalonaut Aug 1, 2026
fdfedc9
Address review: plain-language module doc + TODO for is_remote_placeh…
cephalonaut Aug 1, 2026
e279548
Fix #8: Remove conversation_id from TrackedChild and OrchestrationEve…
cephalonaut Aug 1, 2026
1844d8f
Fold M2 TECH spec updates into M1
cephalonaut Aug 1, 2026
654df04
Add ChildPaneMaterialization and decide_child_pane_materialization
cephalonaut Jul 29, 2026
ce5560c
QUALITY-928 M2T2: unified child pane dispatch + converged attach
cephalonaut Jul 29, 2026
639a549
QUALITY-928 M2T4: flag-gate M2 pane dispatch behind OrchestrationUnif…
cephalonaut Jul 29, 2026
a80e6e6
Preserve live child session attach after M2 rebase
cephalonaut Jul 29, 2026
ea79bc3
Add [orchestration-unified-debug] INFO instrumentation for M1 bug inv…
cephalonaut Jul 29, 2026
f8015b0
Fix tracker insertion gap, task-cache staleness, and pane re-drive
cephalonaut Jul 29, 2026
e96248c
Replace pending child panes during live-session transition
cephalonaut Jul 30, 2026
c2f2017
Treat empty remote children as existing agent conversations
cephalonaut Jul 30, 2026
1bf4030
Restore completed owner children as cloud continuations
cephalonaut Jul 30, 2026
f37ef5a
Replace restored owner child panes off-tree
cephalonaut Jul 30, 2026
fcd440a
Instrument restored child follow-up identity transitions
cephalonaut Jul 30, 2026
c669b69
Arm follow-up replay suppression before ordered events
cephalonaut Jul 30, 2026
909010c
Fix shared viewer child materialization
cephalonaut Jul 30, 2026
a2c3a2d
Fix viewer child transcript presentation
cephalonaut Jul 30, 2026
0fdcdd8
Add ownership-aware child capabilities
oz-agent Jul 30, 2026
862d814
Restore observer orchestration hierarchies
cephalonaut Jul 30, 2026
e166c2a
Restore durable observer parent panes
cephalonaut Jul 30, 2026
a432449
Clean up orchestration tracker follow-ups
cephalonaut Jul 31, 2026
2f7d587
Remove [orchestration-unified-debug] instrumentation
cephalonaut Jul 31, 2026
a118137
Remove empty if-let leftover from debug instrumentation removal
cephalonaut Jul 31, 2026
8c97c2b
Extract is_durable_observer_parent to M3
cephalonaut Jul 31, 2026
490360a
Remove dead variables left by debug instrumentation cleanup
cephalonaut Aug 1, 2026
7e5b0d2
Revert incidental comment change in write_updated_conversation_state
cephalonaut Aug 1, 2026
b51a274
Restore blank line removed incidentally in conversation.rs
cephalonaut Aug 1, 2026
fb2f0c3
Address review feedback: dead code, comments, tracker field docs
cephalonaut Aug 1, 2026
4511bce
Fix 4 unused variable warnings
cephalonaut Aug 1, 2026
8cfc413
Fix #8 and #9: remove stamp_conversation_id_for_run, refactor OVM fetch
cephalonaut Aug 1, 2026
6c23d40
Fix compilation errors after #8/#9 rebase cleanup
cephalonaut Aug 1, 2026
4f2a186
Add is_durable_observer_parent: observer-parent restore for owned clo…
cephalonaut Jul 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/src/ai/agent/api/convert_conversation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ pub fn convert_conversation_data_to_ai_conversation(
orchestration_harness_type: None,
parent_conversation_id: None,
is_remote_child: false,
is_durable_observer_parent: false,
root_task_is_optimistic: None,
run_id: None,
autoexecute_override: None,
Expand All @@ -104,6 +105,7 @@ pub fn convert_conversation_data_to_ai_conversation(
orchestration_harness_type: None,
parent_conversation_id: None,
is_remote_child: false,
is_durable_observer_parent: false,
root_task_is_optimistic: None,
run_id: metadata
.ambient_agent_task_id
Expand Down
26 changes: 23 additions & 3 deletions app/src/ai/agent/conversation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,10 @@ pub struct AIConversation {
/// these conversations — the remote worker's own client handles status
/// reporting.
is_remote_child: bool,
/// True when this is an owned cloud parent hosted by a remote driver and
/// observed locally. Unlike `is_viewing_shared_session`, this marker is
/// durable so the local observer cursor and hierarchy can be restored.
is_durable_observer_parent: bool,

/// The last event sequence number observed from the v2 orchestration
/// event log. Used on restore to resume event delivery without
Expand Down Expand Up @@ -411,6 +415,7 @@ impl AIConversation {
orchestration_harness_type: None,
parent_conversation_id: None,
is_remote_child: false,
is_durable_observer_parent: false,
last_event_sequence: None,
orchestration_configs: HashMap::new(),
pinned: false,
Expand Down Expand Up @@ -545,6 +550,7 @@ impl AIConversation {
orchestration_harness_type,
parent_conversation_id,
is_remote_child,
is_durable_observer_parent,
run_id,
autoexecute_override,
last_event_sequence,
Expand Down Expand Up @@ -596,6 +602,7 @@ impl AIConversation {
data.orchestration_harness_type,
parent_conversation_id,
data.is_remote_child,
data.is_durable_observer_parent,
data.run_id,
autoexecute_override,
data.last_event_sequence,
Expand All @@ -613,6 +620,7 @@ impl AIConversation {
None,
None,
false,
false,
None,
AIConversationAutoexecuteMode::default(),
None,
Expand All @@ -622,7 +630,7 @@ impl AIConversation {

Ok(Self {
id,
is_viewing_shared_session: false,
is_viewing_shared_session: is_durable_observer_parent,
is_cli_agent_transcript: false,
task_store,
status,
Expand Down Expand Up @@ -653,6 +661,7 @@ impl AIConversation {
orchestration_harness_type,
parent_conversation_id,
is_remote_child,
is_durable_observer_parent,
last_event_sequence,
orchestration_configs: HashMap::new(),
pinned,
Expand Down Expand Up @@ -683,6 +692,14 @@ impl AIConversation {
self.is_viewing_shared_session = is_viewing_shared_session;
}

pub fn is_durable_observer_parent(&self) -> bool {
self.is_durable_observer_parent
}

pub fn set_is_durable_observer_parent(&mut self, durable: bool) {
self.is_durable_observer_parent = durable;
}

pub fn is_cli_agent_transcript(&self) -> bool {
self.is_cli_agent_transcript
}
Expand Down Expand Up @@ -3477,8 +3494,10 @@ impl AIConversation {
&mut self,
ctx: &mut ModelContext<BlocklistAIHistoryModel>,
) {
// We should not persist non-local conversations (e.g. shared sessions).
if self.is_viewing_shared_session {
// Passive shared-session views remain ephemeral. Owned cloud parents
// are the narrow exception: their local observer cursor and child
// hierarchy must survive restart.
if self.is_viewing_shared_session && !self.is_durable_observer_parent {
return;
}

Expand Down Expand Up @@ -3546,6 +3565,7 @@ impl AIConversation {
orchestration_harness_type: self.orchestration_harness_type.clone(),
parent_conversation_id: self.parent_conversation_id.map(|id| id.to_string()),
is_remote_child: self.is_remote_child,
is_durable_observer_parent: self.is_durable_observer_parent,
// Legacy field; retained for backward-compatible
// deserialization but no longer written. The optimistic-root
// case is now handled by `Task::source_for_persistence`
Expand Down
53 changes: 53 additions & 0 deletions app/src/ai/agent_conversations_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1719,6 +1719,59 @@ impl AgentConversationsModel {
}
}

/// Updates a cached task to reflect that execution has started and its
/// session is now known (from a `run_session_linked` wire event). If the
/// task is not yet cached, starts a fetch to retrieve it.
///
/// Mutating the cache entry directly avoids a full round-trip while still
/// giving `decide_child_pane_materialization` the `InProgress` +
/// `is_sandbox_running=true` + `session_id` it needs to return `AttachLive`
/// on the next pill click. `TasksUpdated` is emitted so any pending
/// re-drives fire immediately.
pub fn update_task_as_running_with_session(
&mut self,
task_id: &AmbientAgentTaskId,
session_id_str: String,
ctx: &mut ModelContext<Self>,
) {
use crate::ai::ambient_agents::AmbientAgentTaskState;
if let Some(task) = self.tasks.get_mut(task_id) {
task.session_id = Some(session_id_str);
task.is_sandbox_running = true;
// Only promote to InProgress if still in a queued/pending state;
// never downgrade a terminal state that may have arrived concurrently.
match task.state {
AmbientAgentTaskState::Queued
| AmbientAgentTaskState::Pending
| AmbientAgentTaskState::Claimed => {
task.state = AmbientAgentTaskState::InProgress;
}
_ => {}
}
ctx.emit(AgentConversationsModelEvent::TasksUpdated);
} else {
// Task not cached yet; start a fetch.
self.async_fetch_task(task_id, ctx);
}
}

/// Evicts a task from the cache and immediately starts a fresh
/// `GET /agent/runs/{id}` fetch. Used by the family drain when a terminal
/// lifecycle event arrives for a child whose cached state is stale (e.g.
/// still shows `Queued` from the initial discovery fetch). The refreshed
/// data — including the server conversation token and terminal state —
/// enables `decide_child_pane_materialization` to return `LoadTranscript`
/// so subsequent pill clicks load the cloud transcript.
pub fn evict_and_refetch_task(
&mut self,
task_id: &AmbientAgentTaskId,
ctx: &mut ModelContext<Self>,
) {
self.tasks.remove(task_id);
self.task_fetch_state.remove(task_id);
self.async_fetch_task(task_id, ctx);
}

/// Get raw task data by task ID, fetching from server if not in memory.
/// If the task is already in memory, returns it immediately.
/// If not, spawns an async task to fetch it from the server, stores it in memory,
Expand Down
15 changes: 15 additions & 0 deletions app/src/ai/agent_conversations_model_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ fn create_test_task(
display_name: Some(format!("User {creator_uid}")),
}),
executor: None,
scope: None,
conversation_id: None,
request_usage: None,
agent_config_snapshot: None,
Expand Down Expand Up @@ -234,6 +235,7 @@ fn test_title_update_refreshes_shadowing_task_title() {
orchestration_harness_type: None,
parent_conversation_id: None,
is_remote_child: false,
is_durable_observer_parent: false,
root_task_is_optimistic: None,
run_id: None,
autoexecute_override: None,
Expand Down Expand Up @@ -341,6 +343,7 @@ fn test_display_status_uses_matching_conversation_for_in_progress_task() {
orchestration_harness_type: None,
parent_conversation_id: None,
is_remote_child: false,
is_durable_observer_parent: false,
root_task_is_optimistic: None,
run_id: Some(task_id.clone()),
autoexecute_override: None,
Expand Down Expand Up @@ -398,6 +401,7 @@ fn test_display_status_uses_active_execution_over_previous_conversation_status()
orchestration_harness_type: None,
parent_conversation_id: None,
is_remote_child: false,
is_durable_observer_parent: false,
root_task_is_optimistic: None,
run_id: Some(task_id.clone()),
autoexecute_override: None,
Expand Down Expand Up @@ -462,6 +466,7 @@ fn test_display_status_updates_when_blocked_conversation_resumes() {
orchestration_harness_type: None,
parent_conversation_id: None,
is_remote_child: false,
is_durable_observer_parent: false,
root_task_is_optimistic: None,
run_id: Some(task_id.clone()),
autoexecute_override: None,
Expand Down Expand Up @@ -542,6 +547,7 @@ fn test_display_status_terminal_task_state_overrides_matching_conversation() {
orchestration_harness_type: None,
parent_conversation_id: None,
is_remote_child: false,
is_durable_observer_parent: false,
root_task_is_optimistic: None,
run_id: Some(task_id.clone()),
autoexecute_override: None,
Expand Down Expand Up @@ -597,6 +603,7 @@ fn test_status_filter_uses_display_status_for_task_backed_conversations() {
orchestration_harness_type: None,
parent_conversation_id: None,
is_remote_child: false,
is_durable_observer_parent: false,
root_task_is_optimistic: None,
run_id: Some(task_id.clone()),
autoexecute_override: None,
Expand Down Expand Up @@ -1052,6 +1059,7 @@ fn test_get_entries_excludes_conversation_shadowed_by_child_task() {
orchestration_harness_type: None,
parent_conversation_id: None,
is_remote_child: false,
is_durable_observer_parent: false,
root_task_is_optimistic: None,
run_id: None,
autoexecute_override: None,
Expand Down Expand Up @@ -1202,6 +1210,7 @@ fn test_get_entries_merges_task_and_local_conversation_by_run_id() {
orchestration_harness_type: None,
parent_conversation_id: None,
is_remote_child: false,
is_durable_observer_parent: false,
root_task_is_optimistic: None,
run_id: Some(task_id.clone()),
autoexecute_override: None,
Expand Down Expand Up @@ -1257,6 +1266,7 @@ fn test_get_entries_merges_task_and_local_conversation_by_server_token() {
orchestration_harness_type: None,
parent_conversation_id: None,
is_remote_child: false,
is_durable_observer_parent: false,
root_task_is_optimistic: None,
run_id: None,
autoexecute_override: None,
Expand Down Expand Up @@ -1467,6 +1477,7 @@ fn test_resolve_open_action_returns_none_for_active_unattachable_session() {
orchestration_harness_type: None,
parent_conversation_id: None,
is_remote_child: false,
is_durable_observer_parent: false,
root_task_is_optimistic: None,
run_id: Some(task_id.clone()),
autoexecute_override: None,
Expand Down Expand Up @@ -1753,6 +1764,7 @@ fn test_server_token_assignment_updates_copy_link_resolution() {
orchestration_harness_type: None,
parent_conversation_id: None,
is_remote_child: false,
is_durable_observer_parent: false,
root_task_is_optimistic: None,
run_id: None,
autoexecute_override: None,
Expand Down Expand Up @@ -1915,6 +1927,7 @@ fn test_resolve_copy_link_uses_attached_synced_conversation_for_task_without_tok
orchestration_harness_type: None,
parent_conversation_id: None,
is_remote_child: false,
is_durable_observer_parent: false,
root_task_is_optimistic: None,
run_id: Some(task_id.clone()),
autoexecute_override: None,
Expand Down Expand Up @@ -2244,6 +2257,7 @@ fn test_get_entries_prefers_task_when_task_id_matches_conversation_run_id() {
orchestration_harness_type: None,
parent_conversation_id: None,
is_remote_child: false,
is_durable_observer_parent: false,
root_task_is_optimistic: None,
run_id: Some(task_id.clone()),
autoexecute_override: None,
Expand Down Expand Up @@ -2305,6 +2319,7 @@ fn test_get_entries_prefers_task_when_server_token_matches() {
orchestration_harness_type: None,
parent_conversation_id: None,
is_remote_child: false,
is_durable_observer_parent: false,
root_task_is_optimistic: None,
run_id: None,
autoexecute_override: None,
Expand Down
3 changes: 2 additions & 1 deletion app/src/ai/ambient_agents/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ pub mod telemetry;

pub use task::{
AgentConfigSnapshot, AgentSource, AmbientAgentLiveSessionState, AmbientAgentTask,
AmbientAgentTaskState, TaskStatusMessage, cancel_task_silently, cancel_task_with_toast,
AmbientAgentTaskState, TaskOwnership, TaskStatusMessage, cancel_task_silently,
cancel_task_with_toast,
};
pub const OUT_OF_CREDITS_TASK_FAILURE_MESSAGE: &str =
"Out of credits. Upgrade your Warp plan to continue running cloud agents.";
Expand Down
1 change: 1 addition & 0 deletions app/src/ai/ambient_agents/spawn_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ fn task_with(
session_link,
creator: None,
executor: None,
scope: None,
conversation_id: None,
request_usage: None,
agent_config_snapshot: None,
Expand Down
Loading