Skip to content

fix: prevent agent participants getting stuck in private chat#1053

Open
m9h wants to merge 1 commit intoPAIR-code:mainfrom
m9h:fix/private-chat-agent-stuck
Open

fix: prevent agent participants getting stuck in private chat#1053
m9h wants to merge 1 commit intoPAIR-code:mainfrom
m9h:fix/private-chat-agent-stuck

Conversation

@m9h
Copy link

@m9h m9h commented Mar 11, 2026

Summary

Fixes #1011 and #938 — two related bugs that cause private chat stages to hang indefinitely.

Bug 1: Spinner hangs when mediator returns shouldRespond: false (#938)

When a mediator's structured output includes shouldRespond: false, no message is written to Firestore. The frontend has no signal, so the "Waiting for a response..." spinner hangs for 120 seconds until the timeout fires.

Bug 2: Agent participants get stuck in private chat (#1011)

When a mediator reaches maxResponses or returns readyToEndChat: true with an empty message, no new chat document is created. Agent participants only respond to UserType.MEDIATOR messages, so they never get triggered and the experiment is stuck forever.

Root cause

Both bugs share the same root cause: when a mediator stops responding (for any reason), no Firestore document is written, so neither the frontend nor agent participants receive a signal.

Fix

When a mediator can no longer respond, send a system message ("<Name> has left the chat.") to the private chat collection. This:

  • Stops the frontend spinner — the system message's senderId differs from the participant's publicId, breaking the isWaitingForResponse condition
  • Unblocks agent participants — the trigger now allows agents to respond to UserType.SYSTEM messages (in addition to UserType.MEDIATOR), so they can set readyToEndChat and advance
  • Uses trigger log deduplication (left-chat-{publicId}) to prevent infinite loops (system message → trigger → mediator can't respond → system message → ...)
  • Skips mediator responses to system messages — only agent participants should react to "left the chat" signals, not mediators

Also moves the empty-text failure check (!response.text) to after structured output extraction, so shouldRespond: false and readyToEndChat: true with empty text aren't incorrectly treated as API failures.

Changes

File Change
functions/src/chat/chat.agent.ts Send "left the chat" system message when mediator hits maxResponses or returns shouldRespond: false; reorder empty-text check
functions/src/chat/chat.utils.ts Add sendSystemPrivateChatMessage() utility
functions/src/triggers/chat.triggers.ts Skip mediator responses to system messages; allow agent participants to respond to system messages

Test plan

  • Test with mediator configured with maxResponses: 3 — verify system message appears after 3rd response and agent advances
  • Test with mediator returning shouldRespond: false via structured output — verify spinner stops immediately and system message appears
  • Test normal private chat flow (no shouldRespond/maxResponses) — verify no regression
  • Test with human participant (not agent) — verify "left the chat" message appears and spinner stops
  • Verify no infinite loop: system message should only be sent once per mediator per stage (check trigger logs)

🤖 Generated with Claude Code

@google-cla
Copy link

google-cla bot commented Mar 11, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@rasmi
Copy link
Collaborator

rasmi commented Mar 11, 2026

Hi @m9h -- thanks for the PR! It seems to have a lot of unrelated commits?

@m9h
Copy link
Author

m9h commented Mar 14, 2026

Sorry about that @rasmi. Those weren't supposed to be included. I will clean this up as soon as I can get back to it.

…ode#1011, PAIR-code#938)

Two related bugs cause private chat stages to hang indefinitely:

1. When a mediator returns shouldRespond: false (PAIR-code#938), no message is
   written to Firestore. The frontend spinner hangs for 120s because
   there's no signal that the mediator chose not to respond.

2. When a mediator hits maxResponses or returns readyToEndChat with an
   empty message (PAIR-code#1011), agent participants never get triggered because
   they only respond to MEDIATOR-type messages. No new message is written,
   so the agent is stuck forever.

Fix: When a mediator can no longer respond (maxResponses reached or
shouldRespond: false), send a system message ("<name> has left the chat")
to the private chat. This:
- Stops the frontend spinner (system message sender != participant)
- Triggers agent participants to act (they now also respond to SYSTEM
  messages, not just MEDIATOR messages)
- Uses trigger log deduplication to prevent infinite loops
- Moves the empty-text check after shouldRespond/readyToEndChat
  extraction so structured output signals aren't treated as failures

Changes:
- chat.agent.ts: Send "left the chat" system message when mediator
  can't respond; handle shouldRespond:false for mediator type; reorder
  empty-text check to respect structured output signals
- chat.utils.ts: Add sendSystemPrivateChatMessage() utility
- chat.triggers.ts: Skip mediator responses to system messages; allow
  agent participants to respond to system messages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@m9h m9h force-pushed the fix/private-chat-agent-stuck branch from 95eaeab to e00787c Compare March 14, 2026 06:10
@cjqian
Copy link
Member

cjqian commented Mar 20, 2026

@rasmi is this related to #1011 ?

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.

Agent participants can get stuck in private chat

3 participants