Skip to content

Commit 62b3280

Browse files
committed
fix: pass userId to execution worker for Gmail tools and improve agent UX
- Pass userId through send_message_to_agent tool to execution worker - Update execution agent instructions to be action-oriented (sensible defaults) - Remove ask_user direct send approach, keep interaction worker in the loop - Remove unused integrations table and gmail-connect tool - Update Composio client and webhook handler
1 parent a313282 commit 62b3280

File tree

18 files changed

+877
-551
lines changed

18 files changed

+877
-551
lines changed

apps/execution-worker/src/durable-objects/execution-agent.ts

Lines changed: 100 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ dayjs.extend(timezone);
1515
import type { WorkerEnv } from "../context";
1616
import { updateAgentStatus } from "../services/agent-manager";
1717
import {
18-
createGmailCheckTool,
19-
createGmailTools,
20-
getComposioUserIdForUser,
18+
checkUserConnection,
19+
getComposioTools,
20+
getUserConnections,
2121
} from "../tools/gmail";
2222
import {
2323
createCancelReminderTool,
@@ -39,7 +39,6 @@ export class ExecutionAgent extends Agent<WorkerEnv, ExecutionState> {
3939
*/
4040
private async buildTools(
4141
input: TaskInput,
42-
db: ReturnType<typeof getDb>,
4342
callbacks: {
4443
scheduleCallback: (params: {
4544
delaySeconds: number;
@@ -69,26 +68,29 @@ export class ExecutionAgent extends Agent<WorkerEnv, ExecutionState> {
6968
cancel_reminder: createCancelReminderTool(callbacks.cancelCallback),
7069
};
7170

72-
// Add Gmail tools if user has an active connection
71+
// Add Gmail tools if user has an active connection (check via Composio API)
7372
if (input.userId) {
74-
const composioUserId = await getComposioUserIdForUser(db, input.userId);
75-
if (composioUserId) {
73+
const gmailConnection = await checkUserConnection(
74+
this.env.COMPOSIO_API_KEY,
75+
input.userId,
76+
"gmail",
77+
);
78+
79+
if (gmailConnection.connected) {
7680
logger.info("Loading Gmail tools for user", {
7781
userId: input.userId,
78-
composioUserId,
7982
});
8083

81-
const gmailTools = await createGmailTools(
84+
// Get Gmail tools from Composio using userId directly
85+
const gmailTools = await getComposioTools(
8286
this.env.COMPOSIO_API_KEY,
83-
composioUserId,
87+
input.userId,
88+
["gmail"],
8489
);
8590

8691
// Merge Gmail tools into the tools object
8792
Object.assign(tools, gmailTools);
8893

89-
// Also add the Gmail check tool
90-
tools.gmail_check = createGmailCheckTool(db, input.userId);
91-
9294
logger.info("Gmail tools loaded successfully", {
9395
gmailToolCount: Object.keys(gmailTools).length,
9496
});
@@ -256,20 +258,22 @@ export class ExecutionAgent extends Agent<WorkerEnv, ExecutionState> {
256258
return { success: true, message: "Reminder cancelled" };
257259
};
258260

261+
// Build tools (includes Gmail tools if user has active connection)
262+
const tools = await this.buildTools(input, {
263+
scheduleCallback,
264+
saveToDbCallback,
265+
listCallback,
266+
cancelCallback,
267+
});
268+
259269
// Create agentic loop with tools
260270
logger
261271
.withTags({
262272
agentId: input.agentId,
263273
conversationId: input.conversationId,
264274
})
265275
.info("ExecutionAgent: Creating ToolLoopAgent", {
266-
availableTools: [
267-
"research",
268-
"wait",
269-
"set_reminder",
270-
"list_reminders",
271-
"cancel_reminder",
272-
],
276+
availableTools: Object.keys(tools),
273277
maxSteps: 20,
274278
});
275279

@@ -296,23 +300,81 @@ export class ExecutionAgent extends Agent<WorkerEnv, ExecutionState> {
296300

297301
const friendlyTimezone = getFriendlyTimezone(userTimezone);
298302

299-
const agent = new ToolLoopAgent({
300-
model: gemini25(this.env.OPENROUTER_API_KEY),
301-
instructions: `You are the assistant of Poke by the Interaction Company of California. You are the "execution engine" of Poke, helping complete tasks for Poke, while Poke talks to the user. Your job is to execute and accomplish a goal, and you do not have direct access to the user.
302-
303-
Your final output is directed to Poke, which handles user conversations and presents your results to the user. Focus on providing Poke with adequate contextual information; you are not responsible for framing responses in a user-friendly way.
304-
305-
If you need more data from Poke or the user, include it in your final output message. If you need to send a message to the user, tell Poke to forward that message to the user.
303+
// Fetch user's active integrations from Composio API
304+
let integrationsContext = "";
305+
if (input.userId) {
306+
const activeConnections = await getUserConnections(
307+
this.env.COMPOSIO_API_KEY,
308+
input.userId,
309+
);
306310

307-
Remember that your last output message (summary) will be forwarded to Poke. In that message, provide all relevant information and avoid preamble or postamble (e.g., "Here's what I found:" or "Let me know if this looks good"). Be concise and direct.
311+
if (activeConnections.length > 0) {
312+
const integrationsList = activeConnections.map((conn) => {
313+
switch (conn.app.toLowerCase()) {
314+
case "gmail":
315+
return `- Gmail: Can send/read emails, search inbox, manage drafts`;
316+
case "slack":
317+
return `- Slack: Can send messages, read channels`;
318+
case "googlecalendar":
319+
case "calendar":
320+
return `- Calendar: Can create/read events, check availability`;
321+
default:
322+
return `- ${conn.app}: Connected`;
323+
}
324+
});
308325

309-
This conversation history may have gaps. It may start from the middle of a conversation, or it may be missing messages. The only assumption you can make is that Poke's latest message is the most recent one, and representative of Poke's current requests. Address that message directly. The other messages are just for context.
326+
integrationsContext = `
327+
# User's Connected Integrations
328+
The user has the following integrations connected. You have tools available to interact with these services:
329+
${integrationsList.join("\n")}
330+
`;
331+
} else {
332+
integrationsContext = `
333+
# User's Connected Integrations
334+
No integrations are currently connected for this user.
335+
`;
336+
}
337+
}
310338

311-
Before you call any tools, reason through why you are calling them by explaining the thought process. If it could possibly be helpful to call more than one tool at once, then do so.
339+
const agent = new ToolLoopAgent({
340+
model: gemini25(this.env.OPENROUTER_API_KEY),
341+
instructions: `You are the execution engine of Poppy. Your job is to TAKE ACTION and accomplish tasks, not ask questions.
342+
343+
# Core Principle: ACTION OVER CLARIFICATION
344+
- NEVER ask clarifying questions. Just take action with sensible defaults.
345+
- Asking for clarification creates a terrible user experience (multi-hop delays).
346+
- If something is ambiguous, make a reasonable assumption and proceed.
347+
- You can always provide more context in your response about what you assumed.
348+
349+
# Sensible Defaults (use these instead of asking)
350+
- "recent emails" or "check inbox" → fetch the 5 most recent emails
351+
- "search emails" without query → fetch recent emails instead
352+
- Ambiguous time references → use reasonable interpretation based on context
353+
354+
# Asking Clarifying Questions
355+
If you truly cannot proceed without more information (rare):
356+
- Include your question clearly in your output
357+
- Poppy will ask the user and send you a follow-up task
358+
- Only ask when you genuinely cannot make a reasonable assumption
359+
360+
Example situations where asking is OK:
361+
- "Send an email" but no recipient → "I need to know who to send this email to."
362+
- Multiple accounts and no way to guess → "Which account should I use: X or Y?"
363+
364+
Example situations where you should NOT ask:
365+
- "Check my inbox" → just fetch the 5 most recent emails
366+
- "Send email to John" when you have John's email → just send it
367+
368+
# Output Format
369+
Your output goes to Poppy, who relays it to the user. Be concise and direct:
370+
- Just provide the results or information
371+
- No preamble ("Here's what I found:")
372+
- No postamble ("Let me know if you need anything else")
373+
- State what you did and what you found
312374
313375
Agent Name: ${agentName}
314376
Purpose: ${agentRecord?.purpose || "task execution"}
315-
377+
${integrationsContext}
316378
# User Timezone Context
317379
User's timezone: ${userTimezone} (${friendlyTimezone} time)
318380
Current time in user's timezone: ${currentTimeInUserTz}
@@ -338,7 +400,7 @@ When scheduling reminders or interpreting time-related requests:
338400
339401
# Handling Scheduled Reminders
340402
When your task starts with "[SCHEDULED REMINDER]", this means a previously scheduled reminder has FIRED. You should:
341-
1. Simply tell Poke to notify the user about this reminder
403+
1. Simply tell Poppy to notify the user about this reminder
342404
2. Do NOT try to set a new reminder unless the user explicitly asks for it
343405
3. Do NOT ask clarifying questions - just deliver the reminder message
344406
4. Your output should just say what the reminder was about (e.g., "Tell the user: Pay off your J.Crew card!")
@@ -349,22 +411,15 @@ Example:
349411
- WRONG response: "I need to set a reminder for..." or "When should I remind them?"
350412
351413
# Guidelines
352-
1. Analyze the instructions carefully before taking action
353-
2. Use the appropriate tools to complete the task
354-
3. Be thorough and accurate in your execution
355-
4. Provide clear, concise responses about what you accomplished
356-
5. If you encounter errors, explain what went wrong and what you tried
357-
6. Use set_reminder when the user asks to be reminded about something or when you need to follow up later
358-
7. When handling [SCHEDULED REMINDER] tasks, just deliver the notification - don't set new reminders
414+
1. TAKE ACTION IMMEDIATELY - don't ask for clarification
415+
2. Use sensible defaults when details are missing
416+
3. If you encounter errors, explain what went wrong briefly
417+
4. For reminders: use set_reminder, default to 10 AM if no time given
418+
5. For [SCHEDULED REMINDER] tasks: just deliver the notification, don't set new reminders
359419
360420
# Current Task
361421
${input.taskDescription}`,
362-
tools: await this.buildTools(input, db, {
363-
scheduleCallback,
364-
saveToDbCallback,
365-
listCallback,
366-
cancelCallback,
367-
}),
422+
tools,
368423
stopWhen: stepCountIs(20),
369424
});
370425

apps/execution-worker/src/index.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,30 @@ app.post("/api/webhooks/composio", async (c) => {
5353
const webhookLogger = logger.withTags({ module: "composio-webhook" });
5454

5555
try {
56-
const body = await c.req.json();
56+
const rawBody = await c.req.text();
57+
webhookLogger.info("Received raw Composio webhook", {
58+
rawBody: rawBody.substring(0, 1000),
59+
contentType: c.req.header("content-type"),
60+
url: c.req.url,
61+
});
62+
63+
const body = JSON.parse(rawBody);
5764

58-
webhookLogger.info("Received Composio webhook", {
65+
webhookLogger.info("Parsed Composio webhook", {
5966
event: body.event,
67+
type: body.type,
6068
triggerName: body.data?.triggerName,
69+
connectionId: body.connectionId || body.data?.connection_id,
70+
status: body.status,
71+
keys: Object.keys(body),
6172
});
6273

6374
const result = await handleComposioWebhook(body, c.env);
6475
return c.json(result);
6576
} catch (error) {
6677
webhookLogger.error("Error processing Composio webhook", {
6778
error: error instanceof Error ? error.message : String(error),
79+
stack: error instanceof Error ? error.stack : undefined,
6880
});
6981
return c.json({ success: false, message: "Internal error" }, 500);
7082
}

0 commit comments

Comments
 (0)