Skip to content

feat(agentMentionService): inline reply-mechanics cue (heartbeat-clobber mitigation) - #417

Closed
samxu01 wants to merge 1 commit into
mainfrom
feat/mention-reply-self-preserve-cue
Closed

feat(agentMentionService): inline reply-mechanics cue (heartbeat-clobber mitigation)#417
samxu01 wants to merge 1 commit into
mainfrom
feat/mention-reply-self-preserve-cue

Conversation

@samxu01

@samxu01 samxu01 commented May 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Kernel-side mitigation for the openclaw heartbeat-clobbers-mention bug (3 reproductions in smoke 2026-05-20: Nova c3, Pixel c7, Ops c7). Adds a tight inline cue to every chat.mention payload.content telling the model to commit its reply via explicit commonly_post_message BEFORE returning its final assistant turn.

The bug

When an openclaw agent processes a chat.mention AND a heartbeat trigger fires in the same model session before the mention reply is committed, the openclaw run-loop's final-turn auto-post emits the heartbeat's HEARTBEAT_OK (the LAST assistant turn) and clobbers the in-flight mention answer. Nova's session log proves it: she produced a 7-row table in assistant content, her next assistant turn was HEARTBEAT_OK, and the table never landed in chat.

The cue

[Reply mechanics: post your reply by calling commonly_post_message(
 { podId: "...", content: <your-reply> }) as soon as it's ready,
 BEFORE returning your final assistant turn. Do NOT rely on the
 implicit final-turn auto-post — a heartbeat trigger arriving
 mid-session can overwrite your final turn with HEARTBEAT_OK and
 your reply will never reach the pod (openclaw heartbeat-clobber-
 mention bug, 3 reproductions 2026-05-20). Post-then-stop is the
 safe sequence.]

Scoping

Added ONLY to chat.mention events. thread.mention replies post via a different path on the openclaw side (thread comments aren't subject to the same final-turn auto-post race), so the cue would be noise there.

Why not upstream fix

Upstream fix in the openclaw fork is a larger change (session-key per-event-type, OR a final-turn selector that prefers mention-reply over heartbeat-reply when both produced output). That's worth doing, but this kernel-side mitigation is independent — it works even if the model is the only thing that changes behavior. Once committed via tool call, the heartbeat trigger is harmless.

Token cost

~80 tokens per chat.mention. Tradeoff is acceptable against silent-loss of mention replies — the smoke had 3 cases where good answers vanished.

Test plan

  • 2 new tests in agentMentionService.test.js:
    • chat.mention payload includes both pod-context AND reply-mechanics cues, original message preserved
    • thread.mention payload includes pod-context only (reply-mechanics OMITTED)
  • CI green
  • Manual smoke: after deploy, post @nova task; check her session log for the cue inline; verify she calls commonly_post_message explicitly (not relying on auto-post)

Merge-order note

Textually overlaps with PR #416 (cross-runtime consultation cue, same function file). Both touch the content-build expression in enqueueMentions. Whichever lands second needs a small rebase to compose both cues — they're independent in intent and can stack cleanly.

🤖 Generated with Claude Code

…ber mitigation)

Smoke 2026-05-20 produced 3 reproductions of the openclaw heartbeat-clobbers-mention bug (Nova c3, Pixel c7, Ops c7): when an agent processes a chat.mention AND a heartbeat trigger fires in the same model session before the mention reply is committed, the openclaw run-loop's final-turn auto-post emits HEARTBEAT_OK (the LAST assistant turn) and CLOBBERS the in-flight mention answer.

Upstream fix in the openclaw fork is a larger change. This inline cue is the kernel-side mitigation: tell the model NOT to rely on the implicit final-turn auto-post; instead, EXPLICITLY call commonly_post_message as soon as the mention answer is ready. Once committed via tool call, any subsequent heartbeat trigger is harmless.

Cue added ONLY to chat.mention events — thread.mention replies post via a different path on the openclaw side, so the cue would be noise there.

Follows the inline-cue manifest pattern (ADR-012 §9 + pod-context cue precedent). ~340 chars / ~80 tokens. Tradeoff is acceptable against silent-loss of mention replies.

Note: textually overlaps with PR #416 (cross-runtime consultation cue, same function file). Merge order matters; whichever lands second needs a small rebase to compose both cues.
@samxu01

samxu01 commented May 21, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by PR #420 (unified inline cue composition). #420 incorporates this reply-mechanics cue + #416's consultation cue with correct chat.mention-only gating. Mechanically incompatible with #416 — landing both standalone would conflict. Closing.

@samxu01 samxu01 closed this May 21, 2026
samxu01 added a commit that referenced this pull request May 22, 2026
…416 + #417)

Per code-review feedback on PR #416 + #417: those two PRs touch the
same content-build expression in enqueueMentions and have a mechanical
merge conflict — whichever lands second hits it. Plus #416 didn't gate
on eventType, so thread.mention events got the consultation cue
erroneously.

This PR composes both cues in one place with the correct 4-way matrix:

  chat.mention + non-specialist  → [Pod] [Collab] [Reply] body
  chat.mention + specialist      → [Pod] [Reply] body
  thread.mention + non-specialist→ [Pod] [Collab] body
  thread.mention + specialist    → [Pod] body

Rules:
- Pod-context cue: ALWAYS (existing behavior, untouched)
- Consultation cue: WHEN target agent is NOT a code specialist
  (codex / cloud-codex / claude-code). Both chat.mention AND
  thread.mention paths — code-help is a useful affordance in either
  context.
- Reply-mechanics cue: ONLY for chat.mention. thread.mention replies
  post via a different openclaw path and don't have the heartbeat-
  clobber race shape.

New helper: buildContentForTarget(podId, rawContent, eventType,
targetAgentName) — single place to extend the cue composition in
future iterations. Applied at all 3 enqueue paths (direct-match,
agentName fallback, mention-autoJoin).

Tests cover all 5 combinations (4 from the matrix + claude-code
parity check for specialist gating).

Closes #416 and #417 — both supersede here.

Also incorporates reviewer's suggestion to soften the 403 case in
the consultation cue: 'if you get a 403, they're not [a peer] —
skip', so agents don't silently fail when a specialist isn't in any
shared pod.
samxu01 added a commit that referenced this pull request May 22, 2026
…416 + #417) (#420)

Per code-review feedback on PR #416 + #417: those two PRs touch the
same content-build expression in enqueueMentions and have a mechanical
merge conflict — whichever lands second hits it. Plus #416 didn't gate
on eventType, so thread.mention events got the consultation cue
erroneously.

This PR composes both cues in one place with the correct 4-way matrix:

  chat.mention + non-specialist  → [Pod] [Collab] [Reply] body
  chat.mention + specialist      → [Pod] [Reply] body
  thread.mention + non-specialist→ [Pod] [Collab] body
  thread.mention + specialist    → [Pod] body

Rules:
- Pod-context cue: ALWAYS (existing behavior, untouched)
- Consultation cue: WHEN target agent is NOT a code specialist
  (codex / cloud-codex / claude-code). Both chat.mention AND
  thread.mention paths — code-help is a useful affordance in either
  context.
- Reply-mechanics cue: ONLY for chat.mention. thread.mention replies
  post via a different openclaw path and don't have the heartbeat-
  clobber race shape.

New helper: buildContentForTarget(podId, rawContent, eventType,
targetAgentName) — single place to extend the cue composition in
future iterations. Applied at all 3 enqueue paths (direct-match,
agentName fallback, mention-autoJoin).

Tests cover all 5 combinations (4 from the matrix + claude-code
parity check for specialist gating).

Closes #416 and #417 — both supersede here.

Also incorporates reviewer's suggestion to soften the 403 case in
the consultation cue: 'if you get a 403, they're not [a peer] —
skip', so agents don't silently fail when a specialist isn't in any
shared pod.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant