Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/ripe-ghosts-bet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
---

Add escalation and learning capture tools for Addie

- New `escalate_to_admin` tool for capability gaps and requests needing human action
- New `capture_learning` tool to flag valuable user insights for synthesis
- Admin API endpoints for managing escalations
- System prompt updates to prevent Addie from promising actions without tools
4 changes: 4 additions & 0 deletions server/public/admin-addie.html
Original file line number Diff line number Diff line change
Expand Up @@ -1603,6 +1603,7 @@ <h2 style="margin: 0;">Conversation Threads</h2>
<label><input type="checkbox" id="threads-flagged-only"> Flagged only</label>
<label><input type="checkbox" id="threads-unreviewed-only"> Unreviewed only</label>
<label><input type="checkbox" id="threads-has-feedback"> Has user feedback</label>
<label><input type="checkbox" id="threads-multi-turn" checked> Multi-turn only</label>
<button class="btn btn-secondary" onclick="loadThreads()">Refresh</button>
</div>
</div>
Expand Down Expand Up @@ -2439,6 +2440,7 @@ <h3>${escapeHtml(doc.title)}</h3>
const flaggedOnly = document.getElementById('threads-flagged-only').checked;
const unreviewedOnly = document.getElementById('threads-unreviewed-only').checked;
const hasUserFeedback = document.getElementById('threads-has-feedback').checked;
const multiTurnOnly = document.getElementById('threads-multi-turn').checked;
const timeframe = document.getElementById('stats-timeframe').value;

const params = new URLSearchParams();
Expand All @@ -2447,6 +2449,7 @@ <h3>${escapeHtml(doc.title)}</h3>
if (flaggedOnly) params.append('flagged_only', 'true');
if (unreviewedOnly) params.append('unreviewed_only', 'true');
if (hasUserFeedback) params.append('has_user_feedback', 'true');
if (multiTurnOnly) params.append('min_messages', '2');

// Add timeframe filter to threads query
if (timeframe !== 'all') {
Expand Down Expand Up @@ -4812,6 +4815,7 @@ <h3 style="margin-bottom: var(--space-3); display: flex; align-items: center; ga
document.getElementById('threads-flagged-only').addEventListener('change', loadThreads);
document.getElementById('threads-unreviewed-only').addEventListener('change', loadThreads);
document.getElementById('threads-has-feedback').addEventListener('change', loadThreads);
document.getElementById('threads-multi-turn').addEventListener('change', loadThreads);
</script>
</body>
</html>
27 changes: 20 additions & 7 deletions server/src/addie/bolt-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ import {
BILLING_TOOLS,
createBillingToolHandlers,
} from './mcp/billing-tools.js';
import {
ESCALATION_TOOLS,
createEscalationToolHandlers,
} from './mcp/escalation-tools.js';
import { SUGGESTED_PROMPTS, buildDynamicSuggestedPrompts } from './prompts.js';
import { AddieModelConfig, ModelConfig } from '../config/models.js';
import { getMemberContext, formatMemberContextForPrompt, type MemberContext } from './member-context.js';
Expand Down Expand Up @@ -609,7 +613,8 @@ async function buildMessageWithMemberContext(
*/
async function createUserScopedTools(
memberContext: MemberContext | null,
slackUserId?: string
slackUserId?: string,
threadId?: string
): Promise<UserScopedToolsResult> {
const memberHandlers = createMemberToolHandlers(memberContext);
const allTools = [...MEMBER_TOOLS];
Expand All @@ -623,6 +628,14 @@ async function createUserScopedTools(
}
logger.debug('Addie Bolt: Billing tools enabled');

// Add escalation tools for all users
const escalationHandlers = createEscalationToolHandlers(memberContext, slackUserId, threadId);
allTools.push(...ESCALATION_TOOLS);
for (const [name, handler] of escalationHandlers) {
allHandlers.set(name, handler);
}
logger.debug('Addie Bolt: Escalation tools enabled');

// Check if user is AAO admin (based on aao-admin working group membership)
const userIsAdmin = slackUserId ? await isSlackUserAdmin(slackUserId) : false;

Expand Down Expand Up @@ -936,7 +949,7 @@ async function handleUserMessage({
});

// Create user-scoped tools (includes admin tools if user is admin)
const { tools: userTools, isAdmin: userIsAdmin } = await createUserScopedTools(memberContext, userId);
const { tools: userTools, isAdmin: userIsAdmin } = await createUserScopedTools(memberContext, userId, thread.thread_id);

// Admin users get higher iteration limit for bulk operations
const processOptions = userIsAdmin ? { maxIterations: ADMIN_MAX_ITERATIONS } : undefined;
Expand Down Expand Up @@ -1361,7 +1374,7 @@ async function handleAppMention({
});

// Create user-scoped tools (includes admin tools if user is admin)
const { tools: userTools, isAdmin: userIsAdmin } = await createUserScopedTools(memberContext, userId);
const { tools: userTools, isAdmin: userIsAdmin } = await createUserScopedTools(memberContext, userId, thread.thread_id);

// Admin users get higher iteration limit for bulk operations
const processOptions = userIsAdmin ? { maxIterations: ADMIN_MAX_ITERATIONS } : undefined;
Expand Down Expand Up @@ -1825,7 +1838,7 @@ async function handleDirectMessage(
});

// Create user-scoped tools
const { tools: userTools, isAdmin: userIsAdmin } = await createUserScopedTools(memberContext, userId);
const { tools: userTools, isAdmin: userIsAdmin } = await createUserScopedTools(memberContext, userId, thread.thread_id);

// Admin users get higher iteration limit for bulk operations
const processOptions = userIsAdmin ? { maxIterations: ADMIN_MAX_ITERATIONS } : undefined;
Expand Down Expand Up @@ -2109,7 +2122,7 @@ async function handleActiveThreadReply({
});

// Create user-scoped tools
const { tools: userTools, isAdmin: userIsAdmin } = await createUserScopedTools(memberContext, userId);
const { tools: userTools, isAdmin: userIsAdmin } = await createUserScopedTools(memberContext, userId, thread.thread_id);

// Admin users get higher iteration limit
const processOptions = userIsAdmin ? { maxIterations: ADMIN_MAX_ITERATIONS } : undefined;
Expand Down Expand Up @@ -2490,7 +2503,7 @@ async function handleChannelMessage({
);

// Generate a response with the specified tools (includes admin tools if user is admin)
const { tools: userTools, isAdmin: userIsAdmin } = await createUserScopedTools(memberContext, userId);
const { tools: userTools, isAdmin: userIsAdmin } = await createUserScopedTools(memberContext, userId, thread.thread_id);
// Use precision model (Opus) for billing/financial queries
const processOptions = {
...(userIsAdmin ? { maxIterations: ADMIN_MAX_ITERATIONS } : {}),
Expand Down Expand Up @@ -2954,7 +2967,7 @@ async function handleReactionAdded({
);

// Create user-scoped tools
const { tools: userTools, isAdmin: userIsAdmin } = await createUserScopedTools(memberContext, reactingUserId);
const { tools: userTools, isAdmin: userIsAdmin } = await createUserScopedTools(memberContext, reactingUserId, thread.thread_id);

// Admin users get higher iteration limit for bulk operations
const processOptions = userIsAdmin ? { maxIterations: ADMIN_MAX_ITERATIONS } : undefined;
Expand Down
18 changes: 15 additions & 3 deletions server/src/addie/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ import {
MEETING_TOOLS,
createMeetingToolHandlers,
} from './mcp/meeting-tools.js';
import {
ESCALATION_TOOLS,
createEscalationToolHandlers,
} from './mcp/escalation-tools.js';
import { AddieDatabase } from '../db/addie-db.js';
import { SUGGESTED_PROMPTS, STATUS_MESSAGES, buildDynamicSuggestedPrompts } from './prompts.js';
import { AddieModelConfig } from '../config/models.js';
Expand Down Expand Up @@ -241,12 +245,20 @@ async function buildMessageWithMemberContext(
*/
async function createUserScopedTools(
memberContext: MemberContext | null,
slackUserId?: string
slackUserId?: string,
threadId?: string
): Promise<UserScopedToolsResult> {
const memberHandlers = createMemberToolHandlers(memberContext);
const allTools = [...MEMBER_TOOLS];
const allHandlers = new Map(memberHandlers);

// Add escalation tools (available to all users)
const escalationHandlers = createEscalationToolHandlers(memberContext, slackUserId, threadId);
allTools.push(...ESCALATION_TOOLS);
for (const [name, handler] of escalationHandlers) {
allHandlers.set(name, handler);
}

// Check if user is AAO admin (based on aao-admin working group membership)
const userIsAdmin = slackUserId ? await isSlackUserAdmin(slackUserId) : false;

Expand Down Expand Up @@ -403,7 +415,7 @@ export async function handleAssistantMessage(
};
} else {
// Create user-scoped tools (these can only operate on behalf of this user)
const { tools: userTools, isAdmin: userIsAdmin } = await createUserScopedTools(memberContext, event.user);
const { tools: userTools, isAdmin: userIsAdmin } = await createUserScopedTools(memberContext, event.user, event.thread_ts);

// Admin users get higher iteration limit for bulk operations
const processOptions = userIsAdmin ? { maxIterations: ADMIN_MAX_ITERATIONS } : undefined;
Expand Down Expand Up @@ -554,7 +566,7 @@ export async function handleAppMention(event: AppMentionEvent): Promise<void> {
};
} else {
// Create user-scoped tools (these can only operate on behalf of this user)
const { tools: userTools, isAdmin: userIsAdmin } = await createUserScopedTools(memberContext, event.user);
const { tools: userTools, isAdmin: userIsAdmin } = await createUserScopedTools(memberContext, event.user, event.thread_ts || event.ts);

// Admin users get higher iteration limit for bulk operations
const processOptions = userIsAdmin ? { maxIterations: ADMIN_MAX_ITERATIONS } : undefined;
Expand Down
Loading