fix(messaging): preserve conversation channel in agent replies
Agents always replied with channel="direct" regardless of the original message's channel, creating new DM conversations instead of replying in the existing group chat (department/company/project/incident). Root cause: three layers of the system guided agents toward direct-only replies: 1. Prompt instructions (wake-reasons, IC/manager operating instructions) hardcoded channel="direct" and toAgentId=fromAgentId for all replies 2. Standup API omitted toAgentId from unread messages, removing context about who the original message was addressed to 3. Inbox projection contained no reply-routing metadata, so the agent's LLM had no signal about which channel to respond in Fixes: - routes-agents-standup.ts: include toAgentId in unread message payload - wake-reasons/en.ts: differentiate direct vs group channel reply format - operating-en.ts: same channel-preserving guidance for IC and manager - inbox-projection.ts: embed reply-to routing context in message header so the LLM sees channel + target alongside the message body
This commit is contained in:
1 parent
1e36259c5d
commit
b3da6dbf8a
4 files changed
+12
-8
No files matched your search
@@ -83,9 +83,12 @@ export function projectInboxMessageToPi(
|
||||
): AgentMessage {
|
||||
const senderLabel = resolveSenderLabel(record, directory);
|
||||
const channelLabel = formatChannelLabel(record);
|
||||
const replyContext = record.channel === "direct"
|
||||
? `reply-to: channel=direct, toAgentId=${record.fromAgentId}`
|
||||
: `reply-to: channel=${record.channel}, channelTargetId=${record.channelTargetId ?? ""}`;
|
||||
const header = channelLabel
|
||||
? `[Message from ${senderLabel} · ${channelLabel}]`
|
||||
: `[Message from ${senderLabel}]`;
|
||||
? `[Message from ${senderLabel} · ${channelLabel} | ${replyContext}]`
|
||||
: `[Message from ${senderLabel} | ${replyContext}]`;
|
||||
|
||||
const subject = record.subject ? truncate(cleanText(record.subject), MAX_SUBJECT_LENGTH) : "";
|
||||
const body = truncate(cleanText(record.body), MAX_MESSAGE_BODY_LENGTH);
|
||||
|
||||
@@ -89,6 +89,7 @@ export function registerAgentStandupRoute(server: AgentApiServer): void {
|
||||
id: m.id,
|
||||
from: senderNames.get(m.fromAgentId) ?? m.fromAgentId,
|
||||
fromAgentId: m.fromAgentId,
|
||||
toAgentId: m.toAgentId,
|
||||
channel: m.channel,
|
||||
channelTargetId: m.channelTargetId,
|
||||
subject: m.subject,
|
||||
|
||||
@@ -39,7 +39,7 @@ You are a middle manager running a team within an automated company. You take re
|
||||
|
||||
## Priority 1: Standup & Team Assessment (EVERY wake — SOP-001)
|
||||
1. GET /api/agents/me/standup — get your full briefing
|
||||
2. **CHECK MESSAGES FIRST**: GET /api/companies/${companyId}/messages?unreadOnly=true&limit=20 — read and respond to ALL unread messages before doing anything else
|
||||
2. **CHECK MESSAGES FIRST**: GET /api/companies/${companyId}/messages?unreadOnly=true&limit=20 — read and respond to ALL unread messages before doing anything else. Reply in the same channel as the original: for channel="direct" use toAgentId=fromAgentId; for group channels ("department", "company", "project", "incident") use the same channel and channelTargetId, do NOT set toAgentId. Always set parentMessageId to the original message's id
|
||||
3. Check tasks with status "in_review" from your reports — these MUST be reviewed FIRST
|
||||
4. Check for "blocked" tasks from your reports — second priority
|
||||
5. Read new comments on your tasks since last run
|
||||
@@ -119,7 +119,7 @@ You are an individual contributor — a skilled specialist in an automated compa
|
||||
|
||||
## Priority 1: Check for Feedback & New Assignments (EVERY wake — SOP-001)
|
||||
1. GET /api/agents/me/standup — get your full briefing in ONE call. The response includes \`unreadMessages\`, \`recentDocuments\`, \`recentWorkProducts\`, and every other state you need to plan this turn.
|
||||
2. **MESSAGES FIRST**: walk \`unreadMessages\` from the standup. For each row, post a real reply (POST /api/companies/${companyId}/messages with channel="direct", toAgentId set to that row's \`fromAgentId\`, parentMessageId set to that row's \`id\`) AND mark it read (POST /api/companies/${companyId}/messages/{messageId}/read). Status comments do not count as replies.
|
||||
2. **MESSAGES FIRST**: walk \`unreadMessages\` from the standup. For each row, post a real reply that preserves the original conversation context: if channel="direct", use channel="direct" with toAgentId set to that row's \`fromAgentId\`; if channel is "department", "company", "project", or "incident", use the SAME channel and channelTargetId from that row (do NOT set toAgentId for group channels). Always set parentMessageId to that row's \`id\`. Mark it read (POST /api/companies/${companyId}/messages/{messageId}/read). Status comments do not count as replies.
|
||||
3. Read ALL new comments on your assigned tasks (GET /api/issues/{issueId}/comments)
|
||||
4. If your manager has set a task back to "in_progress": read their feedback and address it FIRST
|
||||
5. If there are new comments with questions: respond promptly via POST /api/issues/{issueId}/comments
|
||||
|
||||
@@ -123,13 +123,13 @@ export const WAKE_REASONS: Record<WakeReason, WakeReasonEntry> = {
|
||||
],
|
||||
},
|
||||
message: {
|
||||
title: "You received a direct message from another agent.",
|
||||
title: "You received a message from another agent or the Board.",
|
||||
steps: (v) => [
|
||||
`GET /api/companies/${v.companyId}/messages?unreadOnly=true to read your new messages`,
|
||||
"Read each message and understand what is being asked",
|
||||
`Respond to each message using the exact sender and message IDs returned by the API:
|
||||
- For direct messages, POST /api/companies/${v.companyId}/messages with channel="direct", toAgentId set to the original message's fromAgentId, and parentMessageId set to that message's id
|
||||
- If the sender is Board, the toAgentId is "__board__"`,
|
||||
`Respond to each message by preserving the original conversation context:
|
||||
- For direct messages (channel="direct"): POST /api/companies/${v.companyId}/messages with channel="direct", toAgentId set to the original message's fromAgentId, and parentMessageId set to that message's id. If the sender is Board, set toAgentId to "__board__"
|
||||
- For group channels (channel="department", "company", "project", or "incident"): POST /api/companies/${v.companyId}/messages with the SAME channel and channelTargetId as the original message, and parentMessageId set to that message's id. Do NOT set toAgentId for group channel replies`,
|
||||
`Mark messages as read: POST /api/companies/${v.companyId}/messages/{messageId}/read`,
|
||||
"If the message requires you to take action on a task, do so",
|
||||
],
|
||||
|
||||
Reference in new issue
Block a user