Problem
When the main agent starts a workflow with run_workflow, the run continues in the backend, but its completion is delivered back to the agent only by the browser. If the page is closed, unloaded, discarded, or otherwise absent when the run finishes, no automatic completion turn is created. Reopening the project shows the terminal run in history, but the main agent never receives the run ... succeeded/failed message and does not inspect or summarize the result until the user prompts it manually.
This makes autonomous workflow execution depend on a live frontend tab even though both workflow runs and orchestrator turns are otherwise designed to continue independently of their client streams.
Reproduction
- Open a project's orchestrator chat and have the main agent start a workflow run.
- Wait until the run has started and is visible as running.
- Close/unload the page (or let the tab be discarded) before the workflow reaches a terminal state.
- Wait for the workflow to finish on the backend.
- Reopen the project.
Actual
- Run history shows the run as
success or error.
- The chat has no automatic run-finished notice/follow-up.
- The main agent has no completion message in its persisted history and does not continue from the result.
Expected
The initiating orchestrator session receives a durable completion event/automatic turn regardless of whether any browser is connected. Reopening the project shows the agent's persisted follow-up.
Root cause
The agent notification path is frontend-owned and entirely in memory:
run_workflow creates an orchestrator run, starts it in the background, and immediately returns {run_id, status: "running"}. The persisted Run has workflow provenance but no initiating session/delivery association.
- The frontend receives
run_started from the orchestrator SSE stream and attaches a run WebSocket in orchestratorStream.ts.
- Only the WebSocket
run_finished handler invokes onRunFinished in runWebSocket.ts.
App.tsx keeps the tracked run IDs, dedupe set, pending notices, and drain timers in React refs. Draining the queue starts a new auto_user orchestrator turn; this is the only path that tells the main agent the run finished.
- App unmount closes every run socket and clears the drain timers (
App.tsx).
- On reload,
restoreActiveRuns attaches only runs still marked running/pending (App.tsx). A run that finished while the page was absent is terminal, so the client never attaches to replay its run_finished backlog and never reconciles an undelivered completion.
The runner's replayable WebSocket backlog therefore does not solve this case: no subscriber is created for an already-terminal run, and there is no durable "completion delivered to session" state to reconcile.
Proposed direction
Move orchestrator-run completion delivery behind a durable backend boundary; keep the browser WebSocket responsible only for live visualization and OS notifications.
- Persist which orchestrator session initiated each
kind="orchestrator" run (or create a durable run-completion inbox keyed by session and run).
- Persist notification state/idempotency so each terminal run is delivered effectively once across page reloads, WebSocket reconnects, and backend retries.
- When the runner persists a terminal status, enqueue the same automatic run-finished turn currently created by
drainRunNotices.
- Serialize delivery with any active turn for that session instead of superseding it; retain multiple completion events until they can be drained.
- Reconcile terminal-but-undelivered runs after process restart and/or when session history is loaded.
- Preserve the current semantics for success, failure, and explicit user cancellation.
Acceptance criteria
- A workflow started by the main agent can finish with no page/tab connected, and the initiating session still receives and persists the automatic completion turn.
- Reopening the project shows the run notice and the main agent's follow-up without a manual prompt.
- A run produces no duplicate agent completion turns after reconnect, reload, retry, or reconciliation.
- Multiple outstanding runs for one session are delivered without one completion superseding another or an active user turn.
- Success, error, and explicit user-cancel terminal states follow the current notification semantics.
- Add backend coverage for page/client absence, idempotent delivery, multiple queued completions, active-turn serialization, and restart/reconciliation.
Related
Problem
When the main agent starts a workflow with
run_workflow, the run continues in the backend, but its completion is delivered back to the agent only by the browser. If the page is closed, unloaded, discarded, or otherwise absent when the run finishes, no automatic completion turn is created. Reopening the project shows the terminal run in history, but the main agent never receives therun ... succeeded/failedmessage and does not inspect or summarize the result until the user prompts it manually.This makes autonomous workflow execution depend on a live frontend tab even though both workflow runs and orchestrator turns are otherwise designed to continue independently of their client streams.
Reproduction
Actual
successorerror.Expected
The initiating orchestrator session receives a durable completion event/automatic turn regardless of whether any browser is connected. Reopening the project shows the agent's persisted follow-up.
Root cause
The agent notification path is frontend-owned and entirely in memory:
run_workflowcreates anorchestratorrun, starts it in the background, and immediately returns{run_id, status: "running"}. The persistedRunhas workflow provenance but no initiating session/delivery association.run_startedfrom the orchestrator SSE stream and attaches a run WebSocket inorchestratorStream.ts.run_finishedhandler invokesonRunFinishedinrunWebSocket.ts.App.tsxkeeps the tracked run IDs, dedupe set, pending notices, and drain timers in React refs. Draining the queue starts a newauto_userorchestrator turn; this is the only path that tells the main agent the run finished.App.tsx).restoreActiveRunsattaches only runs still markedrunning/pending(App.tsx). A run that finished while the page was absent is terminal, so the client never attaches to replay itsrun_finishedbacklog and never reconciles an undelivered completion.The runner's replayable WebSocket backlog therefore does not solve this case: no subscriber is created for an already-terminal run, and there is no durable "completion delivered to session" state to reconcile.
Proposed direction
Move orchestrator-run completion delivery behind a durable backend boundary; keep the browser WebSocket responsible only for live visualization and OS notifications.
kind="orchestrator"run (or create a durable run-completion inbox keyed by session and run).drainRunNotices.Acceptance criteria
Related
run_agentSSE streams. This issue is different: full workflow completion is currently converted into an orchestrator turn only by frontend code, so there is no backend delivery to reconnect to when the page was absent at completion time.