diff --git a/src/agent/create.ts b/src/agent/create.ts index c8d3156..9a87834 100644 --- a/src/agent/create.ts +++ b/src/agent/create.ts @@ -89,14 +89,44 @@ export async function createAgent( getServerToolName(name), ); + const baseMemoryTool = modelHandle.startsWith("anthropic/") + ? "memory" + : "memory_apply_patch"; const defaultBaseTools = baseTools ?? [ - "memory", + baseMemoryTool, "web_search", "conversation_search", "fetch_webpage", ]; - const toolNames = [...serverToolNames, ...defaultBaseTools]; + let toolNames = [...serverToolNames, ...defaultBaseTools]; + + // Fallback: if server doesn't have memory_apply_patch, use legacy memory tool + if (toolNames.includes("memory_apply_patch")) { + try { + const resp = await client.tools.list({ name: "memory_apply_patch" }); + const hasMemoryApplyPatch = + Array.isArray(resp.items) && resp.items.length > 0; + if (!hasMemoryApplyPatch) { + console.warn( + "memory_apply_patch tool not found on server; falling back to 'memory' tool", + ); + toolNames = toolNames.map((n) => + n === "memory_apply_patch" ? "memory" : n, + ); + } + } catch (err) { + // If the capability check fails for any reason, conservatively fall back to 'memory' + console.warn( + `Unable to verify memory_apply_patch availability (falling back to 'memory'): ${ + err instanceof Error ? err.message : String(err) + }`, + ); + toolNames = toolNames.map((n) => + n === "memory_apply_patch" ? "memory" : n, + ); + } + } // Load memory blocks from .mdx files const defaultMemoryBlocks = @@ -312,6 +342,8 @@ export async function createAgent( enable_sleeptime: enableSleeptime, }); + // Note: Preflight check above falls back to 'memory' when 'memory_apply_patch' is unavailable. + // Apply updateArgs if provided (e.g., reasoningEffort, verbosity, etc.) // Skip if updateArgs only contains context_window (already set in create) if (updateArgs && Object.keys(updateArgs).length > 0) { diff --git a/src/agent/prompts/skill_unload_reminder.txt b/src/agent/prompts/skill_unload_reminder.txt index a927486..4f8088a 100644 --- a/src/agent/prompts/skill_unload_reminder.txt +++ b/src/agent/prompts/skill_unload_reminder.txt @@ -3,4 +3,4 @@ The `loaded_skills` block has at least one skill loaded. You should: 1. Check if loaded skills are relevant for the current task. 2. For any skills that are irrelevant, unload them using the `memory` tool. If the block will be empty after unloading, add a "[CURRENTLY EMPTY]" tag. - \ No newline at end of file + diff --git a/src/tools/toolset.ts b/src/tools/toolset.ts index e25eeea..adfa365 100644 --- a/src/tools/toolset.ts +++ b/src/tools/toolset.ts @@ -149,6 +149,65 @@ export async function forceToolsetSwitch( // Remove old Letta tools and add new ones await unlinkToolsFromAgent(agentId); await linkToolsToAgent(agentId); + + // Ensure base memory tool uses memory_apply_patch instead of legacy memory + try { + const client = await getClient(); + const agent = await client.agents.retrieve(agentId, { + include: ["agent.tools"], + }); + + const currentTools = agent.tools || []; + const mapByName = new Map(currentTools.map((t) => [t.name, t.id])); + + // Determine which memory tool we want based on toolset + const desiredMemoryTool = + toolsetName === "default" ? "memory" : "memory_apply_patch"; + const otherMemoryTool = + desiredMemoryTool === "memory" ? "memory_apply_patch" : "memory"; + + // Ensure desired memory tool is attached + let desiredId = mapByName.get(desiredMemoryTool); + if (!desiredId) { + const resp = await client.tools.list({ name: desiredMemoryTool }); + desiredId = resp.items[0]?.id; + } + if (!desiredId) { + console.warn( + `Could not find tool id for ${desiredMemoryTool}. Keeping existing memory tool if present.`, + ); + } + + const otherId = mapByName.get(otherMemoryTool); + + // Build new tool_ids: add desired memory tool, remove the other if present + const currentIds = currentTools + .map((t) => t.id) + .filter((id): id is string => typeof id === "string"); + const newIds = new Set(currentIds); + + // Only swap if we have a valid desired tool id; otherwise keep existing + if (desiredId) { + if (otherId) newIds.delete(otherId); + newIds.add(desiredId); + } + + // Update tool_rules: rewrite any rules targeting the other tool to the desired tool + const updatedRules = (agent.tool_rules || []).map((r) => + r.tool_name === otherMemoryTool + ? { ...r, tool_name: desiredMemoryTool } + : r, + ); + + await client.agents.update(agentId, { + tool_ids: Array.from(newIds), + tool_rules: updatedRules, + }); + } catch (err) { + console.warn( + `Warning: Failed to enforce memory_apply_patch base tool: ${err instanceof Error ? err.message : String(err)}`, + ); + } } /** @@ -193,6 +252,62 @@ export async function switchToolsetForModel( await unlinkToolsFromAgent(agentId); await linkToolsToAgent(agentId); + // Ensure base memory tool uses memory_apply_patch instead of legacy memory + try { + const agentWithTools = await client.agents.retrieve(agentId, { + include: ["agent.tools"], + }); + const currentTools = agentWithTools.tools || []; + const mapByName = new Map(currentTools.map((t) => [t.name, t.id])); + + // Determine which memory tool we want based on provider + const desiredMemoryTool = isOpenAIModel(resolvedModel) + ? "memory_apply_patch" + : (await import("./manager")).isGeminiModel(resolvedModel) + ? "memory_apply_patch" + : "memory"; + const otherMemoryTool = + desiredMemoryTool === "memory" ? "memory_apply_patch" : "memory"; + + // Ensure desired memory tool attached + let desiredId = mapByName.get(desiredMemoryTool); + if (!desiredId) { + const resp = await client.tools.list({ name: desiredMemoryTool }); + desiredId = resp.items[0]?.id; + } + if (!desiredId) { + console.warn( + `Could not find tool id for ${desiredMemoryTool}. Keeping existing memory tool if present.`, + ); + } + + const otherId = mapByName.get(otherMemoryTool); + + const currentIds = currentTools + .map((t) => t.id) + .filter((id): id is string => typeof id === "string"); + const newIds = new Set(currentIds); + if (desiredId) { + if (otherId) newIds.delete(otherId); + newIds.add(desiredId); + } + + const updatedRules = (agentWithTools.tool_rules || []).map((r) => + r.tool_name === otherMemoryTool + ? { ...r, tool_name: desiredMemoryTool } + : r, + ); + + await client.agents.update(agentId, { + tool_ids: Array.from(newIds), + tool_rules: updatedRules, + }); + } catch (err) { + console.warn( + `Warning: Failed to enforce memory_apply_patch base tool: ${err instanceof Error ? err.message : String(err)}`, + ); + } + const { isGeminiModel } = await import("./manager"); const toolsetName = isOpenAIModel(resolvedModel) ? "codex"