From a402e7682815d6ca1db6bd05995b7c2bd518ac93 Mon Sep 17 00:00:00 2001 From: Josh Centers Date: Mon, 16 Mar 2026 01:18:00 +0100 Subject: [PATCH 1/2] fix(teams): remove chatroom fan-out to prevent agent feedback loops Every [#team: ...] post previously triggered a full queue message for each teammate, causing a new Claude invocation per agent. When agents responded with their own [#team: ...] posts, this created an exponential feedback loop that exhausted API token budgets in minutes. Chat room messages are now stored as history only. Agents read recent chat room messages as passive context when next invoked for a real task, preserving coordination without the runaway cost of active fan-out. Co-Authored-By: Claude Sonnet 4.6 --- packages/teams/src/conversation.ts | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/packages/teams/src/conversation.ts b/packages/teams/src/conversation.ts index ee93d47f..64ec76a8 100644 --- a/packages/teams/src/conversation.ts +++ b/packages/teams/src/conversation.ts @@ -94,23 +94,18 @@ export function postToChatRoom( teamAgents: string[], originalData: { channel: string; sender: string; senderId?: string | null; messageId: string } ): number { - const chatMsg = `[Chat room #${teamId} — @${fromAgent}]:\n${message}`; const id = insertChatMessage(teamId, fromAgent, message); - // Enqueue for every teammate (except the sender) - for (const agentId of teamAgents) { - if (agentId === fromAgent) continue; - const msgId = `chat_${teamId}_${fromAgent}_${agentId}_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`; - enqueueMessage({ - channel: originalData.channel, - sender: originalData.sender, - senderId: originalData.senderId ?? undefined, - message: chatMsg, - messageId: msgId, - agent: agentId, - fromAgent, - }); - } - log('DEBUG', `Chat room message: @${fromAgent} → #${teamId} (${teamAgents.length - 1} teammate(s))`); + // Chat room messages are stored as history only — NOT enqueued as active messages for teammates. + // + // Previously, every [#team: ...] post was fanned out to all other agents as a full queue + // message, triggering a new Claude invocation per agent. When agents responded with their + // own [#team: ...] posts, this created an exponential feedback loop that exhausted + // API token budgets in minutes. + // + // Agents read recent chat room history as passive context (prepended to their system prompt) + // when they are next invoked for a real task. This preserves coordination without the runaway + // cost of active fan-out. + log('DEBUG', `Chat room message stored: @${fromAgent} → #${teamId} (no fan-out)`); return id; } From 8785647f476b7835b4f8a57b095cf199185f6d47 Mon Sep 17 00:00:00 2001 From: Josh Centers Date: Mon, 16 Mar 2026 02:23:10 +0100 Subject: [PATCH 2/2] fix(teams): remove dead parameters from postToChatRoom after fan-out removal The teamAgents and originalData parameters became unused dead code when the enqueueMessage fan-out loop was removed. Every call site was computing and passing values that were silently discarded. Cleaned up the function signature and both call sites: - packages/teams/src/conversation.ts (agent response handler) - packages/server/src/routes/chatroom.ts (human REST endpoint) Co-Authored-By: Claude Sonnet 4.6 --- packages/server/src/routes/chatroom.ts | 6 +----- packages/teams/src/conversation.ts | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/server/src/routes/chatroom.ts b/packages/server/src/routes/chatroom.ts index e5f2ca97..91bd4bea 100644 --- a/packages/server/src/routes/chatroom.ts +++ b/packages/server/src/routes/chatroom.ts @@ -32,11 +32,7 @@ app.post('/api/chatroom/:teamId', async (c) => { return c.json({ error: 'message is required' }, 400); } - const id = postToChatRoom(teamId, 'user', body.message.trim(), team.agents, { - channel: 'chatroom', - sender: 'user', - messageId: `chatroom_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`, - }); + const id = postToChatRoom(teamId, 'user', body.message.trim()); return c.json({ ok: true, id }); }); diff --git a/packages/teams/src/conversation.ts b/packages/teams/src/conversation.ts index 64ec76a8..beaf5870 100644 --- a/packages/teams/src/conversation.ts +++ b/packages/teams/src/conversation.ts @@ -91,8 +91,6 @@ export function postToChatRoom( teamId: string, fromAgent: string, message: string, - teamAgents: string[], - originalData: { channel: string; sender: string; senderId?: string | null; messageId: string } ): number { const id = insertChatMessage(teamId, fromAgent, message); // Chat room messages are stored as history only — NOT enqueued as active messages for teammates. @@ -212,9 +210,7 @@ export async function handleTeamResponse(params: { log('INFO', `Chat room broadcasts from @${agentId}: ${chatRoomMsgs.map(m => `#${m.teamId}`).join(', ')}`); } for (const crMsg of chatRoomMsgs) { - postToChatRoom(crMsg.teamId, agentId, crMsg.message, teams[crMsg.teamId].agents, { - channel, sender, senderId: data.senderId, messageId, - }); + postToChatRoom(crMsg.teamId, agentId, crMsg.message); } const teamContext = resolveTeamContext(agentId, isTeamRouted, data, teams);