Skip to content

fix(sessions): skip A2A flow for persistent sessions#67278

Closed
Sway-Chan wants to merge 15 commits into
openclaw:mainfrom
Sway-Chan:fix/skip-a2a-for-persistent-sessions
Closed

fix(sessions): skip A2A flow for persistent sessions#67278
Sway-Chan wants to merge 15 commits into
openclaw:mainfrom
Sway-Chan:fix/skip-a2a-for-persistent-sessions

Conversation

@Sway-Chan

@Sway-Chan Sway-Chan commented Apr 15, 2026

Copy link
Copy Markdown

Summary

  • Persistent sessions (spawnMode=session) accumulate internal A2A messages (ping-pong rounds + announce step) as permanent User messages in their transcript
  • These polluted messages are replayed on session resume, causing the agent to process stale internal directives and get stuck
  • Skip the entire A2A/announce flow when the target session is persistent by checking spawnMode from the subagent registry — covering both the sessions_send entry path and the subagent cleanup announce path

Root Cause

runSessionsSendA2AFlow writes all ping-pong and announce messages as permanent User messages into the target session's store. For oneshot sessions this is harmless (session is discarded after use), but for persistent sessions these messages accumulate and are replayed on every resume via session/load.

Additionally, subagent natural completion (not user-triggered sessions_send) routes a completion event through runSubagentAnnounceFlow during cleanup. This second path is short-circuited at startSubagentAnnounceCleanupFlow to enforce the same invariant — without it, a completion event is delivered into the persistent session as an inbound user message and triggers a stray model reply ("Background task ...") that bubbles back to the main session via the announce queue.

Why A2A skip is safe for persistent sessions

Multi-round cross-review workflows are orchestrated by the main agent's explicit sessions_send calls, not by A2A ping-pong. Skipping A2A does not affect any existing workflow.

Test plan

  • Spawn a persistent session, send multiple messages — verify no A2A messages appear in session transcript
  • Verify oneshot sessions still get A2A flow as before
  • Run workflow with persistent agents — verify cross-review still works via main agent orchestration
  • Verify subagent natural completion does not deliver stray "Background task ..." messages into persistent session

🤖 Generated with Claude Code

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: XS labels Apr 15, 2026
@greptile-apps

greptile-apps Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a guard in startA2AFlow inside createSessionsSendTool to skip the A2A (ping-pong + announce) flow when the target session's spawnMode is "session", preventing internal A2A messages from accumulating as permanent User messages in persistent session transcripts. The fix is a minimal two-line addition after looking up the subagent registry entry for the resolved session key.

Confidence Score: 5/5

Safe to merge — the fix is minimal, logically sound, and leaves the spawnMode === 'run' / unregistered-session paths completely unaffected.

The change is two lines that gate on a well-typed, optional registry field. The early return is void-compatible, the registry lookup key matches the existing A2A flow argument, and null/missing entries fall through to the original behavior. No P0/P1 issues identified.

No files require special attention.

Reviews (1): Last reviewed commit: "fix(sessions): skip A2A flow for persist..." | Re-trigger Greptile

@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: f95fceeaa9

ℹ️ 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 thread src/agents/tools/sessions-send-tool.ts Outdated
Persistent sessions (spawnMode=session) accumulate internal A2A messages
(ping-pong rounds + announce step) as permanent User messages in their
transcript. These polluted messages are replayed on session resume,
causing the agent to process stale internal directives and get stuck.

Skip the entire A2A flow when the target session is persistent, since
multi-round cross-review workflows (/X) are orchestrated by the main
agent's explicit sessions_send calls, not by A2A ping-pong.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@Sway-Chan
Sway-Chan force-pushed the fix/skip-a2a-for-persistent-sessions branch from c4185ef to d0db44f Compare April 15, 2026 16:27
…trap test

The vi.fn(() => undefined) inferred as Mock<() => undefined> which is
incompatible with the declared type () => string | undefined.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@openclaw-barnacle openclaw-barnacle Bot added the channel: matrix Channel integration: matrix label Apr 15, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the channel: matrix Channel integration: matrix label Apr 17, 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: 3baf5d125c

ℹ️ 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 thread src/agents/tools/sessions-send-tool.ts Outdated
Sway-Chan and others added 3 commits April 17, 2026 09:21
The early return for persistent sessions skipped the A2A announce flow
but still returned delivery status "pending", misleading orchestration
callers into waiting for a completion signal that would never arrive.

