From 325136ace31845a77033dc035cba14dbf1af7474 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 26 Feb 2026 13:50:58 -0800 Subject: [PATCH] feat: per-agent allowedTools and disallowedTools config (#410) --- lettabot.example.yaml | 6 ++++++ src/config/types.ts | 4 ++++ src/main.ts | 15 ++++++++------- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/lettabot.example.yaml b/lettabot.example.yaml index 4384a4b..1cc2fa9 100644 --- a/lettabot.example.yaml +++ b/lettabot.example.yaml @@ -20,6 +20,10 @@ agents: # Note: model is configured on the Letta agent server-side. # Select a model during `lettabot onboard` or change it with `lettabot model set `. + # Per-agent tool access (overrides global features.allowedTools / features.disallowedTools) + # features: + # allowedTools: [Read, Glob, Grep, web_search, conversation_search] # Read-only agent (no Bash/Edit/Write) + # Conversation routing (optional) conversations: mode: shared # "shared" (default) or "per-channel" @@ -78,6 +82,8 @@ features: # sendFileMaxSize: 52428800 # Max file size in bytes for (default: 50MB) # sendFileCleanup: false # Allow to delete files after send (default: false) # memfs: true # Enable memory filesystem (git-backed context repository). Syncs memory blocks to local files. + # allowedTools: [Bash, Read, Edit, Write, Glob, Grep, Task, web_search, conversation_search] # Global default + # disallowedTools: [EnterPlanMode, ExitPlanMode] # Global default # display: # showToolCalls: false # Show tool invocations in chat (e.g. "Using tool: Read (file_path: ...)") # showReasoning: false # Show agent reasoning/thinking in chat diff --git a/src/config/types.ts b/src/config/types.ts index 6700510..0f0b5b6 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -86,6 +86,8 @@ export interface AgentConfig { sendFileMaxSize?: number; // Max file size in bytes for (default: 50MB) sendFileCleanup?: boolean; // Allow to delete after send (default: false) display?: DisplayConfig; + allowedTools?: string[]; // Per-agent tool whitelist (overrides global/env ALLOWED_TOOLS) + disallowedTools?: string[]; // Per-agent tool blocklist (overrides global/env DISALLOWED_TOOLS) }; /** Polling config */ polling?: PollingYamlConfig; @@ -167,6 +169,8 @@ export interface LettaBotConfig { sendFileMaxSize?: number; // Max file size in bytes for (default: 50MB) sendFileCleanup?: boolean; // Allow to delete after send (default: false) display?: DisplayConfig; // Show tool calls / reasoning in channel output + allowedTools?: string[]; // Global tool whitelist (overridden by per-agent, falls back to ALLOWED_TOOLS env) + disallowedTools?: string[]; // Global tool blocklist (overridden by per-agent, falls back to DISALLOWED_TOOLS env) }; // Polling - system-level background checks (Gmail, etc.) diff --git a/src/main.ts b/src/main.ts index a497daf..c496018 100644 --- a/src/main.ts +++ b/src/main.ts @@ -494,12 +494,13 @@ function ensureRequiredTools(tools: string[]): string[] { // Global config (shared across all agents) const globalConfig = { workingDir: getWorkingDir(), - allowedTools: ensureRequiredTools(parseCsvList( - process.env.ALLOWED_TOOLS || 'Bash,Read,Edit,Write,Glob,Grep,Task,web_search,conversation_search', - )), - disallowedTools: parseCsvList( - process.env.DISALLOWED_TOOLS || 'EnterPlanMode,ExitPlanMode', + allowedTools: ensureRequiredTools( + yamlConfig.features?.allowedTools ?? + parseCsvList(process.env.ALLOWED_TOOLS || 'Bash,Read,Edit,Write,Glob,Grep,Task,web_search,conversation_search'), ), + disallowedTools: + yamlConfig.features?.disallowedTools ?? + parseCsvList(process.env.DISALLOWED_TOOLS || 'EnterPlanMode,ExitPlanMode'), attachmentsMaxBytes: resolveAttachmentsMaxBytes(), attachmentsMaxAgeDays: resolveAttachmentsMaxAgeDays(), cronEnabled: process.env.CRON_ENABLED === 'true', // Legacy env var fallback @@ -578,8 +579,8 @@ async function main() { const bot = new LettaBot({ workingDir: globalConfig.workingDir, agentName: agentConfig.name, - allowedTools: globalConfig.allowedTools, - disallowedTools: globalConfig.disallowedTools, + allowedTools: ensureRequiredTools(agentConfig.features?.allowedTools ?? globalConfig.allowedTools), + disallowedTools: agentConfig.features?.disallowedTools ?? globalConfig.disallowedTools, displayName: agentConfig.displayName, maxToolCalls: agentConfig.features?.maxToolCalls, sendFileDir: agentConfig.features?.sendFileDir,