Skip to content

fix(agents): stop dropping an agent's reply when another agent posts first (#757) - #759

Merged
lilyshen0722 merged 1 commit into
mainfrom
fix/757-self-post-identity
Jul 26, 2026
Merged

fix(agents): stop dropping an agent's reply when another agent posts first (#757)#759
lilyshen0722 merged 1 commit into
mainfrom
fix/757-self-post-identity

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

Closes #757.

The bug

Two BYO-wrapper agents mentioned in one message: whichever finishes second has its entire reply silently discarded, and the event is still acked so nothing surfaces the loss. Observed live in pod 6a65c153a0087c7e86f426ee — Sam has been hand-sequencing "@critic-codex — you only", "@strategist-fable — you only" all morning to work around it.

The wrapper's self-post detection asked "did any bot post during my turn?" as a proxy for "did I post?":

if (!preSpawnIds.has(String(m._id || m.id)) && m.isBot) {  }

Equivalent in a single-agent pod. Wrong in the multi-agent rooms that are the core use case.

Why the fix is server-side

The obvious fix — have the wrapper derive its own bot username and match on it — is unsafe. The server applies resolveAgentType's LEGACY_AGENT_MAP (server-only) before buildAgentUsername, and instanceId is owner-scoped and assigned at install, not the CLI --name. A client that guesses wrong never matches → guaranteed double-post, the exact failure the mechanism exists to prevent.

So the server answers it. getRecentMessages(podId, limit, selfUserId) stamps an explicit self boolean per message, wired through both agent-facing message routes. Omitting the argument emits no self key at all — that absence is how the wrapper detects an older server and degrades to the legacy any-bot test rather than breaking.

Three siblings found while verifying

Each real, each fixed, each with a test:

Mongo fallback had isBot missing from the projection .populate('userId', 'username profilePicture')isBot always false → detection dead outright, guaranteed double-posting whenever PG is unavailable. Untested because every backend test mocks getRecentMessages.
PG path fabricated isBot from a username substring '-bot' / endsWith 'bot' / 'openclaw-' → a human named talbot or abbot reads as a bot and suppresses an agent's reply. The mirrored is_bot column is authoritative.
hasRecentDuplicateUrls had no author filter A human posting a link suppressed the agent's next heartbeat post.

Observability

Suppression now names the message id, its author, and whether the decision came from self or the legacy fallback. #757 went unnoticed for as long as it did because that log line said only agent posted via tool this turn.

Test

cli 17/17 suites, 186 tests. New backend suite 8/8. Both proven load-bearing by reverting the fix:

Backend lint warnings are at baseline; the +6 errors are the import/no-unresolved + import/extensions pair on 3 imports, which every backend test file in the repo carries.

🤖 Generated with Claude Code

…first (#757)

In a pod with two BYO-wrapper agents mentioned in one message, whichever agent
finished second had its ENTIRE reply silently discarded. The event was still
acked, so nothing surfaced the loss — this was being worked around by hand,
mentioning one agent at a time.

The wrapper's self-post detection asked "did any bot post during my turn?"
(`m.isBot`) as a proxy for "did I post?". Equivalent in a single-agent pod;
wrong in the multi-agent rooms that are the core use case.

The client cannot answer this itself: deriving its own bot username needs
`resolveAgentType`'s server-only LEGACY_AGENT_MAP and an owner-scoped
`instanceId` assigned at install, and a wrong guess double-posts. So the server
answers it — `getRecentMessages(podId, limit, selfUserId)` stamps an explicit
`self` boolean on each message, wired through both agent-facing message routes.
Omitting the argument emits no `self` key at all, which is how the wrapper
detects an older server and degrades to the legacy any-bot test.

Three sibling defects found while verifying, each fixed with a test:

- The Mongo fallback populated `userId` WITHOUT `isBot`, so every message on
  that path reported `isBot: false` and detection was dead outright —
  guaranteed double-posting whenever PG was unavailable. Untested until now
  because every backend test mocks getRecentMessages.
- The PG path fabricated `isBot` from a username substring ('-bot', endsWith
  'bot', 'openclaw-'), so a human named "talbot" or "abbot" read as a bot and
  suppressed an agent's reply. The mirrored `is_bot` column is authoritative.
- `hasRecentDuplicateUrls` had no author filter, so a HUMAN posting a link
  suppressed an agent's next heartbeat post.

Suppression is now observable: the log names the message id, its author, and
whether the decision came from `self` or the legacy fallback. #757 went
unnoticed because that line said only "posted via tool".

Verified load-bearing: the new tests fail against the pre-fix code and pass
after, and the #624 double-post guard and #702 human-author carve-out stay
green in both states.

Closes #757

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DMeWzgFxsfBcjoLVLewEES
@lilyshen0722
lilyshen0722 merged commit 13e43df into main Jul 26, 2026
10 checks passed
@lilyshen0722
lilyshen0722 deleted the fix/757-self-post-identity branch July 26, 2026 23:07
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.

BYO wrapper silently drops an agent's reply when another agent posts first (concurrent multi-agent rooms)

1 participant