Hoist the spawnMode check before delivery construction so the returned
status is "skipped" when A2A is not attempted.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

@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: c38de15b5c

ℹ️ 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 thread src/agents/tools/sessions-send-tool.ts
Sway-Chan and others added 3 commits April 17, 2026 11:29
…A2A skip

The skipA2A check in sessions-send-tool relies on
getLatestSubagentRunByChildSessionKey() to detect persistent child
sessions. The TTL sweep was removing session-mode records 5 minutes
after cleanupCompletedAt, silently flipping skipA2A back to false and
reintroducing transcript pollution for long-lived sessions.

Session-mode records should outlive the per-round TTL: the child
session is still alive, consistent with shouldKeepThreadBindingAfterRun
which keeps the thread binding for session-mode + non-KILLED runs.
Only sweep once the session is truly terminated (KILLED/ERROR).

Co-Authored-By: Claude Opus 4.7 <[email protected]>
CI oxlint flagged the single-line `if (skipA2A) return;` as violating
the `curly` rule.

Co-Authored-By: Claude Opus 4.7 <[email protected]>

@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: 9fb82275a7

ℹ️ 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 thread src/agents/subagent-registry.ts Outdated
…reset

Without this, a persistent child that has completed its initial round
(endedReason=complete) was kept alive in the registry by the sweep
policy forever: sessions.delete and sessions.reset do not flip the
endedReason to killed (markSubagentRunTerminated skips entries with
endedAt set), so the record would leak indefinitely and could later
misclassify an unrelated sessions_send on the same childSessionKey as
session-mode.

cleanupSessionBeforeMutation is the common path for both delete and
reset, so wire the purge there. The purge is scoped to spawnMode=
"session" to avoid racing with in-flight run-mode entries.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime size: S and removed size: XS labels Apr 17, 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: fa004ef7ab

ℹ️ 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 thread src/gateway/session-reset-service.ts Outdated
Reset keeps the session key and only rotates sessionId/transcript, so
the spawnMode="session" marker that sessions_send uses for skipA2A
must stay in place. Removing it on reset forced later sends on the
same key back into the A2A flow, reintroducing the persistent
transcript pollution this PR is trying to prevent.

Only delete, which retires the key entirely, now triggers the purge.

Co-Authored-By: Claude Opus 4.7 <[email protected]>

@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: 0c8aa8cdba

ℹ️ 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 thread src/agents/subagent-registry.ts Outdated
@Sway-Chan

Copy link
Copy Markdown
Author

CI note: skills.test.ts failure is pre-existing on main

The two red checks on this PR (checks-node-agentic-agents-plugins + checks-node-core aggregator) share a single root cause — the agents-plugins shard matrix entry fails inside the aggregator, propagating to both status rows.

Failing test: src/agents/skills.test.ts:248buildWorkspaceSkillCommandSpecs > includes enabled Claude bundle markdown commands as native OpenClaw slash commands (expects workflows_review, receives []).

Not caused by this PR:

  • This PR touches only src/agents/subagent-registry.ts, src/agents/tools/sessions-send-tool.ts, and src/gateway/session-reset-service.ts — no changes under src/agents/skills/, src/agents/plugins/, or src/plugins/.
  • The same test fails on upstream/main HEAD (commit dfc3f0b3, run 24546894276) with an identical assertion error.
  • Likely regression source: 9da4d5f5 fix(plugins): reuse shared discovery cache / follow-up 35395089, both landed on main after this PR's merge-base and not reverted.

I will file a separate issue tracking the main-branch failure. Requesting reviewer to admin-merge or wait for main fix before re-running — rebasing will not resolve this.

@Sway-Chan

Copy link
Copy Markdown
Author

Tracking issue for the main-branch test failure: #67965.

…endedReason

killSubagentRun marks the run terminated on KILLED/ERROR but does not delete
the session entry, so the child session key remains valid for sessions.send.
Treating KILLED/ERROR as "session terminated" in the sweep guard would drop the
skipA2A marker while the child is still usable, regressing A2A pollution.

Gate session-mode rows out of the per-round TTL entirely; their sole removal
path is purgeSubagentRunsForSessionKey on session-delete.

@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: 2bff02b1b1

