fix(omp): keep parent non-idle while internal task subagents run - #3371
fix(omp): keep parent non-idle while internal task subagents run#3371jasonhnd wants to merge 2 commits into
Conversation
OMP can end the parent model loop while `task` children are still writing. Do not complete the Paseo turn until the subagent index reports no runners. Reconcile successful get_subagents replies so listed ids stay running, and treat absence of a previously listed id as completed. Never-listed lifecycle children stay running so an empty first snapshot cannot fake-idle the parent. Closes getpaseo#2232
OMP emits tool_execution_end for `task` as a dispatch ack, then starts children. Keep the parent call running and settle it from the subagent index once a linked child exists and none remain running. completeTurn force-settles a task that never produced a child. Wire-order tests emit the result before subagent_lifecycle started.
|
| Filename | Overview |
|---|---|
| packages/server/src/server/agent/providers/omp/agent.ts | Adds the idle gate and deferred task settlement, but the settlement predicate can close multi-child tasks during a gap between child lifecycle starts. |
| packages/server/src/server/agent/providers/omp/subagent-index.ts | Adds running/link queries and two-way snapshot reconciliation with protection for lifecycle-only children. |
| packages/server/src/server/agent/providers/omp/cli-runtime.ts | Adds validated get_subagents request handling through the existing JSONL RPC boundary. |
| packages/server/src/server/agent/providers/omp/rpc-types.ts | Adds the get_subagents command and derives the snapshot type from its Zod schema. |
| packages/server/src/server/agent/providers/omp/agent.test.ts | Covers the principal idle-gate and task-card wire ordering, but only exercises one child per task call. |
| packages/server/src/server/agent/providers/omp/subagent-index.test.ts | Covers snapshot disappearance, lifecycle-only children, and linked-child status queries. |
Sequence Diagram
sequenceDiagram
participant OMP
participant Session as OmpAgentSession
participant Index as OmpSubagentIndex
participant Parent as Parent turn
OMP->>Session: task tool_execution_end (dispatch ack)
Session->>Session: Keep task call running
OMP->>Session: child lifecycle started
Session->>Index: Record linked running child
OMP->>Session: child lifecycle completed
Session->>Index: Mark child completed
Session->>Session: Settle when linked child exists and none run
Session->>OMP: get_subagents during idle poll
OMP-->>Session: Running-child snapshots
Session->>Index: Reconcile snapshots
Index-->>Session: Any child still running?
alt child running
Session->>Parent: Keep turn active
else no child running
Session->>Parent: Complete turn
end
Reviews (1): Last reviewed commit: "fix(omp): hold task cards open until lin..." | Re-trigger Greptile
| private settleDeferredTaskCalls(): void { | ||
| const pendingIds = Array.from(this.deferredTaskResults.keys()); | ||
| for (const toolCallId of pendingIds) { | ||
| if ( | ||
| this.subagentIndex.hasLinkedChild(this.runtimeSession, toolCallId) && | ||
| !this.subagentIndex.hasRunningLinkedTo(this.runtimeSession, toolCallId) | ||
| ) { | ||
| this.finalizeDeferredTask(toolCallId); | ||
| } | ||
| } |
There was a problem hiding this comment.
Premature multi-child task settlement
When one task launches multiple children and an earlier child completes before a later child emits started, this predicate sees a linked child with none currently running and finalizes the task. The later child then cannot update the removed task card, and the idle poll can complete the parent turn while that child is starting.
Knowledge Base Used: Agent Orchestration
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fa9fc5e624
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| state.description = snapshot.description ?? snapshot.assignment ?? state.description; | ||
| state.toolCallId = snapshot.parentToolCallId ?? state.toolCallId; | ||
| state.status = mapSnapshotStatus(snapshot.status); | ||
| events.push(this.upsert(snapshot.id, state.status, state)); |
There was a problem hiding this comment.
Suppress unchanged subagent snapshot upserts
When an idle parent has a running task child, completeTurnAfterProviderIdle polls every 10 ms and each successful get_subagents response reaches this unconditional events.push, even when no snapshot field changed. AgentManager dispatches every resulting upsert to connected clients, so one child can generate roughly 100 redundant updates per second—and batch tasks multiply that traffic—for the child's entire runtime. Emit only when the snapshot changes or when an ID disappears.
Useful? React with 👍 / 👎.
Linked issue
Closes #2232
Replaces #2245. That PR had the right idle-gate direction, but review found two defects this rewrite addresses. Do not merge #2245.
Type of change
What does this PR do
OMP can end the parent model loop (
agent_end+!isStreaming && !isCompacting) while internaltaskchildren are still writing. Paseo then marks the parent idle/stopped even though work continues.This PR:
completeTurnuntil the subagent index reports no running children.get_subagentsreplies list only still-running children. An id that previously appeared and is now missing is treated as completed. A child seen only via lifecycle (never in a snapshot) is not killed by an empty first reply. Ifget_subagentsis unavailable, fall back to the event index.tasktool_execution_endis a dispatch ack; children start later. Keep the parent call running and settle it from the index once a linked child exists and none remain running.completeTurnforce-settles a task that never produced a child. Failed tasks still complete immediately.Wire-order tests emit
tool_execution_endbeforesubagent_lifecycle: started, matching OMP v17.Out of scope
persistence.nativeHandlepointing at a missing.jsonlwhile the real artifact is a same-stem directory. That is the other half of OMP parent shows idle/stopped while internal task subagents are still active; nativeHandle points at missing .jsonl #2232 and needs a separate change.detachedchildren are included in the idle gate. Excluding them would restore the original false-idle for long fan-out audits; including them can pin a later turn. Maintainer call welcome.How did you verify it
Result: 19 files, 127/127 passed, including:
agent_endget_subagentsdoes not complete a never-listed lifecycle childget_subagentsunavailable still honors lifecyclerunningwhen the result precedesstarted, then completes when the child finishesAlso:
npm run lintandnpm run typecheck --workspace=@getpaseo/serveron touched files.I did not dogfood against a live OMP 17 desktop session in this pass.
Review notes
Addresses the two findings on #2245 from @ABorakati:
tool_execution_end.Checklist
npm run typecheckpassesnpm run lintpassesnpm run formatran