From 57dad14a0100e51be0b6397dc5c9eb42300780c0 Mon Sep 17 00:00:00 2001 From: glifocat Date: Thu, 7 May 2026 16:50:59 +0200 Subject: [PATCH] fix(destinations): default to replying to the origin destination MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a multi-destination agent receives an inbound message, the model had no explicit guidance about which destination to address by default and would sometimes pick the wrong one — e.g. Casa replying to the admin's group questions in Laura's DM instead of in the group itself. The formatter already injects `from=""` on every inbound tag (formatter.ts:184), so the origin is right there in the prompt — the system prompt just never told the agent to use it. Added one line to buildDestinationsSection() that nudges the agent toward replying via the same destination the message came from, with an out for explicit cross-destination requests ("tell Laura that…"). Single-destination groups are unaffected (they take a separate short-circuit path with a fallback that auto-replies to the origin). Tests cover the multi-destination, single-destination, and no-destination cases. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../agent-runner/src/destinations.test.ts | 52 +++++++++++++++++++ container/agent-runner/src/destinations.ts | 4 ++ 2 files changed, 56 insertions(+) create mode 100644 container/agent-runner/src/destinations.test.ts diff --git a/container/agent-runner/src/destinations.test.ts b/container/agent-runner/src/destinations.test.ts new file mode 100644 index 0000000..f5e5818 --- /dev/null +++ b/container/agent-runner/src/destinations.test.ts @@ -0,0 +1,52 @@ +import { afterEach, beforeEach, describe, expect, it } from 'bun:test'; + +import { closeSessionDb, getInboundDb, initTestSessionDb } from './db/connection.js'; +import { buildSystemPromptAddendum } from './destinations.js'; + +beforeEach(() => { + initTestSessionDb(); +}); + +afterEach(() => { + closeSessionDb(); +}); + +function seedDestination(name: string, displayName: string, channelType: string, platformId: string): void { + getInboundDb() + .prepare( + `INSERT INTO destinations (name, display_name, type, channel_type, platform_id, agent_group_id) + VALUES (?, ?, 'channel', ?, ?, NULL)`, + ) + .run(name, displayName, channelType, platformId); +} + +describe('buildSystemPromptAddendum — multi-destination routing guidance', () => { + it('includes default-routing nudge when there are >1 destinations', () => { + seedDestination('casa', 'Casa', 'whatsapp', 'group-1@g.us'); + seedDestination('whatsapp-mg-17780', 'whatsapp-mg-17780', 'whatsapp', 'phone-2@s.whatsapp.net'); + + const prompt = buildSystemPromptAddendum('Casa'); + + expect(prompt).toContain('Default routing'); + expect(prompt).toContain('from="name"'); + expect(prompt).toContain('`casa`'); + expect(prompt).toContain('`whatsapp-mg-17780`'); + }); + + it('omits the default-routing nudge for a single destination (short-circuited)', () => { + seedDestination('casa', 'Casa', 'whatsapp', 'group-1@g.us'); + + const prompt = buildSystemPromptAddendum('Casa'); + + // Single-destination path uses the simpler "no special wrapping needed" copy + expect(prompt).toContain('no special wrapping needed'); + expect(prompt).not.toContain('Default routing'); + }); + + it('handles the no-destination case without crashing', () => { + const prompt = buildSystemPromptAddendum('Casa'); + + expect(prompt).toContain('no configured destinations'); + expect(prompt).not.toContain('Default routing'); + }); +}); diff --git a/container/agent-runner/src/destinations.ts b/container/agent-runner/src/destinations.ts index 013bd3b..c17b59a 100644 --- a/container/agent-runner/src/destinations.ts +++ b/container/agent-runner/src/destinations.ts @@ -128,6 +128,10 @@ function buildDestinationsSection(): string { lines.push('Text outside of `` blocks is scratchpad — logged but not sent anywhere.'); lines.push('Use `...` to make scratchpad intent explicit.'); lines.push(''); + lines.push( + '**Default routing**: when replying to an incoming message, address the same destination the message came `from` — every inbound `` tag carries a `from="name"` attribute that names the origin destination. Only address a different destination when the request itself asks you to (e.g., "tell Laura that…").', + ); + lines.push(''); lines.push( 'To send a message mid-response (e.g., an acknowledgment before a long task), call the `send_message` MCP tool with the `to` parameter set to a destination name.', );