ℹ️ 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 thread src/agents/subagent-registry.ts
purgeSubagentRunsForSessionKey only scanned in-memory subagentRuns, but read
paths (getSubagentRunsSnapshotForRead) merge disk state so other worker
processes can observe runs. A session-mode row created by another worker would
stay on disk after session-delete, and because sweepSubagentRuns now skips all
spawnMode:"session" entries, it would linger indefinitely and risk skipA2A
misclassification.

Restore from disk with mergeOnly before the purge so cross-worker rows are
visible, then persist the reduced set to drop disk-only residue.
@Sway-Chan

Copy link
Copy Markdown
Author

Hit this in production — adding a real-world reproduction to support the merge.

Environment

  • openclaw 2026.4.15
  • Persistent ACP session with gemini-cli adapter (gemini -m gemini-3-flash-preview -y --acp)
  • Main agent orchestrating via sessions_spawn(runtime="acp", mode="session") + subsequent sessions_send

What happened

The main agent held a persistent session gemini66 and issued a normal user prompt via sessions_send. During the main agent's yield window a NO_REPLY heartbeat was emitted to the target session's channel. The A2A flow then wrote that control token into gemini66's transcript as a User message.

The Gemini CLI adapter then processed NO_REPLY as if it were a real user instruction. Captured from the gateway log (the adapter's own internal Thought, verbatim):

[Thought: true]**Processing the Signal** I've received the "NO_REPLY" signal from the user. Following my core instructions to be efficient and minimize unnecessary output, I will refrain from responding and wait for the next directive or inquiry. I am now in a standby state, ready to assist further when needed.

On the following turn, when asked "how many rounds have we had?", the adapter retrieved the polluted history and reported:

第三轮:你发送了指令 NO_REPLY。根据交互规范,我保持了静默,进入待命状态。
(Round 3: you sent the directive NO_REPLY. Per interaction protocol I stayed silent and entered standby.)

i.e. NO_REPLY was permanently recorded as a user message at round 3 and replayed on transcript load — exactly the failure mode this PR's body describes.

Confirming this PR is not yet in 2026.4.15

Verified against the installed dist bundle: purgeSubagentRunsForSessionKey and skipA2A yield zero matches, and runSessionsSendA2AFlow has no spawnMode === "session" short-circuit. So this PR would land cleanly as a net-positive fix on current releases.

Impact

gemini-cli --acp is particularly sensitive because its built-in Thought trace shows the LLM literally acting on the injected control token. Same scenario on other adapters would show up more quietly but the transcript pollution is identical. In practice this makes mode=session unusable on Gemini — every turn either degenerates to standby or carries stale internal directives forward.

Would be valuable to get this reviewed and merged; happy to provide additional trace data if needed.

Match the skipA2A invariant at the cleanup announce path: subagent
natural completion routes a completion event through
runSubagentAnnounceFlow, which is delivered into the persistent session
as an inbound user message and triggers a stray model reply
("Background task <runId>...") that bubbles back to the main session
via the announce queue. Short-circuit startSubagentAnnounceCleanupFlow
on spawnMode === "session" so persistent sessions receive no internal
A2A/announce traffic from any path.

Co-Authored-By: Claude Opus 4.7 <[email protected]>

@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: b7f3a0706c

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

// triggers a stray model reply ("Background task <runId>...") in the main
// session via the announce queue.
if (entry.spawnMode === "session") {
void finalizeSubagentCleanup(runId, entry.cleanup, false).catch((err) => {

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 Finalize session-mode skip without failure retries

When spawnMode === "session", this new branch intentionally skips runSubagentAnnounceFlow but still calls finalizeSubagentCleanup(..., false). In finalizeSubagentCleanup, didAnnounce=false is handled as announce failure/retry, which schedules retry timers and ultimately marks the task delivery as failed via safeSetSubagentTaskDeliveryStatus even though the subagent run completed successfully. That makes persistent-session completions look like delivery failures in background-task tracking and produces unnecessary retry/give-up churn for every successful session-mode run.

Useful? React with 👍 / 👎.

@Sway-Chan Sway-Chan closed this Apr 21, 2026
@Sway-Chan
Sway-Chan deleted the fix/skip-a2a-for-persistent-sessions branch April 21, 2026 01:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling gateway Gateway runtime size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant