From c6d43a1a251d27d739b415505ec772d5b15b1d66 Mon Sep 17 00:00:00 2001 From: Charles Packer Date: Thu, 15 Jan 2026 19:18:37 -0800 Subject: [PATCH] fix: auto-create missing skills blocks when resuming older agents (#559) Co-authored-by: Letta --- src/agent/memory.ts | 63 +++++++++++++++++++++++++++++++++++++++++++++ src/headless.ts | 8 +++++- src/index.ts | 8 +++++- 3 files changed, 77 insertions(+), 2 deletions(-) diff --git a/src/agent/memory.ts b/src/agent/memory.ts index 56dbe3f..f469f18 100644 --- a/src/agent/memory.ts +++ b/src/agent/memory.ts @@ -144,3 +144,66 @@ export async function getDefaultMemoryBlocks(): Promise { } return cachedMemoryBlocks; } + +/** + * Ensure an agent has the required skills blocks (skills, loaded_skills). + * If missing, creates them with default values and attaches to the agent. + * This is needed for backwards compatibility with agents created before skills were added. + * + * @param agentId - The agent ID to check/update + * @returns Array of block labels that were created (empty if all existed) + */ +export async function ensureSkillsBlocks(agentId: string): Promise { + const { getClient } = await import("./client"); + const client = await getClient(); + + // Get current blocks on the agent + // Response may be paginated or an array depending on SDK version + const blocksResponse = await client.agents.blocks.list(agentId); + const blocks = Array.isArray(blocksResponse) + ? blocksResponse + : (blocksResponse as { items?: Array<{ label?: string }> }).items || []; + const existingLabels = new Set(blocks.map((b) => b.label)); + + const createdLabels: string[] = []; + + // Check each required skills block + for (const label of ISOLATED_BLOCK_LABELS) { + if (existingLabels.has(label)) { + continue; + } + + // Load the default block definition from .mdx + const content = MEMORY_PROMPTS[`${label}.mdx`]; + if (!content) { + console.warn(`Missing embedded prompt file for ${label}.mdx`); + continue; + } + + const { frontmatter, body } = parseMdxFrontmatter(content); + + const blockData: CreateBlock = { + label, + value: body, + read_only: true, // Skills blocks are read-only (managed by Skill tool) + }; + + if (frontmatter.description) { + blockData.description = frontmatter.description; + } + + if (frontmatter.limit) { + const limit = parseInt(frontmatter.limit, 10); + if (!Number.isNaN(limit) && limit > 0) { + blockData.limit = limit; + } + } + + // Create the block and attach to agent + const createdBlock = await client.blocks.create(blockData); + await client.agents.blocks.attach(createdBlock.id, { agent_id: agentId }); + createdLabels.push(label); + } + + return createdLabels; +} diff --git a/src/headless.ts b/src/headless.ts index 937aa45..a2f06bd 100644 --- a/src/headless.ts +++ b/src/headless.ts @@ -16,7 +16,7 @@ import { import { getClient } from "./agent/client"; import { initializeLoadedSkillsFlag, setAgentContext } from "./agent/context"; import { createAgent } from "./agent/create"; -import { ISOLATED_BLOCK_LABELS } from "./agent/memory"; +import { ensureSkillsBlocks, ISOLATED_BLOCK_LABELS } from "./agent/memory"; import { sendMessageStream } from "./agent/message"; import { getModelUpdateArgs } from "./agent/model"; import { SessionStats } from "./agent/stats"; @@ -529,6 +529,12 @@ export async function handleHeadlessCommand( conversationId, }); + // Ensure the agent has the required skills blocks (for backwards compatibility) + const createdBlocks = await ensureSkillsBlocks(agent.id); + if (createdBlocks.length > 0) { + console.log("Created missing skills blocks for agent compatibility"); + } + // Set agent context for tools that need it (e.g., Skill tool, Task tool) setAgentContext(agent.id, skillsDirectory); await initializeLoadedSkillsFlag(); diff --git a/src/index.ts b/src/index.ts index 3f350a9..63a5ef8 100755 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,7 @@ import { getResumeData, type ResumeData } from "./agent/check-approval"; import { getClient } from "./agent/client"; import { initializeLoadedSkillsFlag, setAgentContext } from "./agent/context"; import type { AgentProvenance } from "./agent/create"; -import { ISOLATED_BLOCK_LABELS } from "./agent/memory"; +import { ensureSkillsBlocks, ISOLATED_BLOCK_LABELS } from "./agent/memory"; import { LETTA_CLOUD_API_URL } from "./auth/oauth"; import { ConversationSelector } from "./cli/components/ConversationSelector"; import type { ApprovalRequest } from "./cli/helpers/stream"; @@ -1227,6 +1227,12 @@ async function main(): Promise { settingsManager.updateLocalProjectSettings({ lastAgent: agent.id }); settingsManager.updateSettings({ lastAgent: agent.id }); + // Ensure the agent has the required skills blocks (for backwards compatibility) + const createdBlocks = await ensureSkillsBlocks(agent.id); + if (createdBlocks.length > 0) { + console.log("Created missing skills blocks for agent compatibility"); + } + // Set agent context for tools that need it (e.g., Skill tool) setAgentContext(agent.id, skillsDirectory); await initializeLoadedSkillsFlag();