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
- Create a Hermes watch routing to a channel session key:
agent:main:slack:channel:CXXXXXXX
- Trigger a dispatch —
enqueueSystemEvent + sendMessageSlack both succeed
requestHeartbeatNow({ sessionKey: "agent:main:slack:channel:CXXXXXXX", reason: "hermes:event", coalesceMs: 0 }) is called (confirmed in logs)
- 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
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
requestHeartbeatNowin plugin API, shipped in 2026.3.2).We updated the Hermes plugin to use
requestHeartbeatNowinstead of thePOST /tools/invoke → sessions_sendworkaround. 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
agent:main:slack:channel:CXXXXXXXenqueueSystemEvent+sendMessageSlackboth succeedrequestHeartbeatNow({ sessionKey: "agent:main:slack:channel:CXXXXXXX", reason: "hermes:event", coalesceMs: 0 })is called (confirmed in logs)Evidence from logs
Session transcript had 3 messages before the event. Zero messages added after. Next scheduled heartbeat: 30 minutes away.
Observed vs expected
agent:main:main:thread:*agent:main:slack:channel:*Root cause hypothesis
In
heartbeat-runner.ts, the targeted wake path callsrunOnce({ heartbeat: targetAgent.heartbeat, sessionKey }). Theheartbeatconfig belongs to the main agent. For channel sessions, some condition inrunOnceorresolveHeartbeatSessioncauses 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(withgateway.tools.allow: ["sessions_send"]+tools.sessions.visibility: "all"config). Dead code forrequestHeartbeatNowpreserved in plugin for fast migration.Reference