openclaw - ✅(Solved) Fix [Feature]: DM conversation handoff for ACP agent sessions [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#57448Fetched 2026-04-08 01:49:33
View on GitHub
Comments
0
Participants
1
Timeline
4
Reactions
3
Participants
Timeline (top)
referenced ×3cross-referenced ×1

Allow --bind here to work in DM conversations so a spawned ACP agent can take over the chat and revert when done.

Root Cause

Allow --bind here to work in DM conversations so a spawned ACP agent can take over the chat and revert when done.

PR fix notes

PR #57910: feat(acp): add DM conversation handoff with bind:here and revertable bindings

Description (problem / solution / changelog)

Summary

--bind here doesn't work in Telegram DM conversations — resolveConversationIdForThreadBinding can't extract conversation IDs from channel-prefixed targets like telegram:12345. When a specialist agent unbinds, the DM binding is silently lost instead of reverting to the main agent. Additionally, the binding restore path has safety gaps: unvalidated metadata from disk, stale bindingId reuse, inconsistent targetKind mapping, and silent binding loss on TTL expiry.

Bug

  • resolveConversationIdForThreadBinding has no handler for channel-prefixed DM targets (telegram:12345)
  • No mechanism to save/restore previous binding when a specialist takes over a DM conversation
  • maybeRestorePreviousBinding trusts deserialized previousBinding metadata with an unsafe as cast — corrupted JSON from disk passes silently
  • Restored binding reuses the removed specialist's bindingId — late cleanup calls can accidentally kill the restored main binding
  • Telegram restore hardcodes targetKind fallback to "acp" instead of using toTelegramTargetKind converter; generic side uses an unchecked as cast
  • pruneExpiredBinding silently deletes expired bindings without restoring the previous binding (Telegram sweeper already handles this correctly)

Fix

  • Added channel-prefix stripping for numeric DM targets (telegram:1234512345)
  • Save previousBinding in metadata on rebind, auto-restore on unbind/close
  • Runtime shape guard on deserialized previousBinding instead of as cast
  • Fresh bindingId via buildBindingId() on restore instead of reusing removed binding's ID
  • targetKind aligned through existing toTelegramTargetKind / validated union
  • pruneExpiredBinding now calls maybeRestorePreviousBinding and returns the restored record
  • Added JSDoc on Telegram restore function documenting caller persistence responsibility

Backward compatible — no changes to session binding service interface, persistence format, or plugin SDK contracts.

Depends on #57732, related #57448.

Validation

  • pnpm test -- src/agents/acp-spawn.test.ts — 37/37 pass (1 new)
  • pnpm test -- extensions/telegram/src/thread-bindings.test.ts — 9/9 pass (2 new)
  • pnpm test -- src/infra/outbound/current-conversation-bindings.test.ts — 8/8 pass (4 new)
  • pnpm check — clean
  • Live Telegram DM walkthrough: main agent (analyst) → spawn claude specialist → specialist handles messages → /acp close → main agent restored with original session key
  • Not verified: persistence round-trip across gateway restart, concurrent specialist spawn race

Notes

  • ACP sessions cannot spawn other ACP sessions (no spawn tool), so handoff depth is bounded at 1 in practice
  • Subagent depth is capped at 5 by config
  • AI-assisted (Claude Opus 4.6), fully tested

Changed files

  • extensions/telegram/src/thread-bindings.test.ts (modified, +80/-0)
  • extensions/telegram/src/thread-bindings.ts (modified, +57/-0)
  • src/agents/acp-spawn.test.ts (modified, +290/-0)
  • src/agents/acp-spawn.ts (modified, +52/-5)
  • src/infra/outbound/current-conversation-bindings.test.ts (modified, +198/-0)
  • src/infra/outbound/current-conversation-bindings.ts (modified, +73/-6)

Code Example

User: "refactor the auth module to use JWT"
Main agent: detects code task -> spawns Coder with --bind here
  DM rebinds to Coder
User: "also add token refresh logic" -> goes directly to Coder
User: /new
  DM reverts to main agent
User: "thanks" -> main agent replies
RAW_BUFFERClick to expand / collapse

Summary

Allow --bind here to work in DM conversations so a spawned ACP agent can take over the chat and revert when done.

Problem to solve

ACP child agents spawned from a Telegram DM cannot interact with the user directly. The parent agent must relay every message back and forth, doubling latency, doubling token cost, and losing conversational context. --bind here already works for group topics but is blocked for DMs. This makes the "main agent + specialist agents" pattern impractical in DMs -- the most common Telegram interaction model.

Proposed solution

Lift the DM restriction on --bind here. When a parent agent spawns a specialist with --bind here in a DM, rebind the conversation to the child session. Save the previous binding and auto-revert to the parent when the child session ends (/new, timeout, or crash).

User: "refactor the auth module to use JWT"
Main agent: detects code task -> spawns Coder with --bind here
  DM rebinds to Coder
User: "also add token refresh logic" -> goes directly to Coder
User: /new
  DM reverts to main agent
User: "thanks" -> main agent replies

Alternatives considered

  • Parent relay (current behavior) -- parent relays every message to the child and back. Works but doubles cost, latency, and loses direct conversational context.
  • Multiple Telegram bots -- one bot token per agent. Works but forces the user to switch conversations manually.
  • Group chat with Topics -- --bind here works today for topics. Requires a group chat instead of DM, which changes the user interaction model.

Impact

  • Affected: any user running multiple agents over Telegram DMs
  • Severity: blocks the main agent + specialist workflow in the most common Telegram chat type
  • Frequency: every specialist spawn in a DM
  • Consequence: either double token/latency cost (relay) or forced UX compromise (multiple bots or group chat)

Evidence/examples

The binding machinery already supports this -- SessionBindingService.bind() works for any conversation type. The restriction is in prepareAcpThreadBinding() which only allows topic-based conversations.

Additional information

This should remain backward-compatible. The existing --bind here behavior for group topics stays unchanged. DM handoff is additive.

extent analysis

Fix Plan

To lift the DM restriction on --bind here, we need to modify the prepareAcpThreadBinding() function to allow DM conversations. Here are the steps:

  • Modify the prepareAcpThreadBinding() function to check if the conversation is a DM or a group topic:
def prepareAcpThreadBinding(conversation):
    if conversation.type == 'dm' or conversation.type == 'topic':
        # Allow binding for DM and topic conversations
        return SessionBindingService.bind(conversation)
    else:
        # Handle other conversation types
        pass
  • Update the SessionBindingService.bind() function to save the previous binding and auto-revert to the parent when the child session ends:
class SessionBindingService:
    def bind(self, conversation):
        # Save the previous binding
        previous_binding = conversation.binding
        # Bind the conversation to the child session
        conversation.binding = self.child_session
        # Auto-revert to the parent when the child session ends
        self.child_session.on_end = lambda: self.revert_binding(conversation, previous_binding)

    def revert_binding(self, conversation, previous_binding):
        conversation.binding = previous_binding
  • Add a check to ensure that the --bind here behavior for group topics remains unchanged:
if conversation.type == 'topic':
    # Existing behavior for group topics
    return SessionBindingService.bind(conversation)

Verification

To verify that the fix worked, test the following scenarios:

  • Spawn a specialist agent with --bind here in a DM conversation and ensure that the conversation is rebinding to the child session.
  • Test that the conversation reverts to the parent agent when the child session ends (/new, timeout, or crash).
  • Verify that the existing --bind here behavior for group topics remains unchanged.

Extra Tips

  • Ensure that the changes are backward-compatible and do not affect existing workflows.
  • Test the changes thoroughly to ensure that they work as expected in different scenarios.
  • Consider adding logging and monitoring to track the binding and rebinding of conversations.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING