Skip to content

Define durable final fallback delivery semantics across channels #87561

@osolmaz

Description

@osolmaz

Problem

OpenClaw has several user-visible failure modes where an agent turn ends with a sanitized fallback/error message internally, but the user sees silence because the channel delivery layer suppresses, drops, or cannot prove delivery of the final payload.

The current WhatsApp report in #84569 is one concrete example: the embedded runner can produce a safe final isError: true payload for an incomplete turn, but the WhatsApp delivery path suppresses it. The same product problem shows up in related channel and recovery issues: final user-visible work exists, or should exist, but delivery status is ambiguous or absent.

This should be solved as a core channel contract:

If core emits a sanitized final fallback message, every durable text-capable channel must either deliver it or report a real delivery failure. It must not silently suppress it.

Related issues and PRs

Directly related:

Delivery and receipt semantics:

Session and recovery surfaces:

Other related silent/lost delivery reports:

Desired contract

Core should define and test these rules:

  1. ReplyPayload.isError === true means user-visible error/fallback classification. It must not mean automatic suppression.
  2. A sanitized final fallback is deliverable text when the channel declares durable final text support.
  3. Hidden artifacts may still be suppressed: reasoning, compaction notices, non-final tool/progress errors, hook-cancelled payloads, and payloads that sanitize to empty.
  4. A visible final payload must finish with an explicit status:
    • sent, with platform identity or receipt
    • failed, with failure stage and error
    • partial_failed, if earlier payloads were sent and a later payload failed
    • suppressed, only when suppression is intentional and has a reason
  5. Empty platform identity such as messageId: "" must not be treated as successful delivery for visible final text.
  6. Delivery outcomes must be visible to recovery/diagnostics so heartbeat/session recovery can tell the difference between delivered, suppressed, failed, and platform-outcome-unknown.

Implementation plan

  1. Clarify the SDK/channel contract around isError, durable final text, and visible fallback delivery.

    Likely surfaces:

    • src/auto-reply/reply-payload.ts
    • src/channels/plugins/outbound.types.ts
    • src/channels/message/types.ts
    • src/plugin-sdk/channel-outbound.ts
  2. Tighten core durable delivery semantics.

    Likely surfaces:

    • src/infra/outbound/deliver.ts
    • src/infra/outbound/deliver-types.ts
    • src/channels/message/send.ts

    Core should not let a visible final fallback disappear as a no-op result.

  3. Make final fallback classification explicit enough that channels do not infer policy from isError alone.

    The important distinction is:

    • final user-visible fallback
    • normal final answer
    • non-final progress/tool/block payload
    • hidden/system artifact
  4. Audit bundled channel adapters for isError suppression or empty-identity delivery results.

    Known starting points include WhatsApp, Telegram, Slack, Discord, Signal, Feishu, and Matrix.

  5. Revisit sendTextOnlyErrorPayloads.

    This should either be removed, narrowed to “adapter needs full payload context,” or guarded so routing through sendPayload cannot silently drop final visible fallback text.

  6. Add general tests before channel-specific tests.

    Core tests should prove:

    • incomplete/empty model turn creates sanitized final fallback
    • raw provider JSON, request IDs, stacks, and partial aborted text do not leak
    • final isError: true text is delivered on a durable text channel
    • empty identity for visible final text is not counted as delivered
    • hidden/non-final payloads can still be suppressed with explicit reason
    • recovery/diagnostics can inspect the outcome
  7. Add cross-channel conformance coverage.

    For channels declaring durable final text support, prove:

    • normal final text delivers
    • sanitized final fallback delivers
    • empty sanitized text suppresses with reason
    • non-final tool/progress errors can remain suppressed
    • delivery result includes platform identity or receipt when sent
  8. Bring channel adapters into compliance.

    fix(whatsapp): deliver final error payloads so incomplete-turn errors reach users #84578 can be treated as the immediate WhatsApp compliance patch, but the long-term fix should not be framed around WhatsApp. WhatsApp is one proving case for the shared durable final text contract.

  9. Add observability.

    Session/diagnostic state should expose at least:

    • final fallback emitted
    • delivery sent
    • delivery suppressed with reason
    • delivery failed
    • platform outcome unknown

Non-goals

  • Do not add per-channel config like sendErrorPayloads for this behavior. Delivering safe final fallback text should be the product default.
  • Do not expose raw provider/internal errors to users.
  • Do not make every isError payload visible; non-final tool/progress/internal payloads may still be hidden.
  • Do not count empty message IDs or empty receipts as successful visible delivery.

Acceptance proof

This should not be considered production-ready until there is:

  • core unit coverage for sanitized fallback creation
  • core delivery coverage for visible final fallback semantics
  • cross-channel durable final text conformance coverage
  • channel-specific regression tests for at least WhatsApp, Telegram, Slack, and Discord where relevant
  • live proof on at least one real channel
  • for WhatsApp session stalls on long model_call: incomplete turn with payloads=0, reply never delivered #84569 specifically, live WhatsApp plus long model/fallback proof when credentials and the vLLM environment are available

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.maintainerMaintainer-authored PR

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions