feat(slack-agent): prefetch context into system prompt#1169
Conversation
…into system prompt Fetch members, task statuses, and devices via MCP at agent startup and inject them into the system prompt so the agent has this context without needing tool calls. Removes list_members, list_task_statuses, and list_devices from the agent's available tools. Also identifies the current user in the prompt context.
📝 WalkthroughWalkthroughFetches team members, task statuses, and devices concurrently via a new helper and appends the formatted agent context to the Slack agent system prompt; also adds Changes
Sequence Diagram(s)sequenceDiagram
participant Runner as RunSlackAgent
participant MCP as MCP Client
participant Tools as Tool Registry
participant Agent as Prompt Composer
Runner->>MCP: fetchAgentContext() (concurrent)
activate MCP
MCP->>MCP: parallel: list_members, list_task_statuses, list_devices
MCP-->>Runner: formatted agentContext
deactivate MCP
Runner->>Tools: listTools()
Tools-->>Runner: tools list
Runner->>Agent: compose contextualSystem (orgId + newline + agentContext + tools info)
Agent-->>Runner: final system prompt
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@apps/api/src/app/api/integrations/slack/events/utils/run-agent/run-agent.ts`:
- Around line 289-296: The three MCP tool calls using Promise.all should be made
resilient: replace Promise.all with Promise.allSettled when calling
mcpClient.callTool for list_members, list_task_statuses, and list_devices (the
variables membersResult, statusesResult, devicesResult), then inspect each
settled result and extract value on status === "fulfilled" or use a sensible
default (e.g., [] or {}) on "rejected"; also log errors for rejected entries
with contextual info about the tool name so prefetch failures don't abort agent
startup.
🧹 Nitpick comments (1)
apps/api/src/app/api/integrations/slack/events/utils/run-agent/run-agent.ts (1)
300-302: Consider adding runtime type guards for MCP responses.The type assertions assume the MCP response shape is correct. While optional chaining provides some safety, adding simple type guards would improve robustness against malformed responses.
💡 Example type guard approach
function isMembersResponse(data: unknown): data is { members: { id: string; name: string | null; email: string }[] } { return data != null && typeof data === "object" && "members" in data && Array.isArray((data as any).members); } // Usage: const membersData = isMembersResponse(membersResult.structuredContent) ? membersResult.structuredContent : null;
| const [membersResult, statusesResult, devicesResult] = await Promise.all([ | ||
| mcpClient.callTool({ name: "list_members", arguments: {} }), | ||
| mcpClient.callTool({ name: "list_task_statuses", arguments: {} }), | ||
| mcpClient.callTool({ | ||
| name: "list_devices", | ||
| arguments: { includeOffline: true }, | ||
| }), | ||
| ]); |
There was a problem hiding this comment.
Missing error handling for MCP tool calls.
If any of the three MCP calls fails, Promise.all rejects and the entire agent startup fails. Since this is prefetched context (not critical for agent operation), consider using Promise.allSettled to handle partial failures gracefully.
🛡️ Proposed fix using Promise.allSettled
- const [membersResult, statusesResult, devicesResult] = await Promise.all([
- mcpClient.callTool({ name: "list_members", arguments: {} }),
- mcpClient.callTool({ name: "list_task_statuses", arguments: {} }),
- mcpClient.callTool({
- name: "list_devices",
- arguments: { includeOffline: true },
- }),
- ]);
+ const [membersResult, statusesResult, devicesResult] = await Promise.allSettled([
+ mcpClient.callTool({ name: "list_members", arguments: {} }),
+ mcpClient.callTool({ name: "list_task_statuses", arguments: {} }),
+ mcpClient.callTool({
+ name: "list_devices",
+ arguments: { includeOffline: true },
+ }),
+ ]);Then update the data extraction to handle rejected promises:
- const membersData = membersResult.structuredContent as {
+ const membersData = (membersResult.status === "fulfilled" ? membersResult.value.structuredContent : null) as {
members: { id: string; name: string | null; email: string }[];
} | null;(Apply similar changes for statusesResult and devicesResult)
🤖 Prompt for AI Agents
In `@apps/api/src/app/api/integrations/slack/events/utils/run-agent/run-agent.ts`
around lines 289 - 296, The three MCP tool calls using Promise.all should be
made resilient: replace Promise.all with Promise.allSettled when calling
mcpClient.callTool for list_members, list_task_statuses, and list_devices (the
variables membersResult, statusesResult, devicesResult), then inspect each
settled result and extract value on status === "fulfilled" or use a sensible
default (e.g., [] or {}) on "rejected"; also log errors for rejected entries
with contextual info about the tool name so prefetch failures don't abort agent
startup.
🧹 Preview Cleanup CompleteThe following preview resources have been cleaned up:
Thank you for your contribution! 🎉 |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@apps/api/src/app/api/integrations/slack/events/utils/run-agent/run-agent.ts`:
- Around line 336-341: The "Online devices" heading is misleading because
devicesData.devices includes both online and offline entries; update the logic
in the block that builds lines (referencing devicesData, devicesData.devices and
the lines variable) to either filter devicesData.devices to only include
d.isOnline === true before mapping, or change the sections.push call to a
neutral heading like "Devices" (or "Devices (mixed status)") so the label
matches the listed items; ensure the chosen fix updates the heading text in the
sections.push invocation and preserves the existing per-device status string
(d.isOnline ? "online" : "offline").
- Around line 300-315: The code currently injects full emails into system
prompts via membersData and currentUser; update the output building in
run-agent.ts so that sections only include name and id by default and include
email only when name is null (e.g., replace `${currentUser.name ??
currentUser.email}` and the `email:` fields in both the Current user and Team
members lines with logic that shows name and id, and appends `(email: ...)` only
when name is missing), and ensure the members map (`membersData.members.map`)
builds `- ${m.name ?? m.email} (id: ${m.id}${m.name ? "" : `, email:
${m.email}`})` to minimize PII exposure.
| const membersData = membersResult.structuredContent as { | ||
| members: { id: string; name: string | null; email: string }[]; | ||
| } | null; | ||
| if (membersData?.members?.length) { | ||
| const currentUser = membersData.members.find((m) => m.id === userId); | ||
| if (currentUser) { | ||
| sections.push( | ||
| `Current user: ${currentUser.name ?? currentUser.email} (id: ${currentUser.id}, email: ${currentUser.email})`, | ||
| ); | ||
| } | ||
|
|
||
| const lines = membersData.members.map( | ||
| (m) => `- ${m.name ?? m.email} (id: ${m.id}, email: ${m.email})`, | ||
| ); | ||
| sections.push(`Team members:\n${lines.join("\n")}`); | ||
| } |
There was a problem hiding this comment.
Reduce PII in the system prompt by default.
The full member email list is injected into every system prompt, which increases PII exposure and token usage. Consider limiting to name + id, and only include email when a name is missing (or behind a flag).
🔒 PII-minimizing example
- sections.push(
- `Current user: ${currentUser.name ?? currentUser.email} (id: ${currentUser.id}, email: ${currentUser.email})`,
- );
+ const currentUserLabel = currentUser.name ?? currentUser.email;
+ sections.push(`Current user: ${currentUserLabel} (id: ${currentUser.id})`);
- const lines = membersData.members.map(
- (m) => `- ${m.name ?? m.email} (id: ${m.id}, email: ${m.email})`,
- );
+ const lines = membersData.members.map((m) =>
+ m.name ? `- ${m.name} (id: ${m.id})` : `- ${m.email} (id: ${m.id})`,
+ );📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const membersData = membersResult.structuredContent as { | |
| members: { id: string; name: string | null; email: string }[]; | |
| } | null; | |
| if (membersData?.members?.length) { | |
| const currentUser = membersData.members.find((m) => m.id === userId); | |
| if (currentUser) { | |
| sections.push( | |
| `Current user: ${currentUser.name ?? currentUser.email} (id: ${currentUser.id}, email: ${currentUser.email})`, | |
| ); | |
| } | |
| const lines = membersData.members.map( | |
| (m) => `- ${m.name ?? m.email} (id: ${m.id}, email: ${m.email})`, | |
| ); | |
| sections.push(`Team members:\n${lines.join("\n")}`); | |
| } | |
| const membersData = membersResult.structuredContent as { | |
| members: { id: string; name: string | null; email: string }[]; | |
| } | null; | |
| if (membersData?.members?.length) { | |
| const currentUser = membersData.members.find((m) => m.id === userId); | |
| if (currentUser) { | |
| const currentUserLabel = currentUser.name ?? currentUser.email; | |
| sections.push(`Current user: ${currentUserLabel} (id: ${currentUser.id})`); | |
| } | |
| const lines = membersData.members.map((m) => | |
| m.name ? `- ${m.name} (id: ${m.id})` : `- ${m.email} (id: ${m.id})`, | |
| ); | |
| sections.push(`Team members:\n${lines.join("\n")}`); | |
| } |
🤖 Prompt for AI Agents
In `@apps/api/src/app/api/integrations/slack/events/utils/run-agent/run-agent.ts`
around lines 300 - 315, The code currently injects full emails into system
prompts via membersData and currentUser; update the output building in
run-agent.ts so that sections only include name and id by default and include
email only when name is null (e.g., replace `${currentUser.name ??
currentUser.email}` and the `email:` fields in both the Current user and Team
members lines with logic that shows name and id, and appends `(email: ...)` only
when name is missing), and ensure the members map (`membersData.members.map`)
builds `- ${m.name ?? m.email} (id: ${m.id}${m.name ? "" : `, email:
${m.email}`})` to minimize PII exposure.
Summary
fetchAgentContext()that callslist_members,list_task_statuses, andlist_devicesvia MCP at agent startup and formats the results into the system promptDENIED_SUPERSET_TOOLS)superset_prefix transform so it actually matchesTest plan
list_memberslist_task_statuseslist_devicesSummary by CodeRabbit