Skip to content

Subagent orchestration can mark dependent work complete without waiting for spawned plan/review agents #30

@nukk-pain

Description

@nukk-pain

Summary

LazyCodex / OMO subagent orchestration can spawn planning, review, or audit agents and then move dependent work forward without collecting or integrating their result.

The user-visible failure is that root agents appear to "send a plan/review agent and naturally move on" instead of enforcing the documented short wait_agent polling + follow-up + inconclusive/respawn behavior before marking dependent work complete.

Environment

  • LazyCodex version: lazycodex-ai 4.7.5
  • Codex version: codex-cli 0.137.0
  • OS: macOS 26.5.1 (Build 25F80)
  • Install method: npx lazycodex-ai / local OMO plugin cache
  • Relevant config: OMO plugin enabled; bundled rules inject Hephaestus / ultrawork / review-work guidance. Relevant skill docs:
    • skills/review-work/SKILL.md: says plan/reviewer agents should be spawned in background, root should poll with short wait_agent cycles, follow up after two waits with no substantive result, and not count silent/ack-only lanes as pass.
    • components/ultrawork/directive.md: same subagent reliability guidance.

Reproduction

This was observed from real LazyCodex session logs, not a synthetic repro.

  1. Run a multi-step task that spawns a dependent audit/research/planning subagent.
  2. Inspect the resulting session JSONL for spawn_agent, wait_agent, send_message/followup_task, close_agent, and update_plan ordering.
  3. Observe cases where the root agent marks dependent steps complete, starts implementation, runs tests, or writes a plan without waiting for spawned subagents.

Expected Behavior

After spawning a subagent whose deliverable informs a dependent decision, root should:

  1. Continue only genuinely independent work while the child runs.
  2. Poll with wait_agent short cycles.
  3. After two non-substantive waits, send a targeted heartbeat/follow-up such as TASK STILL ACTIVE: return <deliverable> or BLOCKED: <reason>.
  4. Integrate the child result, or mark the lane inconclusive and close/respawn smaller.
  5. Only then mark the dependent plan step complete, begin dependent implementation, approve/finalize, or generate a final plan.

Actual Behavior

Observed timelines show dependent transitions before child results are collected.

Case 1: audit subagent spawned, dependent implementation proceeds before result

Session:
~/.codex-lazycodex/sessions/2026/06/05/rollout-2026-06-05T16-03-00-019e9697-a1ad-7163-9e9f-7b1e0e73f271.jsonl

Relevant timeline:

2026-06-05T07:20:50Z update_plan: "부위 선택 정의와 저장 규칙의 전체 감사 기준 수립" in_progress
2026-06-05T07:21:47Z spawn_agent body_region_audit
2026-06-05T07:22:16Z update_plan: "앞서 언급한 의심 부위와 유사 앞/뒤 미분리 사례 확인" completed; "필요한 회귀 테스트/수정 적용" in_progress
2026-06-05T07:22:59Z exec_command: npm run test:run ...
2026-06-05T07:25:34Z exec_command: next dev --turbo -p 3002
2026-06-05T07:25:49Z exec_command: Playwright E2E / manual browser QA
2026-06-05T07:24:34Z wait_agent timeout_ms=30000
2026-06-05T07:26:33Z close_agent body_region_audit
2026-06-05T07:26:39Z update_plan: all steps completed

The audit agent was supposed to find body-region coverage issues "similar to neck front/back not being distinguishable." The root moved into dependent implementation/testing before collecting and integrating that audit result.

Case 2: planning/research agents spawned, no wait/follow-up/close in the session

Session:
~/.codex-lazycodex/sessions/2026/06/05/rollout-2026-06-05T17-15-48-019e96da-49f1-76b3-9796-956e482e42bd.jsonl

Relevant timeline:

2026-06-05T08:16:28Z spawn_agent internal_thread_usage
2026-06-05T08:16:28Z spawn_agent internal_emr_sync
2026-06-05T08:16:29Z spawn_agent internal_schema_ownership
2026-06-05T08:16:29Z spawn_agent internal_tests
2026-06-05T08:16:29Z spawn_agent external_graphql_sync
2026-06-05T08:16:29Z spawn_agent external_postgres_migrations
2026-06-05T08:19:02Z spawn_agent gap_analysis
2026-06-05T08:21:53Z exec_command: mkdir -p .omo/plans .omo/evidence
2026-06-05T08:25:18Z exec_command: verifies .omo/plans/emr-patient-thread-sync-legacy-archive.md exists and has tasks

The same session contains no wait_agent, followup_task, send_message heartbeat, or close_agent calls for these seven spawned agents. This means the plan can be generated/verified without the required research/metis lanes being integrated.

Contrasting case: wait loop behavior exists but is not consistently enforced

