Skip to content

fix(agents): parent resumes after yielded child handoff#110922

Merged
steipete merged 4 commits into
mainfrom
codex/fix-subagent-handoff
Jul 18, 2026
Merged

fix(agents): parent resumes after yielded child handoff#110922
steipete merged 4 commits into
mainfrom
codex/fix-subagent-handoff

Conversation

@steipete

Copy link
Copy Markdown
Contributor

Closes #110572

What Problem This Solves

Fixes an issue where a parent agent that yielded after receiving a child completion could remain stalled instead of resuming to send its final answer. The failure was timing-dependent and could also occur while completion delivery was still in progress.

Why This Change Was Made

The registry now records the exact requester attempt for completion-producing children, persists explicit yield intent before aborting that attempt, and retains the child rows until requester settlement. Yielded batches are durably re-armed with generation-guarded wake state so stale delivery work cannot erase a newer wake. Only completions already delivered or in progress when the requester yields request a fresh turn; children that finish later keep the normal single-delivery path, avoiding duplicate wakes. State remains in the existing SQLite payload and does not require a schema-version change.

This is the owner-boundary fix: requester-attempt settlement owns the race, while child delivery and cleanup keep their existing responsibilities. The regression was introduced by #99396; that guard was intentional, but missed the active-requester yield window.

Implementation and review were AI-assisted.

User Impact

Parent agents now reliably resume and deliver their final response after child work finishes, including the narrow race where completion delivery overlaps sessions_yield. Future child completions still produce one wake, not a duplicate.

Release note: Fix parent-agent handoffs stalling after an already-delivered child result when the parent yields.

Evidence

  • Exact-head Blacksmith Testbox: tbx_01kxvat3rnrwnek1se2smzna41, succeeded in 244.252 seconds.
  • 157 focused tests passed across requester settlement, registry lifecycle, persistence, SQLite round-trip, attempt settlement, and sessions_yield.
  • Core and core-test tsgo checks passed.
  • Targeted formatting and lint passed for all 19 touched files.
  • Max-lines ratchet passed.
  • QA subagent-handoff scenario passed 1 of 1 with mock OpenAI transport on the exact branch head.
  • Full changed-surface gate passed earlier in the same branch series: formatting, plugin boundaries, core and core-test types, full core lint shards, database guard, and cycle checks.
  • Fresh branch autoreview reported no accepted or actionable findings.

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: L maintainer Maintainer-authored PR labels Jul 18, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 223a21dd76

ℹ️ 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".

Comment on lines +677 to +681
onBeforeYield:
requesterSessionKey && requesterTurnRunId
? async () => {
const { markRequesterTurnYielded } = await import("./subagent-registry.js");
markRequesterTurnYielded({ requesterSessionKey, requesterTurnRunId });

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.

P1 Badge Use the completion-owner key when marking yield

When runSessionKey differs from agentSessionKey, this marks the policy/sandbox session instead of the session that owns the child rows. This occurs for default dmScope="main" direct messages: attempt-tool-base-prepare.ts passes the peer-scoped runtime-policy key as agentSessionKey and the main transcript key as runSessionKey, while the spawn immediately above stores completion ownership under runSessionKey. Consequently markRequesterTurnYieldedInRuns finds no rows, and the later yielded settlement rejects those rows because requesterTurnYielded was never set, leaving the parent-stall race unfixed. Derive this callback's requester key from options.runSessionKey ?? options.agentSessionKey.

Useful? React with 👍 / 👎.

@steipete
steipete merged commit bf2da1c into main Jul 18, 2026
208 of 211 checks passed
@steipete
steipete deleted the codex/fix-subagent-handoff branch July 18, 2026 19:39
@steipete

Copy link
Copy Markdown
Contributor Author

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 19, 2026
)

* fix(agents): resume handoff after requester yield

* fix(agents): retain handoff through requester settle

* fix(agents): avoid duplicate future-child wake

* fix(agents): rearm in-flight handoff on yield
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling maintainer Maintainer-authored PR size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Subagent handoff stalls after delivered child result

1 participant