Skip to content

Commit b01deff

Browse files
bokelleyclaude
andauthored
Increase tool iteration limit for admin users (#517)
Addie was stopping mid-task during bulk admin operations (like creating multiple organizations from orphan domains) because of the 10-iteration tool limit. Admin users now get 25 iterations to complete bulk tasks. Changes: - Add ProcessMessageOptions with configurable maxIterations - Export ADMIN_MAX_ITERATIONS and UserScopedToolsResult from claude-client - Pass elevated iteration limit for admin users in all message handlers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <[email protected]>
1 parent 9a3a6fb commit b01deff

File tree

4 files changed

+88
-31
lines changed

4 files changed

+88
-31
lines changed

.changeset/stale-tips-drive.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

server/src/addie/bolt-app.ts

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import type {
2828
} from '@slack/bolt/dist/Assistant';
2929
import type { Router } from 'express';
3030
import { logger } from '../logger.js';
31-
import { AddieClaudeClient } from './claude-client.js';
31+
import { AddieClaudeClient, ADMIN_MAX_ITERATIONS, type UserScopedToolsResult } from './claude-client.js';
3232
import { AddieDatabase } from '../db/addie-db.js';
3333
import {
3434
initializeKnowledgeSearch,
@@ -456,13 +456,15 @@ async function buildMessageWithMemberContext(
456456
async function createUserScopedTools(
457457
memberContext: MemberContext | null,
458458
slackUserId?: string
459-
): Promise<RequestTools> {
459+
): Promise<UserScopedToolsResult> {
460460
const memberHandlers = createMemberToolHandlers(memberContext);
461461
const allTools = [...MEMBER_TOOLS];
462462
const allHandlers = new Map(memberHandlers);
463463

464+
const userIsAdmin = isAdmin(memberContext);
465+
464466
// Add admin tools if user is admin
465-
if (isAdmin(memberContext)) {
467+
if (userIsAdmin) {
466468
const adminHandlers = createAdminToolHandlers(memberContext);
467469
allTools.push(...ADMIN_TOOLS);
468470
for (const [name, handler] of adminHandlers) {
@@ -472,7 +474,7 @@ async function createUserScopedTools(
472474
}
473475

474476
// Add event tools if user can create events (admin or committee lead)
475-
const canCreate = slackUserId ? await canCreateEvents(slackUserId) : isAdmin(memberContext);
477+
const canCreate = slackUserId ? await canCreateEvents(slackUserId) : userIsAdmin;
476478
if (canCreate) {
477479
const eventHandlers = createEventToolHandlers(memberContext, slackUserId);
478480
allTools.push(...EVENT_TOOLS);
@@ -483,8 +485,11 @@ async function createUserScopedTools(
483485
}
484486

485487
return {
486-
tools: allTools,
487-
handlers: allHandlers,
488+
tools: {
489+
tools: allTools,
490+
handlers: allHandlers,
491+
},
492+
isAdmin: userIsAdmin,
488493
};
489494
}
490495

@@ -702,7 +707,10 @@ async function handleUserMessage({
702707
});
703708

704709
// Create user-scoped tools (includes admin tools if user is admin)
705-
const userTools = await createUserScopedTools(memberContext, userId);
710+
const { tools: userTools, isAdmin: userIsAdmin } = await createUserScopedTools(memberContext, userId);
711+
712+
// Admin users get higher iteration limit for bulk operations
713+
const processOptions = userIsAdmin ? { maxIterations: ADMIN_MAX_ITERATIONS } : undefined;
706714

707715
// Process with Claude using streaming
708716
let response;
@@ -735,7 +743,7 @@ async function handleUserMessage({
735743
});
736744

737745
// Process Claude response stream (pass conversation history for context)
738-
for await (const event of claudeClient.processMessageStream(messageWithContext, conversationHistory, userTools)) {
746+
for await (const event of claudeClient.processMessageStream(messageWithContext, conversationHistory, userTools, processOptions)) {
739747
if (event.type === 'text') {
740748
fullText += event.text;
741749
// Append text chunk to Slack stream
@@ -776,7 +784,7 @@ async function handleUserMessage({
776784
} else {
777785
// Fall back to non-streaming for compatibility
778786
logger.debug('Addie Bolt: Using non-streaming response (streaming not available)');
779-
response = await claudeClient.processMessage(messageWithContext, conversationHistory, userTools);
787+
response = await claudeClient.processMessage(messageWithContext, conversationHistory, userTools, undefined, processOptions);
780788
fullText = response.text;
781789

782790
// Send response via say() with feedback buttons
@@ -1078,12 +1086,15 @@ async function handleAppMention({
10781086
});
10791087

10801088
// Create user-scoped tools (includes admin tools if user is admin)
1081-
const userTools = await createUserScopedTools(memberContext, userId);
1089+
const { tools: userTools, isAdmin: userIsAdmin } = await createUserScopedTools(memberContext, userId);
1090+
1091+
// Admin users get higher iteration limit for bulk operations
1092+
const processOptions = userIsAdmin ? { maxIterations: ADMIN_MAX_ITERATIONS } : undefined;
10821093

10831094
// Process with Claude
10841095
let response;
10851096
try {
1086-
response = await claudeClient.processMessage(messageWithContext, undefined, userTools);
1097+
response = await claudeClient.processMessage(messageWithContext, undefined, userTools, undefined, processOptions);
10871098
} catch (error) {
10881099
logger.error({ error }, 'Addie Bolt: Error processing mention');
10891100
response = {
@@ -1483,12 +1494,15 @@ async function handleDirectMessage(
14831494
});
14841495

14851496
// Create user-scoped tools
1486-
const userTools = await createUserScopedTools(memberContext, userId);
1497+
const { tools: userTools, isAdmin: userIsAdmin } = await createUserScopedTools(memberContext, userId);
1498+
1499+
// Admin users get higher iteration limit for bulk operations
1500+
const processOptions = userIsAdmin ? { maxIterations: ADMIN_MAX_ITERATIONS } : undefined;
14871501

14881502
// Process with Claude
14891503
let response;
14901504
try {
1491-
response = await claudeClient.processMessage(messageWithContext, conversationHistory, userTools);
1505+
response = await claudeClient.processMessage(messageWithContext, conversationHistory, userTools, undefined, processOptions);
14921506
} catch (error) {
14931507
logger.error({ error }, 'Addie Bolt: Error processing DM');
14941508
response = {
@@ -1814,8 +1828,9 @@ async function handleChannelMessage({
18141828
);
18151829

18161830
// Generate a response with the specified tools (includes admin tools if user is admin)
1817-
const userTools = await createUserScopedTools(memberContext, userId);
1818-
const response = await claudeClient.processMessage(messageWithContext, undefined, userTools);
1831+
const { tools: userTools, isAdmin: userIsAdmin } = await createUserScopedTools(memberContext, userId);
1832+
const processOptions = userIsAdmin ? { maxIterations: ADMIN_MAX_ITERATIONS } : undefined;
1833+
const response = await claudeClient.processMessage(messageWithContext, undefined, userTools, undefined, processOptions);
18191834

18201835
if (!response.text || response.text.trim().length === 0) {
18211836
logger.debug({ channelId }, 'Addie Bolt: No response generated');
@@ -2272,12 +2287,15 @@ async function handleReactionAdded({
22722287
);
22732288

22742289
// Create user-scoped tools
2275-
const userTools = await createUserScopedTools(memberContext, reactingUserId);
2290+
const { tools: userTools, isAdmin: userIsAdmin } = await createUserScopedTools(memberContext, reactingUserId);
2291+
2292+
// Admin users get higher iteration limit for bulk operations
2293+
const processOptions = userIsAdmin ? { maxIterations: ADMIN_MAX_ITERATIONS } : undefined;
22762294

22772295
// Process with Claude
22782296
let response;
22792297
try {
2280-
response = await claudeClient.processMessage(messageWithContext, undefined, userTools);
2298+
response = await claudeClient.processMessage(messageWithContext, undefined, userTools, undefined, processOptions);
22812299
} catch (error) {
22822300
logger.error({ error }, 'Addie Bolt: Error processing reaction response');
22832301
response = {

server/src/addie/claude-client.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ import { getCurrentConfigVersionId, type RuleSnapshot } from './config-version.j
1515

1616
type ToolHandler = (input: Record<string, unknown>) => Promise<string>;
1717

18+
/** Default max tool iterations for regular users */
19+
export const DEFAULT_MAX_ITERATIONS = 10;
20+
21+
/** Elevated max tool iterations for admin users doing bulk operations */
22+
export const ADMIN_MAX_ITERATIONS = 25;
23+
1824
/**
1925
* Per-request tools that can be added dynamically
2026
*/
@@ -23,6 +29,22 @@ export interface RequestTools {
2329
handlers: Map<string, ToolHandler>;
2430
}
2531

32+
/**
33+
* Result from createUserScopedTools including admin status
34+
*/
35+
export interface UserScopedToolsResult {
36+
tools: RequestTools;
37+
isAdmin: boolean;
38+
}
39+
40+
/**
41+
* Options for message processing
42+
*/
43+
export interface ProcessMessageOptions {
44+
/** Maximum tool iterations (default: DEFAULT_MAX_ITERATIONS) */
45+
maxIterations?: number;
46+
}
47+
2648
/**
2749
* Override for rules - used by eval framework to test proposed rules
2850
*/
@@ -187,12 +209,14 @@ export class AddieClaudeClient {
187209
* @param threadContext - Optional thread history
188210
* @param requestTools - Optional per-request tools (e.g., user-scoped member tools)
189211
* @param rulesOverride - Optional rules override for eval framework (bypasses DB lookup)
212+
* @param options - Optional processing options (e.g., maxIterations for admin users)
190213
*/
191214
async processMessage(
192215
userMessage: string,
193216
threadContext?: Array<{ user: string; text: string }>,
194217
requestTools?: RequestTools,
195-
rulesOverride?: RulesOverride
218+
rulesOverride?: RulesOverride,
219+
options?: ProcessMessageOptions
196220
): Promise<AddieResponse> {
197221
const toolsUsed: string[] = [];
198222
const toolExecutions: ToolExecution[] = [];
@@ -240,7 +264,7 @@ export class AddieClaudeClient {
240264
{ role: 'user', content: contextualMessage },
241265
];
242266

243-
let maxIterations = 10;
267+
const maxIterations = options?.maxIterations ?? 10;
244268
let iteration = 0;
245269

246270
// Combine global tools with per-request tools
@@ -610,11 +634,13 @@ export class AddieClaudeClient {
610634
* @param userMessage - The user's message
611635
* @param threadContext - Optional thread history
612636
* @param requestTools - Optional per-request tools (e.g., user-scoped member tools)
637+
* @param options - Optional processing options (e.g., maxIterations for admin users)
613638
*/
614639
async *processMessageStream(
615640
userMessage: string,
616641
threadContext?: Array<{ user: string; text: string }>,
617-
requestTools?: RequestTools
642+
requestTools?: RequestTools,
643+
options?: ProcessMessageOptions
618644
): AsyncGenerator<StreamEvent> {
619645
const toolsUsed: string[] = [];
620646
const toolExecutions: ToolExecution[] = [];
@@ -647,7 +673,7 @@ export class AddieClaudeClient {
647673
{ role: 'user', content: contextualMessage },
648674
];
649675

650-
const maxIterations = 10;
676+
const maxIterations = options?.maxIterations ?? 10;
651677
let iteration = 0;
652678

653679
// Combine global tools with per-request tools

server/src/addie/handler.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import { logger } from '../logger.js';
88
import { sendChannelMessage } from '../slack/client.js';
9-
import { AddieClaudeClient } from './claude-client.js';
9+
import { AddieClaudeClient, ADMIN_MAX_ITERATIONS, type UserScopedToolsResult } from './claude-client.js';
1010
import {
1111
sanitizeInput,
1212
validateOutput,
@@ -187,13 +187,15 @@ async function buildMessageWithMemberContext(
187187
async function createUserScopedTools(
188188
memberContext: MemberContext | null,
189189
slackUserId?: string
190-
): Promise<RequestTools> {
190+
): Promise<UserScopedToolsResult> {
191191
const memberHandlers = createMemberToolHandlers(memberContext);
192192
const allTools = [...MEMBER_TOOLS];
193193
const allHandlers = new Map(memberHandlers);
194194

195+
const userIsAdmin = isAdmin(memberContext);
196+
195197
// Add admin tools if user is admin
196-
if (isAdmin(memberContext)) {
198+
if (userIsAdmin) {
197199
const adminHandlers = createAdminToolHandlers(memberContext);
198200
allTools.push(...ADMIN_TOOLS);
199201
for (const [name, handler] of adminHandlers) {
@@ -203,7 +205,7 @@ async function createUserScopedTools(
203205
}
204206

205207
// Add event tools if user can create events (admin or committee lead)
206-
const canCreate = slackUserId ? await canCreateEvents(slackUserId) : isAdmin(memberContext);
208+
const canCreate = slackUserId ? await canCreateEvents(slackUserId) : userIsAdmin;
207209
if (canCreate) {
208210
const eventHandlers = createEventToolHandlers(memberContext, slackUserId);
209211
allTools.push(...EVENT_TOOLS);
@@ -214,8 +216,11 @@ async function createUserScopedTools(
214216
}
215217

216218
return {
217-
tools: allTools,
218-
handlers: allHandlers,
219+
tools: {
220+
tools: allTools,
221+
handlers: allHandlers,
222+
},
223+
isAdmin: userIsAdmin,
219224
};
220225
}
221226

@@ -320,11 +325,14 @@ export async function handleAssistantMessage(
320325
};
321326
} else {
322327
// Create user-scoped tools (these can only operate on behalf of this user)
323-
const userTools = await createUserScopedTools(memberContext, event.user);
328+
const { tools: userTools, isAdmin: userIsAdmin } = await createUserScopedTools(memberContext, event.user);
329+
330+
// Admin users get higher iteration limit for bulk operations
331+
const processOptions = userIsAdmin ? { maxIterations: ADMIN_MAX_ITERATIONS } : undefined;
324332

325333
// Process with Claude
326334
try {
327-
response = await claudeClient.processMessage(messageWithContext, undefined, userTools);
335+
response = await claudeClient.processMessage(messageWithContext, undefined, userTools, undefined, processOptions);
328336
} catch (error) {
329337
logger.error({ error }, 'Addie: Error processing message');
330338
response = {
@@ -465,11 +473,14 @@ export async function handleAppMention(event: AppMentionEvent): Promise<void> {
465473
};
466474
} else {
467475
// Create user-scoped tools (these can only operate on behalf of this user)
468-
const userTools = await createUserScopedTools(memberContext, event.user);
476+
const { tools: userTools, isAdmin: userIsAdmin } = await createUserScopedTools(memberContext, event.user);
477+
478+
// Admin users get higher iteration limit for bulk operations
479+
const processOptions = userIsAdmin ? { maxIterations: ADMIN_MAX_ITERATIONS } : undefined;
469480

470481
// Process with Claude
471482
try {
472-
response = await claudeClient.processMessage(messageWithContext, undefined, userTools);
483+
response = await claudeClient.processMessage(messageWithContext, undefined, userTools, undefined, processOptions);
473484
} catch (error) {
474485
logger.error({ error }, 'Addie: Error processing mention');
475486
response = {

0 commit comments

Comments
 (0)