diff --git a/src/agent.ts b/src/agent.ts index 9122c55..70e2c32 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -21,29 +21,53 @@ import { traceTools } from "./tracing.js"; import { restoreSession } from "./session.js"; import { checkPermission } from "./permissions.js"; -const staticTools: AgentTool[] = [ - // GPU - wakeGpu, shutdownGpu, gpuStatus, - // File system +/** + * Core tools — always registered. Small, general-purpose surface the model + * will want on virtually every task. + */ +const coreTools: AgentTool[] = [ readFileTool, writeFileTool, listFilesTool, - // Remote - sshToNas, delegateToNix, - // Browser - browserControl, - // LinkedIn - linkedinSearch, linkedinResults, - // iOS - iosListDevices, iosBuild, iosInstall, iosBuildAndDeploy, - // Launchpad - launchpadRunScraper, launchpadDeploy, launchpadScrape, - // browser-use agentic tasks - browserTask, - // Shell runShell, - // Claude Code delegation - delegateToClaudeSubagent, + sshToNas, + delegateToNix, + wakeGpu, shutdownGpu, gpuStatus, ]; +/** + * Specialized tool groups — opt in via env flags. Each group adds ~hundreds + * of tokens of schema to every single request; only register what's needed. + * + * MAX_TOOLS_BROWSER=true browser_control + browser_task + * MAX_TOOLS_LINKEDIN=true linkedin_search + linkedin_results + * MAX_TOOLS_IOS=true ios_list_devices + ios_build + ios_install + ios_build_and_deploy + * MAX_TOOLS_LAUNCHPAD=true launchpad_run_scraper + launchpad_deploy + launchpad_scrape + * MAX_TOOLS_SUBAGENT=true delegate_to_claude_subagent + * MAX_TOOLS_ALL=true everything (back-compat with pre-split behaviour) + */ +const toolGroups: Record = { + BROWSER: [browserControl, browserTask], + LINKEDIN: [linkedinSearch, linkedinResults], + IOS: [iosListDevices, iosBuild, iosInstall, iosBuildAndDeploy], + LAUNCHPAD: [launchpadRunScraper, launchpadDeploy, launchpadScrape], + SUBAGENT: [delegateToClaudeSubagent], +}; + +function buildStaticTools(): AgentTool[] { + const all = process.env.MAX_TOOLS_ALL === "true"; + const enabled: AgentTool[] = [...coreTools]; + const activeGroups: string[] = ["core"]; + for (const [group, tools] of Object.entries(toolGroups)) { + if (all || process.env[`MAX_TOOLS_${group}`] === "true") { + enabled.push(...tools); + activeGroups.push(group.toLowerCase()); + } + } + log("info", `Tool groups active: ${activeGroups.join(", ")} (${enabled.length} tools)`); + return enabled; +} + +const staticTools: AgentTool[] = buildStaticTools(); + function inferProvider(modelId: string): "anthropic" | "google" { return modelId.startsWith("claude") ? "anthropic" : "google"; } diff --git a/src/memory.ts b/src/memory.ts index 06e67d7..6d8b5e7 100644 --- a/src/memory.ts +++ b/src/memory.ts @@ -1,10 +1,13 @@ import { readFile, writeFile, mkdir } from "fs/promises"; import { existsSync } from "fs"; import path from "path"; +import { log } from "./logger.js"; const MAX_HOME = path.join(process.env.HOME!, "max"); const MEMORY_DIR = path.join(MAX_HOME, "memory"); +const MAX_MEMORY_FILE_CHARS = Number(process.env.MAX_MEMORY_FILE_CHARS) || 20_000; + function formatDate(d: Date): string { return d.toISOString().slice(0, 10); } @@ -17,7 +20,12 @@ function subDays(d: Date, n: number): Date { async function readFileSafe(p: string): Promise { try { - return await readFile(p, "utf-8"); + const content = await readFile(p, "utf-8"); + if (content.length > MAX_MEMORY_FILE_CHARS) { + log("warn", `Memory file ${p} is ${content.length} chars, exceeds cap of ${MAX_MEMORY_FILE_CHARS}; truncating head`); + return content.slice(0, MAX_MEMORY_FILE_CHARS) + `\n\n[truncated ${content.length - MAX_MEMORY_FILE_CHARS} chars — cap is MAX_MEMORY_FILE_CHARS=${MAX_MEMORY_FILE_CHARS}]`; + } + return content; } catch { return ""; }