Session:
~/.codex-lazycodex/sessions/2026/06/05/rollout-2026-06-05T10-40-35-019e9570-7330-7ff1-a152-926a5c67974b.jsonl

Relevant timeline:

2026-06-05T03:25:13Z spawn_agent operation_registry_plan_agent
2026-06-05T03:26:06Z wait_agent timeout_ms=10000
2026-06-05T03:26:13Z wait_agent timeout_ms=10000
2026-06-05T03:26:44Z wait_agent timeout_ms=10000
2026-06-05T03:26:57Z send_message TASK STILL ACTIVE ...
2026-06-05T03:27:01Z wait_agent timeout_ms=10000
2026-06-05T03:27:13Z close_agent operation_registry_plan_agent previous_status=running
2026-06-05T03:27:21Z spawn_agent operation_registry_ordering_small

This shows the desired pattern is possible in the runtime/tooling, but not reliably applied across sessions.

Evidence

Commands used to confirm the behavior:

jq -r 'select(.type=="response_item" and .payload.type=="function_call" and (.payload.name|IN("spawn_agent","wait_agent","send_message","followup_task","close_agent","update_plan","exec_command"))) | [.timestamp,.payload.name, (.payload.arguments|fromjson? // .payload.arguments)] | @json' ~/.codex-lazycodex/sessions/2026/06/05/rollout-2026-06-05T16-03-00-019e9697-a1ad-7163-9e9f-7b1e0e73f271.jsonl

jq -r 'select(.type=="response_item" and .payload.type=="function_call" and (.payload.name|IN("spawn_agent","wait_agent","send_message","followup_task","close_agent","exec_command"))) | [.timestamp,.payload.name, (.payload.arguments|fromjson? // .payload.arguments)] | @json' ~/.codex-lazycodex/sessions/2026/06/05/rollout-2026-06-05T17-15-48-019e96da-49f1-76b3-9796-956e482e42bd.jsonl

gh issue list --repo code-yeongyu/lazycodex --search "wait_agent" --state open
gh issue list --repo code-yeongyu/lazycodex --search "subagent" --state open
gh issue list --repo code-yeongyu/lazycodex --search "dependent work complete spawned agent" --state open

Duplicate search returned no matching open issue for wait_agent, subagent, reviewer agent, dependent work complete spawned agent, plan review agent wait, or wait_agent close_agent.

Root Cause

Strongly evidenced root cause: the waiting/integration rule is prompt-level guidance, not a runtime guard.

The current guidance tells agents to poll, follow up, close/respawn, and not count silent lanes as PASS, but nothing appears to prevent these unsafe transitions while child agents are still active:

  • marking a plan item completed when a spawned child owns evidence for that item
  • moving from discovery/audit to implementation before the audit lane returns
  • generating/verifying a plan before research/metis lanes return
  • finalizing with open child agents

Alternative hypotheses considered:

  1. The child agents completed but their results were omitted from the sampled timeline. Refuted for Case 2 by the absence of any wait_agent, send_message, followup_task, or close_agent calls in the session.
  2. The root only performed independent work while children ran. Refuted for Case 1 by update_plan marking the dependent audit/discovery step complete and starting implementation/tests before collecting the audit.
  3. wait_agent exists but is unavailable. Refuted by the operation-registry session where wait_agent and heartbeat messages were used.

Proposed Fix

Add a runtime or hook-level subagent barrier for dependent transitions.

Possible implementation approaches:

  • Track active child agents spawned by the current root turn/session.
  • Before update_plan marks a dependent step complete, before final answer, and before plan-generation completion, warn or block if relevant child agents are still active and no result/inconclusive status has been recorded.
  • Provide a lightweight helper state such as spawn_agent(..., blocks_step="...") or infer from spawned task names/messages when no explicit step exists.
  • Add a Stop-hook/PostToolUse check that detects active children at finalization or step completion and injects a hard warning: "active subagents not integrated; wait/close/mark inconclusive first."
  • Normalize guidance to prefer wait_agent polling with visible heartbeat status, not silent progress into dependent work.

Likely affected components:

  • Codex subagent orchestration wrappers / hooks in the OMO plugin
  • review-work and ulw-plan/ultrawork injected guidance
  • Any LazyCodex state layer that can observe spawn_agent, wait_agent, close_agent, and update_plan

Verification Plan

  • Reproduce with a test harness/session that spawns a slow planning/audit agent, then attempts to mark the owning step complete before wait_agent returns.
  • Verify the runtime blocks or warns on the unsafe transition.
  • Verify independent root work is still allowed while the child is active.
  • Verify final answer/plan completion is blocked or warned while child agents remain active.
  • Regression check a valid flow: spawn child, do independent reads, wait_agent, integrate result, close child, then mark dependent step complete.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions