Skip to content

[Bug] Inferred commitments marked sent but never delivered to active session #91948

Description

@bizzle12368239

Inferred commitments are marked sent in DB but not actually delivered to the active session

Environment

  • OpenClaw version: 2026.6.1
  • Node: v24.13.1
  • OS: Linux ubuntu 6.8.0-124-generic (x64)
  • Channel: Telegram (tested with single-user direct chat)

Summary

Inferred commitments in the commitments DB are being marked with status: "sent" and a sentAtMs timestamp, but the corresponding message never reaches the user in the active chat session. The delivery chain appears to break between the "queue" step and the actual outbound channel send.

Steps to reproduce

  1. Set up an agent that creates inferred commitments (e.g. kind: "event_check_in", source: "inferred_user_context")
  2. Provide a dueWindow and wait for it to lapse
  3. Observe the commitment being transitioned to status: "sent" with a sentAtMs set
  4. Check the user's chat session — the message is not there

Expected behaviour

When a commitment is status: "sent", the suggested text should be delivered to the user via the chat session specified by sessionKey / to.

Actual behaviour

The commitment is queued and the DB status is updated, but no message is pushed to the user's chat. The commitment effectively disappears.

Evidence / logs

DB observation

A commitments DB snapshot taken at the time of this report contained 43 entries with the following status counts:

{'dismissed': 13, 'sent': 11, 'expired': 12, 'pending': 6, 'delivered': 1}

Note: only 1 of 43 commitments was actually delivered to the user. The 11 sent commitments are effectively orphaned — the status flipped without reaching the chat.

Example commitment object (truncated)

{
  "id": "cm_…",
  "kind": "event_check_in",
  "source": "inferred_user_context",
  "status": "sent",
  "suggestedText": "<example check-in text>",
  "dueWindow": { "earliestMs": 1781082000000, "latestMs": 1781092800000, "timezone": "Europe/London" },
  "createdAtMs": 1781022478760,
  "sentAtMs": 1781085502537,
  "attempts": 1,
  "lastAttemptAtMs": 1781085502537
}
  • dueWindow.latestMs: 11:00 BST (Wed)
  • sentAtMs: 12:18 BST (Wed) — 1h 18m after the due window closed
  • No corresponding outbound Telegram message was found in the active session transcript for the active session at that time.

Delivery mechanism confirmation

The heartbeat system IS functioning for other content types. For example, one agent's "Daily check-in" cron (schedule: 30 10 * * 1-7 Europe/London) runs at 10:30am daily and has a confirmed delivery track record (lastDelivered: true, lastDeliveryStatus: "delivered"). Multiple other scheduled jobs in the same environment also show successful delivery to Telegram.

This confirms the bug is specific to inferred commitments — the delivery mechanism itself works for other content, but inferred commitments are getting orphaned in the queue.

Workaround applied

Modified each agent's HEARTBEAT.md to add an explicit step 2 that queries the commitments DB and delivers pending/sent commitments. The exact text added:

### 2. Send OpenClaw commitments:
— Query `/home/openclaw/.openclaw/commitments/commitments.json` for any commitment matching ALL of:
  - `agentId` = "<this agent>"
  - `to` = "telegram:<chat_id>"
  - `status` = "pending" OR "sent"  (never send "dismissed", "delivered", or "acknowledged")
  - `dueWindow.latestMs` ≤ now
— For each match, decide: is it **still relevant** and **not an issue previously addressed** (resolved already, overtaken by events, superseded, etc.)?
  - **Yes** → send `suggestedText` via `message` tool (action=send, target=telegram:<chat_id>), then update that commitment's `status` to "delivered" in commitments.json
  - **No** → do not deliver; mark the commitment as `dismissed` in commitments.json with a short reason (e.g. "stale — issue resolved elsewhere")
— If nothing matches → skip this step
— If anything fails or you're unsure, fall back to HEARTBEAT_OK and log the issue

This works as a stopgap, but the underlying delivery contract should not depend on each agent re-implementing it via heartbeat polling.

Proposed fix

  1. Make status: "sent" honest — only flip a commitment to sent once the channel send has actually been confirmed, not on queue/attempt.
  2. Drive delivery from the active session — when an active session is idle (between user turns), the runtime should check for queued commitments to that session and deliver them, rather than relying on heartbeat prompts to discover them.
  3. Expose a delivery health metric — e.g. deliveredAtMs vs sentAtMs deltas, surfaced per agent, so this kind of silent drop is visible in normal operation.

Severity

Medium — silently breaks a user-facing feature (proactive check-ins) without surfacing any error to the user or the agent.

Affected versions

Reproduced on 2026.6.1. Likely affects all versions that introduced the inferred-commitments extractor.

Metadata

Metadata

Assignees

Labels

P2Normal backlog priority with limited blast radius.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

Type

No type

Fields

Priority

None yet

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions