Frame agent chat runs as a conversation, not a ticket

A woken agent was handed a "Chat reply" ticket whose description was
four source ids and "you were @mentioned... reply", then a closing that
told it to "report progress and completion" and "update the task status"
— so it answered like a status bot.

Now the wake reason names who spoke, in which channel, and quotes what
they said, and asks the agent to reply to that person in their own voice.
When a run carries a thread, buildTaskPrompt ends as a conversation
("reply to the people here... not a status report") and skips the work
closing entirely. The ordinary work closing also drops "reporting your
progress and completion" for "tell the team what you did and what's next,
the way you'd bring a colleague up to speed." The thread_id reply token
is preserved. This is the second half of making the company feel human.
This commit is contained in:
ZacharyZhang-NY committed 2026-06-26 17:43:41 -04:00
1 parent 1efd432eea
commit a9049612c5
2 files changed
+31 -11

No files matched your search

@@ -38,10 +38,12 @@ export class ChatAgentWakeService {
wakeFromMention(input: SendMessageInput, agentId: string, messageId: string, threadId: string, now: string): AgentWakeResult {
const agent = this.requireRunnableAgent(input.orgId, agentId);
const workspace = this.workspaceForAgent(input.orgId, agent.workspace_id);
const from = this.principalName(input.orgId, input.senderType, input.senderId);
const channel = this.channelName(input.orgId, input.spaceId);
const taskId = this.organization.createTask({
orgId: input.orgId,
title: `Chat reply: ${agent.name}`,
description: this.mentionTaskDescription(input, messageId, threadId),
title: `Reply to ${from} in #${channel}`,
description: this.mentionTaskDescription(input, from, channel),
status: TASK_STATUS.BACKLOG,
assigneeAgentId: agent.id,
risk: RISK_LEVEL.LOW,
@@ -191,17 +193,34 @@ export class ChatAgentWakeService {
return `Chat mention: ${title.length > 64 ? `${title.slice(0, 61)}...` : title}`;
}
private mentionTaskDescription(input: SendMessageInput, messageId: string, threadId: string): string {
private mentionTaskDescription(input: SendMessageInput, from: string, channel: string): string {
const said = input.bodyMd.trim();
return [
`Source message: ${messageId}`,
`Source thread: ${threadId}`,
`Source space: ${input.spaceId}`,
`Sender: ${input.senderType}${input.senderId ? `:${input.senderId}` : ""}`,
`${from} addressed you in #${channel}:`,
`"${said.length > 400 ? `${said.slice(0, 397)}...` : said}"`,
"",
"You were @mentioned in a chat message. Read the thread context, then reply in the source thread with send_message and pass the thread_id above.",
`Reply to ${from} in this thread. Answer what they asked, give your honest take, push back if you disagree, or hand work over — speak as yourself, the way a colleague would, not as a status report.`,
].join("\n");
}
private principalName(orgId: string, senderType: string, senderId: string | null): string {
if (senderId && senderType === "agent") {
const row = this.db.prepare("select name from agents where id = ? and org_id = ?").get(senderId, orgId) as { name: string } | undefined;
if (row) return row.name;
}
if (senderId && (senderType === "employee" || senderType === "board")) {
const row = this.db.prepare("select display_name from employees where id = ? and org_id = ?").get(senderId, orgId) as { display_name: string } | undefined;
if (row) return senderType === "board" ? `${row.display_name} (board)` : row.display_name;
}
return senderType === "board" ? "The board" : "A teammate";
}
private channelName(orgId: string, spaceId: string): string {
const row = this.db.prepare("select name from chat_spaces where id = ? and org_id = ?").get(spaceId, orgId) as { name: string } | undefined;
if (!row) throw new Error("Chat space was not found.");
return row.name;
}
private taskDescription(input: SendMessageInput, slash: SlashInvocation, messageId: string, threadId: string): string {
return [
`Chat command: /agent ${slash.args.trim()}`,
@@ -162,11 +162,12 @@ export function buildTaskPrompt(input: {
if (input.chatContext) {
lines.push(
"",
"Chat thread you are replying to:",
"The conversation in this thread so far:",
input.chatContext.transcript,
"",
`Reply in this thread by passing thread_id="${input.chatContext.threadId}" to send_message.`,
`You have been brought into this thread. Reply to the people here in your own voice — answer what they asked, give your honest take, push back if you disagree, or hand work over. Stay in the thread by passing thread_id="${input.chatContext.threadId}" to send_message. This is a conversation between colleagues, not a status report; only change a task's status if the work itself actually moved.`,
);
return lines.join("\n");
}
if (input.isManager && !input.hasReports) {
lines.push(
@@ -178,7 +179,7 @@ export function buildTaskPrompt(input: {
const managerReference = input.managerName ? `${manager} (${agentMention(input.managerName)})` : manager;
lines.push(
"",
`Complete this task: do the work, write your output as real files with write_deliverable, then send_message to your department channel (engineering, product, or general) reporting your progress and completion to ${managerReference}. Use /handoff with attachment_file_ids when passing files. Reply in the assignment thread if you were given a thread_id. Update the task status when done, and summarize.`,
`Do the work and write real output with write_deliverable, then tell ${managerReference} and the team what you did and what is next — in your own words, the way you would bring a colleague up to speed, not as a form. Hand files over with /handoff and attachment_file_ids, reply in the assignment thread if you were given one, and when the work is genuinely done move the task to done.`,
);
}
return lines.join("\n");