/** * Agent memory block management * Loads memory blocks from .mdx files in src/agent/prompts */ import type { CreateBlock } from "@letta-ai/letta-client/resources/blocks/blocks"; import { READ_ONLY_BLOCK_LABELS } from "./memoryConstants"; import { MEMORY_PROMPTS } from "./promptAssets"; /** * Block labels that are stored globally (shared across all projects). */ export const GLOBAL_BLOCK_LABELS = ["persona", "human"] as const; /** * Block labels that are stored per-project (local to the current directory). * Note: skills/loaded_skills removed in LET-7353 - skills are now injected via system reminders. */ export const PROJECT_BLOCK_LABELS = [] as const; /** * All available memory block labels (derived from global + project blocks) */ export const MEMORY_BLOCK_LABELS = [ ...GLOBAL_BLOCK_LABELS, ...PROJECT_BLOCK_LABELS, ] as const; /** * Type for memory block labels */ export type MemoryBlockLabel = (typeof MEMORY_BLOCK_LABELS)[number]; /** * Block labels that should be read-only (agent cannot modify via memory tools). */ export { READ_ONLY_BLOCK_LABELS }; /** * Block labels that should be isolated per-conversation. * Note: skills/loaded_skills removed in LET-7353. */ export const ISOLATED_BLOCK_LABELS = [] as const; /** * Check if a block label is a project-level block */ export function isProjectBlock(label: string): boolean { return (PROJECT_BLOCK_LABELS as readonly string[]).includes(label); } /** * Parse frontmatter and content from an .mdx file */ export function parseMdxFrontmatter(content: string): { frontmatter: Record; body: string; } { const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/; const match = content.match(frontmatterRegex); if (!match || !match[1] || !match[2]) { return { frontmatter: {}, body: content }; } const frontmatterText = match[1]; const body = match[2]; const frontmatter: Record = {}; // Parse YAML-like frontmatter (simple key: value pairs) for (const line of frontmatterText.split("\n")) { const colonIndex = line.indexOf(":"); if (colonIndex > 0) { const key = line.slice(0, colonIndex).trim(); const value = line.slice(colonIndex + 1).trim(); frontmatter[key] = value; } } return { frontmatter, body: body.trim() }; } /** * Load memory blocks from .mdx files in src/agent/prompts */ async function loadMemoryBlocksFromMdx(): Promise { const memoryBlocks: CreateBlock[] = []; const mdxFiles = MEMORY_BLOCK_LABELS.map((label) => `${label}.mdx`); for (const filename of mdxFiles) { try { const content = MEMORY_PROMPTS[filename]; if (!content) { console.warn(`Missing embedded prompt file: ${filename}`); continue; } const { frontmatter, body } = parseMdxFrontmatter(content); const label = frontmatter.label || filename.replace(".mdx", ""); const block: CreateBlock = { label, value: body, }; if (frontmatter.description) { block.description = frontmatter.description; } if (frontmatter.limit) { const limit = parseInt(frontmatter.limit, 10); if (!Number.isNaN(limit) && limit > 0) { block.limit = limit; } } // Set read-only for blocks managed by specific tools (not memory tools) if ((READ_ONLY_BLOCK_LABELS as readonly string[]).includes(label)) { block.read_only = true; } memoryBlocks.push(block); } catch (error) { console.error(`Error loading ${filename}:`, error); } } return memoryBlocks; } // Cache for loaded memory blocks let cachedMemoryBlocks: CreateBlock[] | null = null; /** * Get default starter memory blocks for new agents */ export async function getDefaultMemoryBlocks(): Promise { if (!cachedMemoryBlocks) { cachedMemoryBlocks = await loadMemoryBlocksFromMdx(); } return cachedMemoryBlocks; }