Skip to content

feat(slack-agent): prefetch context into system prompt#1169

Merged
saddlepaddle merged 3 commits into
mainfrom
add-user-info-task-statuses-and-devices-to-slack-a
Feb 3, 2026
Merged

feat(slack-agent): prefetch context into system prompt#1169
saddlepaddle merged 3 commits into
mainfrom
add-user-info-task-statuses-and-devices-to-slack-a

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented Feb 3, 2026

Summary

  • Adds fetchAgentContext() that calls list_members, list_task_statuses, and list_devices via MCP at agent startup and formats the results into the system prompt
  • Identifies the current user from the members list and includes it in the prompt context
  • Removes those three tools from the agent's available toolset (added to DENIED_SUPERSET_TOOLS)
  • Fixes the tool deny filter to apply before the superset_ prefix transform so it actually matches

Test plan

  • Mention the Slack bot and ask it to create a task assigned to a team member by name — verify it uses the prefetched member ID without calling list_members
  • Ask the bot about task statuses — verify it knows them without calling list_task_statuses
  • Ask the bot about online devices — verify it knows them without calling list_devices
  • Verify the bot correctly identifies who is talking to it

Summary by CodeRabbit

  • Improvements
    • Slack agent now fetches members, task statuses, and devices in parallel for faster initialization.
    • Slack conversations gain broader contextual awareness using member lists, task statuses, and device info to improve relevance.
    • Certain tools are now excluded from Slack agent context to reduce noise and improve reliability.

…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.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 3, 2026

📝 Walkthrough

Walkthrough

Fetches 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 list_members, list_task_statuses, and list_devices to Slack-denied tools and removes their progress entries.

Changes

Cohort / File(s) Summary
Slack run-agent core
apps/api/src/app/api/integrations/slack/events/utils/run-agent/run-agent.ts
Added internal fetchAgentContext to concurrently retrieve and format members, task statuses, and devices; invoked in parallel with listTools() and appended to the contextual system prompt.
Tool exclusions & progress
apps/api/src/app/api/integrations/slack/events/utils/run-agent/run-agent.ts
Added list_members, list_task_statuses, and list_devices to DENIED_SUPERSET_TOOLS; removed their entries from TOOL_PROGRESS_STATUS.
Formatting & comments
apps/api/src/app/api/integrations/slack/events/utils/run-agent/run-agent.ts
Adjusted contextualSystem composition (extra newline/spacing) to include agentContext; updated related comments and section header to reflect Slack-context exclusions.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I hopped through lists with nimble paws,
I fetched our folks, their tasks, and claws,
Concurrently stitched a cozy block—
A prompt that fits right like a sock. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely describes the main change: prefetching context into the system prompt for the Slack agent, which is the central objective of the changeset.
Description check ✅ Passed The PR description covers the key implementation details and includes a comprehensive test plan, though it does not follow the template structure with sections for Bug fix/New feature/Refactor classification.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch add-user-info-task-statuses-and-devices-to-slack-a

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Comment on lines +289 to +296
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 },
}),
]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 3, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch
  • ✅ Electric Fly.io app

Thank you for your contribution! 🎉

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +300 to +315
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")}`);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

Comment thread apps/api/src/app/api/integrations/slack/events/utils/run-agent/run-agent.ts Outdated
@saddlepaddle saddlepaddle merged commit 51badb5 into main Feb 3, 2026
13 checks passed
@Kitenite Kitenite deleted the add-user-info-task-statuses-and-devices-to-slack-a branch February 5, 2026 01:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant