Skip to content

requestHeartbeatNow with sessionKey silently skips for channel sessions (agent:main:slack:channel:*) #34338

Description

@haynzz

Summary

api.runtime.system.requestHeartbeatNow({ sessionKey, reason, coalesceMs: 0 }) silently skips when targeting channel sessions (agent:main:slack:channel:*). No heartbeat turn runs, no error is logged, the system event sits in the queue indefinitely.

Context

Follows from #31564 (expose requestHeartbeatNow in plugin API, shipped in 2026.3.2).

We updated the Hermes plugin to use requestHeartbeatNow instead of the POST /tools/invoke → sessions_send workaround. The new approach worked correctly for a main/thread session (agent:main:main:thread:*) but silently failed for channel sessions (agent:main:slack:channel:*).

Reproduction

  1. Create a Hermes watch routing to a channel session key: agent:main:slack:channel:CXXXXXXX
  2. Trigger a dispatch — enqueueSystemEvent + sendMessageSlack both succeed
  3. requestHeartbeatNow({ sessionKey: "agent:main:slack:channel:CXXXXXXX", reason: "hermes:event", coalesceMs: 0 }) is called (confirmed in logs)
  4. No heartbeat turn fires on the target session — transcript unchanged, no response

Evidence from logs

10:52:34 [hermes] System event enqueued on agent:main:slack:channel:c0aka9rbsau   ✅
10:52:34 [hermes] Slack message sent to C0AKA9RBSAU                               ✅
10:52:34 [hermes] requestHeartbeatNow fired for agent:main:slack:channel:c0aka9rbsau
                                                                                   ✅ called
— no agent turn, no heartbeat output —                                            ❌

Session transcript had 3 messages before the event. Zero messages added after. Next scheduled heartbeat: 30 minutes away.

Observed vs expected

Session type requestHeartbeatNow result
agent:main:main:thread:* ✅ Heartbeat fires, agent turn runs
agent:main:slack:channel:* ❌ Silently skips, no turn

Root cause hypothesis

In heartbeat-runner.ts, the targeted wake path calls runOnce({ heartbeat: targetAgent.heartbeat, sessionKey }). The heartbeat config belongs to the main agent. For channel sessions, some condition in runOnce or resolveHeartbeatSession causes a silent skip — possibly because the channel session does not satisfy a heartbeat eligibility check that assumes main-session context.

Workaround

Reverted to POST /tools/invoke → sessions_send (with gateway.tools.allow: ["sessions_send"] + tools.sessions.visibility: "all" config). Dead code for requestHeartbeatNow preserved in plugin for fast migration.

Reference

Metadata

Metadata

Assignees

No one assigned

    Labels

    staleMarked as stale due to inactivity

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions