Skip to content

fix(acp): opt-in parent completion routing for non-threaded run mode#40279

Closed
dewgie-bot wants to merge 10 commits into
openclaw:mainfrom
dewgie-bot:fix/acp-run-completion-delivery-routing
Closed

fix(acp): opt-in parent completion routing for non-threaded run mode#40279
dewgie-bot wants to merge 10 commits into
openclaw:mainfrom
dewgie-bot:fix/acp-run-completion-delivery-routing

Conversation

@dewgie-bot

Copy link
Copy Markdown

Summary

  • Problem: non-threaded ACP runs (runtime="acp", mode="run", thread=false) could complete successfully but not post completion back to the parent/originating chat.
  • Why it matters: users see silent completions (execution succeeds, no visible result), which breaks non-threaded ACP UX.
  • What changed: added an opt-in config flag to route non-threaded ACP run completions back to parent chat/session.
  • Scope boundary: this PR does not change ACP execution/bootstrap internals.

Change Type

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope

  • Gateway / orchestration
  • API / contracts

User-visible / Behavior Changes

  • New config toggle:
    • acp.dispatch.nonThreadedCompletionToParent (true|false)
  • When true: non-threaded ACP run completions are routed back to parent/main chat/session.
  • When false: previous behavior remains.

Security Impact

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Validated on local gateway with non-threaded ACP Codex runs:

  1. Toggle OFF (false) and run non-threaded ACP marker task.
    • Expected/observed: completion does not auto-post to parent chat.
  2. Toggle ON (true) and run same marker task.
    • Expected/observed: completion does auto-post to parent chat.

Files changed

  • src/agents/acp-spawn.ts
  • src/agents/acp-spawn.test.ts
  • src/config/types.acp.ts
  • src/config/zod-schema.ts
  • src/config/schema.labels.ts
  • src/config/schema.help.ts
  • package.json

Risks and Mitigations

  • Risk: duplicate or misrouted completion posts if context handling is wrong.
    • Mitigation: opt-in toggle, test coverage around run-mode routing behavior.

AI Assistance

This PR was AI-assisted.


This PR was created in conjunction with and with approval from @tkozzer.

@openclaw-barnacle openclaw-barnacle Bot added commands Command implementations agents Agent runtime and tooling size: S labels Mar 8, 2026
@dewgie-bot dewgie-bot marked this pull request as ready for review March 8, 2026 22:02
@greptile-apps

greptile-apps Bot commented Mar 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds an opt-in config flag (acp.dispatch.nonThreadedCompletionToParent) that routes non-threaded ACP mode="run" completions back to the originating parent chat. The implementation correctly uses === true for the opt-in check, ensuring existing deployments remain unaffected unless explicitly enabled.

Key changes:

  • acp-spawn.ts: Added isNonThreadedAcpCompletionDeliveryEnabled helper with strict opt-in semantics; hasExternalDeliveryTarget guards against non-deliverable channels.
  • acp-spawn.test.ts: Four test cases verify default isolation, explicit disable, explicit enable, and rejection of non-deliverable channels.
  • agent.delivery.test.ts: Two regression tests for explicit-route delivery and channel validation.
  • Config layer: Field plumbing is consistent with .optional() for opt-in behavior.

The fix is well-scoped, has good test coverage, and is safe to merge.

Confidence Score: 5/5

  • No issues identified. Opt-in implementation is correct, test coverage is comprehensive, and scope is appropriately bounded.
  • The implementation correctly uses === true for opt-in semantics, preventing unintended activation in existing deployments. All test cases verify proper isolation for default state, explicit false, explicit true, and non-deliverable channels. The prior review's minor style concern about using implicit vs. explicit conditions for future spawn modes is speculative and does not affect current code correctness.
  • No files require special attention

Last reviewed commit: bf33756

Comment thread package.json Outdated
{
"name": "openclaw",
"version": "2026.3.8",
"version": "2026.3.7-acpresp-2",

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.

Version downgrade breaks CalVer ordering

The version is being changed from 2026.3.8 to 2026.3.7-acpresp-2. This is a CalVer regression — March 7 is earlier than March 8. Semver-aware tools (npm, package managers, CI pipelines, dependabot) compare 2026.3.7-acpresp-2 as less than 2026.3.8, which means:

  • Automated changelogs and release tooling may skip or misorient this release.
  • Users on 2026.3.8 would not receive this patch as an "upgrade."
  • Any monotonic version check will treat this as a downgrade.

The version should be bumped forward from the current base, e.g. 2026.3.8-acpresp-1 or simply 2026.3.9 to maintain ordering.

Suggested change
"version": "2026.3.7-acpresp-2",
"version": "2026.3.8-acpresp-1",
Prompt To Fix With AI
This is a comment left during a code review.
Path: package.json
Line: 3

Comment:
**Version downgrade breaks CalVer ordering**

The version is being changed from `2026.3.8` to `2026.3.7-acpresp-2`. This is a CalVer regression — March 7 is earlier than March 8. Semver-aware tools (npm, package managers, CI pipelines, dependabot) compare `2026.3.7-acpresp-2` as **less than** `2026.3.8`, which means:

- Automated changelogs and release tooling may skip or misorient this release.
- Users on `2026.3.8` would not receive this patch as an "upgrade."
- Any monotonic version check will treat this as a downgrade.

The version should be bumped forward from the current base, e.g. `2026.3.8-acpresp-1` or simply `2026.3.9` to maintain ordering.

```suggestion
  "version": "2026.3.8-acpresp-1",
```

How can I resolve this? If you propose a fix, please make it concise.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

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: 349c3c6710

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread package.json Outdated
{
"name": "openclaw",
"version": "2026.3.8",
"version": "2026.3.7-acpresp-2",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Revert accidental package version downgrade

This change downgrades the root package version from 2026.3.8 to 2026.3.7-acpresp-2 in a non-release bugfix commit, which can break release/publish flow (npm won’t accept publishing a lower version than one already published) and makes update/version ordering inconsistent for consumers. This should stay monotonic unless this commit is explicitly performing a coordinated release.

Useful? React with 👍 / 👎.

@dewgie-bot

Copy link
Copy Markdown
Author

@greptile-apps please re-review this PR.

@codex please review.

Comment thread src/agents/acp-spawn.ts
Comment on lines +84 to +86
function isNonThreadedAcpCompletionDeliveryEnabled(cfg: OpenClawConfig): boolean {
return cfg.acp?.dispatch?.nonThreadedCompletionToParent !== false;
}

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.

Default is opt-out, not opt-in

nonThreadedCompletionToParent !== false evaluates to true whenever the field is undefined (i.e. not set at all). This means all existing deployments that have never set this flag will immediately start routing non-threaded run completions back to the parent chat after this PR lands — the opposite of the "opt-in" framing in the PR description and the risk mitigation:

"Mitigation: opt-in toggle, test coverage around run-mode routing behavior."

The schema.help.ts description does say "When true (default)" which at least makes the behaviour explicit, but it directly contradicts the PR description's "opt-in config flag" framing. Any deployment that relied on the previous silent-completion behaviour (e.g. because it has its own downstream completion handler) will silently start getting double-delivery.

If the intent is genuinely opt-in, the default should be false:

Suggested change
function isNonThreadedAcpCompletionDeliveryEnabled(cfg: OpenClawConfig): boolean {
return cfg.acp?.dispatch?.nonThreadedCompletionToParent !== false;
}
function isNonThreadedAcpCompletionDeliveryEnabled(cfg: OpenClawConfig): boolean {
return cfg.acp?.dispatch?.nonThreadedCompletionToParent === true;
}

If the intent is to fix the bug for everyone by default (opt-out of the old broken behaviour), the PR description, help text, and risk section should be updated to reflect that existing installs are affected and must explicitly set false to preserve the old behaviour.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/acp-spawn.ts
Line: 84-86

Comment:
**Default is opt-out, not opt-in**

`nonThreadedCompletionToParent !== false` evaluates to `true` whenever the field is `undefined` (i.e. not set at all). This means **all existing deployments** that have never set this flag will immediately start routing non-threaded run completions back to the parent chat after this PR lands — the opposite of the "opt-in" framing in the PR description and the risk mitigation:

> "Mitigation: opt-in toggle, test coverage around run-mode routing behavior."

The `schema.help.ts` description does say "When true (default)" which at least makes the behaviour explicit, but it directly contradicts the PR description's "opt-in config flag" framing. Any deployment that relied on the previous silent-completion behaviour (e.g. because it has its own downstream completion handler) will silently start getting double-delivery.

If the intent is genuinely **opt-in**, the default should be `false`:
```suggestion
function isNonThreadedAcpCompletionDeliveryEnabled(cfg: OpenClawConfig): boolean {
  return cfg.acp?.dispatch?.nonThreadedCompletionToParent === true;
}
```
If the intent is to fix the bug for everyone by default (opt-out of the old broken behaviour), the PR description, help text, and risk section should be updated to reflect that existing installs are affected and must explicitly set `false` to preserve the old behaviour.

How can I resolve this? If you propose a fix, please make it concise.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

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: c711a6fb3d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/acp-spawn.ts Outdated
Comment on lines +449 to +452
const useInlineDelivery =
hasDeliveryTarget && spawnMode === "session" && !streamToParentRequested;
hasDeliveryTarget &&
!streamToParentRequested &&
(spawnMode !== "run" || isNonThreadedAcpCompletionDeliveryEnabled(cfg));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Skip inline ACP run delivery for internal webchat origin

This condition now enables inline delivery for mode="run" whenever a source channel/to is present, but it does not exclude webchat (the internal channel). In that case spawnAcpDirect sends deliver: true with an internal origin, and the gateway delivery path cannot deliver back to webchat (it falls back to channel selection / non-webchat routing), so non-threaded ACP completions from webchat can still fail to return to the originating chat or be routed incorrectly. The new default therefore regresses/does not fix the intended UX for internal-channel runs.

Useful? React with 👍 / 👎.

@openclaw-barnacle openclaw-barnacle Bot added the docs Improvements or additions to documentation label Mar 8, 2026
@dewgie-bot

Copy link
Copy Markdown
Author

Update after latest feedback:

What I changed

  • Switched non-threaded completion routing to true opt-in:
    • acp.dispatch.nonThreadedCompletionToParent now requires explicit true.
  • Added guard to avoid inline run-mode delivery for internal/non-deliverable channels (e.g. webchat/tui), while preserving existing session-mode + parent-stream behavior.
  • Updated config/docs text to match final semantics (opt-in, default isolated behavior).
  • Added/updated tests for:
    • run mode + unset/false => no inline parent delivery
    • run mode + true + external channel => inline parent delivery
    • run mode + true + internal channel => no broken inline delivery path

Validation run

  • pnpm test -- src/agents/acp-spawn.test.ts src/commands/agent.delivery.test.ts ✅ (28 passed)
  • pnpm build
  • Live smoke tests on this machine:
    • toggle true => marker posted back to parent chat ✅
    • toggle false/unset => run completed but marker did not auto-post to parent chat ✅

@greptile-apps please re-review this PR.

@codex please review.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

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: bf33756dab

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/acp-spawn.ts Outdated
Comment on lines +457 to +460
const useInlineDelivery =
hasDeliveryTarget && spawnMode === "session" && !streamToParentRequested;
hasExternalDeliveryTarget &&
!streamToParentRequested &&
(spawnMode !== "run" || isNonThreadedAcpCompletionDeliveryEnabled(cfg));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Limit opt-in completion relay to non-threaded runs

The new useInlineDelivery condition applies acp.dispatch.nonThreadedCompletionToParent to all mode="run" spawns, including thread=true runs. In that case this path is no longer non-threaded: a bound thread is created and to is set to channel:${boundThreadId}, so enabling the flag for non-threaded parent relay also changes behavior for thread-bound run workflows and can unexpectedly post inline output into the child thread. If this flag is intended to affect only non-threaded runs (as documented), gate it on the spawn actually being non-threaded (for example, no thread binding requested/resolved).

Useful? React with 👍 / 👎.

@dewgie-bot

Copy link
Copy Markdown
Author

Pushed latest updates to this PR (head now includes db124d8).

What I changed in this update:

  • Scoped acp.dispatch.nonThreadedCompletionToParent to truly non-threaded run flows only.
  • Ensured thread-bound run behavior is not unintentionally gated by the non-threaded toggle.
  • Added focused test coverage in src/agents/acp-spawn.test.ts for the thread-bound run case.

Validation performed:

  • pnpm test -- src/agents/acp-spawn.test.ts
  • pnpm test -- src/agents/acp-spawn.test.ts src/commands/agent.delivery.test.ts
  • live smoke on this machine for toggle true/false behavior

@codex please review.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@steipete

Copy link
Copy Markdown
Contributor

Closing this as implemented after Codex review.

Current main already covers the reported non-threaded ACP completion-return path through the background-task notifier flow, so this PR's opt-in inline-delivery toggle is superseded.

What I checked:

  • Run-mode ACP spawns intentionally avoid inline outbound delivery: resolveAcpSpawnBootstrapDeliveryPlan keeps inline delivery for spawnMode === "session" only, with an explicit comment that background run-mode spawns should report back through the parent task lifecycle notifier instead of writing directly to the origin channel. (src/agents/acp-spawn.ts:1008, 00bd2cf7a376)
  • ACP manager records parent-owned background runs with requester delivery context: The control-plane manager resolves the requester session/origin for spawned ACP children, creates a running ACP task record with that requesterOrigin, and marks it complete/failure on lifecycle end. (src/acp/control-plane/manager.core.ts:2121, 00bd2cf7a376)
  • Task registry auto-delivers ACP terminal updates back to requester: maybeDeliverTaskTerminalUpdate sends terminal background-task messages to the stored requester delivery route when it is deliverable, otherwise queues a session fallback event. (src/tasks/task-registry.ts:1018, 00bd2cf7a376)
  • Tests cover ACP terminal completion delivery to the parent route: Current tests assert that an ACP background task with requesterOrigin transitions to deliveryStatus: "delivered" and sends Background task done: ACP background task to the original channel/thread, while keeping foreground chatter suppressed until the terminal notify. (src/tasks/task-registry.test.ts:540, 00bd2cf7a376)
  • Spawn tests match the superseding design: Current ACP spawn tests assert that non-threaded run-mode spawns do not inline-deliver (deliver: false) and that parent relay/notifier paths are used separately via streamTo="parent" or implicit subagent relay cases. (src/agents/acp-spawn.test.ts:1647, 00bd2cf7a376)

So I’m closing this as already implemented rather than keeping a duplicate issue open.

Review notes: reviewed against 9f4ef6162dbd; fix evidence: release v2026.4.22, commit 00bd2cf7a376.

@steipete steipete closed this Apr 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling commands Command implementations docs Improvements or additions to documentation size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants