feat: per-agent allowedTools and disallowedTools config (#410)

This commit is contained in:
Cameron
2026-02-26 13:50:58 -08:00
committed by GitHub
parent 64a0e4b7d8
commit 325136ace3
3 changed files with 18 additions and 7 deletions

View File

@@ -20,6 +20,10 @@ agents:
# Note: model is configured on the Letta agent server-side. # Note: model is configured on the Letta agent server-side.
# Select a model during `lettabot onboard` or change it with `lettabot model set <handle>`. # Select a model during `lettabot onboard` or change it with `lettabot model set <handle>`.
# 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) # Conversation routing (optional)
conversations: conversations:
mode: shared # "shared" (default) or "per-channel" mode: shared # "shared" (default) or "per-channel"
@@ -78,6 +82,8 @@ features:
# sendFileMaxSize: 52428800 # Max file size in bytes for <send-file> (default: 50MB) # sendFileMaxSize: 52428800 # Max file size in bytes for <send-file> (default: 50MB)
# sendFileCleanup: false # Allow <send-file cleanup="true"> to delete files after send (default: false) # sendFileCleanup: false # Allow <send-file cleanup="true"> to delete files after send (default: false)
# memfs: true # Enable memory filesystem (git-backed context repository). Syncs memory blocks to local files. # 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: # display:
# showToolCalls: false # Show tool invocations in chat (e.g. "Using tool: Read (file_path: ...)") # showToolCalls: false # Show tool invocations in chat (e.g. "Using tool: Read (file_path: ...)")
# showReasoning: false # Show agent reasoning/thinking in chat # showReasoning: false # Show agent reasoning/thinking in chat

View File

@@ -86,6 +86,8 @@ export interface AgentConfig {
sendFileMaxSize?: number; // Max file size in bytes for <send-file> (default: 50MB) sendFileMaxSize?: number; // Max file size in bytes for <send-file> (default: 50MB)
sendFileCleanup?: boolean; // Allow <send-file cleanup="true"> to delete after send (default: false) sendFileCleanup?: boolean; // Allow <send-file cleanup="true"> to delete after send (default: false)
display?: DisplayConfig; 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 config */
polling?: PollingYamlConfig; polling?: PollingYamlConfig;
@@ -167,6 +169,8 @@ export interface LettaBotConfig {
sendFileMaxSize?: number; // Max file size in bytes for <send-file> (default: 50MB) sendFileMaxSize?: number; // Max file size in bytes for <send-file> (default: 50MB)
sendFileCleanup?: boolean; // Allow <send-file cleanup="true"> to delete after send (default: false) sendFileCleanup?: boolean; // Allow <send-file cleanup="true"> to delete after send (default: false)
display?: DisplayConfig; // Show tool calls / reasoning in channel output 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.) // Polling - system-level background checks (Gmail, etc.)

View File

@@ -494,12 +494,13 @@ function ensureRequiredTools(tools: string[]): string[] {
// Global config (shared across all agents) // Global config (shared across all agents)
const globalConfig = { const globalConfig = {
workingDir: getWorkingDir(), workingDir: getWorkingDir(),
allowedTools: ensureRequiredTools(parseCsvList( allowedTools: ensureRequiredTools(
process.env.ALLOWED_TOOLS || 'Bash,Read,Edit,Write,Glob,Grep,Task,web_search,conversation_search', yamlConfig.features?.allowedTools ??
)), parseCsvList(process.env.ALLOWED_TOOLS || 'Bash,Read,Edit,Write,Glob,Grep,Task,web_search,conversation_search'),
disallowedTools: parseCsvList(
process.env.DISALLOWED_TOOLS || 'EnterPlanMode,ExitPlanMode',
), ),
disallowedTools:
yamlConfig.features?.disallowedTools ??
parseCsvList(process.env.DISALLOWED_TOOLS || 'EnterPlanMode,ExitPlanMode'),
attachmentsMaxBytes: resolveAttachmentsMaxBytes(), attachmentsMaxBytes: resolveAttachmentsMaxBytes(),
attachmentsMaxAgeDays: resolveAttachmentsMaxAgeDays(), attachmentsMaxAgeDays: resolveAttachmentsMaxAgeDays(),
cronEnabled: process.env.CRON_ENABLED === 'true', // Legacy env var fallback cronEnabled: process.env.CRON_ENABLED === 'true', // Legacy env var fallback
@@ -578,8 +579,8 @@ async function main() {
const bot = new LettaBot({ const bot = new LettaBot({
workingDir: globalConfig.workingDir, workingDir: globalConfig.workingDir,
agentName: agentConfig.name, agentName: agentConfig.name,
allowedTools: globalConfig.allowedTools, allowedTools: ensureRequiredTools(agentConfig.features?.allowedTools ?? globalConfig.allowedTools),
disallowedTools: globalConfig.disallowedTools, disallowedTools: agentConfig.features?.disallowedTools ?? globalConfig.disallowedTools,
displayName: agentConfig.displayName, displayName: agentConfig.displayName,
maxToolCalls: agentConfig.features?.maxToolCalls, maxToolCalls: agentConfig.features?.maxToolCalls,
sendFileDir: agentConfig.features?.sendFileDir, sendFileDir: agentConfig.features?.sendFileDir,