fix: memory defrag skill location (#518)

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
Charles Packer
2026-01-11 19:25:53 -08:00
committed by GitHub
parent 88fa10f0d3
commit 79c92e92cc
4 changed files with 380 additions and 259 deletions

View File

@@ -1,136 +0,0 @@
#!/usr/bin/env bun
/**
* Backup Memory Blocks to Local Files
*
* Exports all memory blocks from an agent to local files for checkpointing and editing.
* Creates a timestamped backup directory with:
* - Individual .md files for each memory block
* - manifest.json with metadata
*
* Usage:
* bun .letta/memory-utils/backup-memory.ts <agent-id> [backup-dir]
*
* Example:
* bun .letta/memory-utils/backup-memory.ts agent-abc123
* bun .letta/memory-utils/backup-memory.ts $LETTA_PARENT_AGENT_ID .letta/backups/working
*/
import { writeFile, mkdir } from "node:fs/promises";
import { join } from "node:path";
import { getClient } from "../../src/agent/client";
import { settingsManager } from "../../src/settings-manager";
interface BackupManifest {
agent_id: string;
timestamp: string;
backup_path: string;
blocks: Array<{
id: string;
label: string;
filename: string;
limit: number;
value_length: number;
}>;
}
async function backupMemory(agentId: string, backupDir?: string): Promise<string> {
await settingsManager.initialize();
const client = await getClient();
// Create backup directory
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const defaultBackupDir = join(process.cwd(), ".letta", "backups", agentId, timestamp);
const backupPath = backupDir || defaultBackupDir;
await mkdir(backupPath, { recursive: true });
console.log(`Backing up memory blocks for agent ${agentId}...`);
console.log(`Backup location: ${backupPath}`);
// Get all memory blocks
const blocksResponse = await client.agents.blocks.list(agentId);
const blocks = Array.isArray(blocksResponse)
? blocksResponse
: (blocksResponse.items || blocksResponse.blocks || []);
console.log(`Found ${blocks.length} memory blocks`);
// Export each block to a file
const manifest: BackupManifest = {
agent_id: agentId,
timestamp: new Date().toISOString(),
backup_path: backupPath,
blocks: [],
};
for (const block of blocks) {
const label = block.label || `block-${block.id}`;
const filename = `${label}.md`;
const filepath = join(backupPath, filename);
// Write block content to file
const content = block.value || "";
await writeFile(filepath, content, "utf-8");
console.log(`${label} -> ${filename} (${content.length} chars)`);
// Add to manifest
manifest.blocks.push({
id: block.id,
label,
filename,
limit: block.limit || 0,
value_length: content.length,
});
}
// Write manifest
const manifestPath = join(backupPath, "manifest.json");
await writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
console.log(` ✓ manifest.json`);
console.log(`\n✅ Backup complete: ${backupPath}`);
return backupPath;
}
// CLI Entry Point
if (import.meta.main) {
const args = process.argv.slice(2);
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
console.log(`
Usage: bun .letta/memory-utils/backup-memory.ts <agent-id> [backup-dir]
Arguments:
agent-id Agent ID to backup (can use $LETTA_PARENT_AGENT_ID)
backup-dir Optional custom backup directory
Default: .letta/backups/<agent-id>/<timestamp>
Examples:
bun .letta/memory-utils/backup-memory.ts agent-abc123
bun .letta/memory-utils/backup-memory.ts $LETTA_PARENT_AGENT_ID
bun .letta/memory-utils/backup-memory.ts agent-abc123 .letta/backups/working
`);
process.exit(0);
}
const agentId = args[0];
const backupDir = args[1];
if (!agentId) {
console.error("Error: agent-id is required");
process.exit(1);
}
backupMemory(agentId, backupDir)
.then((path) => {
// Output just the path for easy capture in scripts
console.log(path);
})
.catch((error) => {
console.error("Error backing up memory:", error.message);
process.exit(1);
});
}
export { backupMemory, type BackupManifest };

View File

@@ -21,18 +21,13 @@ This skill helps you maintain clean, well-organized memory blocks by:
## Workflow ## Workflow
### Step 1: Download Agent File and Dump Memory to Files ### Step 1: Backup Memory to Files
```bash ```bash
# Download agent file to backups npx tsx <SKILL_DIR>/scripts/backup-memory.ts $LETTA_AGENT_ID .letta/backups/working
bun .letta/memory-utils/download-agent.ts $LETTA_AGENT_ID
# Dump memory blocks to files
bun .letta/memory-utils/backup-memory.ts $LETTA_AGENT_ID .letta/backups/working
``` ```
This creates: This creates:
- `.letta/backups/<agent-id>/<timestamp>.af` - Complete agent file backup for full rollback
- `.letta/backups/<agent-id>/<timestamp>/` - Timestamped memory blocks backup - `.letta/backups/<agent-id>/<timestamp>/` - Timestamped memory blocks backup
- `.letta/backups/working/` - Working directory with editable files - `.letta/backups/working/` - Working directory with editable files
- Each memory block as a `.md` file: `persona.md`, `human.md`, `project.md`, etc. - Each memory block as a `.md` file: `persona.md`, `human.md`, `project.md`, etc.
@@ -72,7 +67,7 @@ The memory subagent will:
### Step 3: Restore Cleaned Files to Memory ### Step 3: Restore Cleaned Files to Memory
```bash ```bash
bun .letta/memory-utils/restore-memory.ts $LETTA_AGENT_ID .letta/backups/working npx tsx <SKILL_DIR>/scripts/restore-memory.ts $LETTA_AGENT_ID .letta/backups/working
``` ```
This will: This will:
@@ -84,10 +79,10 @@ This will:
## Example Complete Flow ## Example Complete Flow
```typescript ```typescript
// Step 1: Download agent file and dump memory // Step 1: Backup memory to files
Bash({ Bash({
command: "bun .letta/memory-utils/download-agent.ts $LETTA_AGENT_ID && bun .letta/memory-utils/backup-memory.ts $LETTA_AGENT_ID .letta/backups/working", command: "npx tsx <SKILL_DIR>/scripts/backup-memory.ts $LETTA_AGENT_ID .letta/backups/working",
description: "Download agent file and dump memory to files" description: "Backup memory to files"
}) })
// Step 2: Clean up (subagent edits files and deletes merged ones) // Step 2: Clean up (subagent edits files and deletes merged ones)
@@ -99,35 +94,21 @@ Task({
// Step 3: Restore // Step 3: Restore
Bash({ Bash({
command: "bun .letta/memory-utils/restore-memory.ts $LETTA_AGENT_ID .letta/backups/working", command: "npx tsx <SKILL_DIR>/scripts/restore-memory.ts $LETTA_AGENT_ID .letta/backups/working",
description: "Restore cleaned memory blocks" description: "Restore cleaned memory blocks"
}) })
``` ```
## Rollback ## Rollback
If something goes wrong, you have two rollback options: If something goes wrong, restore from a previous backup:
### Option 1: Restore Memory Blocks Only
```bash ```bash
# Find the backup directory # Find the backup directory
ls -la .letta/backups/<agent-id>/ ls -la .letta/backups/<agent-id>/
# Restore from specific timestamp # Restore from specific timestamp
bun .letta/memory-utils/restore-memory.ts $LETTA_AGENT_ID .letta/backups/<agent-id>/<timestamp> npx tsx <SKILL_DIR>/scripts/restore-memory.ts $LETTA_AGENT_ID .letta/backups/<agent-id>/<timestamp>
```
### Option 2: Full Agent Restore (Nuclear Option)
If memory restoration isn't enough, restore the entire agent from the .af backup:
```bash
# Find the agent backup
ls -la .letta/backups/<agent-id>/*.af
# The .af file can be used to recreate the agent entirely
# Use: letta --from-af .letta/backups/<agent-id>/<timestamp>.af
``` ```
## Dry Run ## Dry Run
@@ -135,20 +116,20 @@ ls -la .letta/backups/<agent-id>/*.af
Preview changes without applying them: Preview changes without applying them:
```bash ```bash
bun .letta/memory-utils/restore-memory.ts $LETTA_AGENT_ID .letta/backups/working --dry-run npx tsx <SKILL_DIR>/scripts/restore-memory.ts $LETTA_AGENT_ID .letta/backups/working --dry-run
``` ```
## What the Memory Subagent Does ## What the Memory Subagent Does
The memory subagent focuses on cleaning up files. It: The memory subagent focuses on cleaning up files. It:
- Reads files from `.letta/backups/working/` - Reads files from `.letta/backups/working/`
- Edits files to improve structure and consolidate redundancy - Edits files to improve structure and consolidate redundancy
- Merges related blocks together to reduce fragmentation - Merges related blocks together to reduce fragmentation
- Reorganizes information for better clarity and scannability - Reorganizes information for better clarity and scannability
- Deletes source files after merging their content (using Bash `rm` command) - Deletes source files after merging their content (using Bash `rm` command)
- Provides detailed before/after reports including merge operations - Provides detailed before/after reports including merge operations
- Does NOT run backup scripts (main agent does this) - Does NOT run backup scripts (main agent does this)
- Does NOT run restore scripts (main agent does this) - Does NOT run restore scripts (main agent does this)
The memory subagent runs with `bypassPermissions` mode, giving it full Bash access to delete files after merging them. The focus is on consolidation and reorganization. The memory subagent runs with `bypassPermissions` mode, giving it full Bash access to delete files after merging them. The focus is on consolidation and reorganization.
@@ -169,10 +150,10 @@ The memory subagent runs with `bypassPermissions` mode, giving it full Bash acce
- After merging blocks, DELETE the source files to avoid duplication - After merging blocks, DELETE the source files to avoid duplication
**When to DELETE a file:** **When to DELETE a file:**
- **After merging** - You've consolidated its content into another block (common and encouraged) - After merging - You've consolidated its content into another block (common and encouraged)
- **Junk data** - File contains only irrelevant test/junk data with no project connection - Junk data - File contains only irrelevant test/junk data with no project connection
- **Empty/deprecated** - File is just a notice with no unique information - Empty/deprecated - File is just a notice with no unique information
- **Don't delete** - If file has unique information that hasn't been merged elsewhere - Don't delete - If file has unique information that hasn't been merged elsewhere
**What to preserve:** **What to preserve:**
- User preferences (sacred - never delete) - User preferences (sacred - never delete)

View File

@@ -0,0 +1,198 @@
#!/usr/bin/env npx tsx
/**
* Backup Memory Blocks to Local Files
*
* Exports all memory blocks from an agent to local files for checkpointing and editing.
* Creates a timestamped backup directory with:
* - Individual .md files for each memory block
* - manifest.json with metadata
*
* This script is standalone and can be run outside the CLI process.
* It reads auth from LETTA_API_KEY env var or ~/.letta/settings.json.
*
* Usage:
* npx tsx backup-memory.ts <agent-id> [backup-dir]
*
* Example:
* npx tsx backup-memory.ts agent-abc123
* npx tsx backup-memory.ts $LETTA_AGENT_ID .letta/backups/working
*/
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { createRequire } from "node:module";
import { homedir } from "node:os";
import { join } from "node:path";
// Use createRequire for @letta-ai/letta-client so NODE_PATH is respected
// (ES module imports don't respect NODE_PATH, but require does)
const require = createRequire(import.meta.url);
const Letta = require("@letta-ai/letta-client")
.default as typeof import("@letta-ai/letta-client").default;
type LettaClient = InstanceType<typeof Letta>;
export interface BackupManifest {
agent_id: string;
timestamp: string;
backup_path: string;
blocks: Array<{
id: string;
label: string;
filename: string;
limit: number;
value_length: number;
}>;
}
/**
* Get API key from env var or settings file
*/
function getApiKey(): string {
if (process.env.LETTA_API_KEY) {
return process.env.LETTA_API_KEY;
}
const settingsPath = join(homedir(), ".letta", "settings.json");
try {
const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
if (settings.env?.LETTA_API_KEY) {
return settings.env.LETTA_API_KEY;
}
} catch {
// Settings file doesn't exist or is invalid
}
throw new Error(
"No LETTA_API_KEY found. Set the env var or run the Letta CLI to authenticate.",
);
}
/**
* Create a Letta client with auth from env/settings
*/
function createClient(): LettaClient {
return new Letta({ apiKey: getApiKey() });
}
/**
* Backup memory blocks to local files
*/
async function backupMemory(
agentId: string,
backupDir?: string,
): Promise<string> {
const client = createClient();
// Create backup directory
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const defaultBackupDir = join(
process.cwd(),
".letta",
"backups",
agentId,
timestamp,
);
const backupPath = backupDir || defaultBackupDir;
mkdirSync(backupPath, { recursive: true });
console.log(`Backing up memory blocks for agent ${agentId}...`);
console.log(`Backup location: ${backupPath}`);
// Get all memory blocks
const blocksResponse = await client.agents.blocks.list(agentId);
const blocks = Array.isArray(blocksResponse)
? blocksResponse
: (blocksResponse as { items?: unknown[] }).items ||
(blocksResponse as { blocks?: unknown[] }).blocks ||
[];
console.log(`Found ${blocks.length} memory blocks`);
// Export each block to a file
const manifest: BackupManifest = {
agent_id: agentId,
timestamp: new Date().toISOString(),
backup_path: backupPath,
blocks: [],
};
for (const block of blocks as Array<{
id: string;
label?: string;
value?: string;
limit?: number;
}>) {
const label = block.label || `block-${block.id}`;
const filename = `${label}.md`;
const filepath = join(backupPath, filename);
// Write block content to file
const content = block.value || "";
writeFileSync(filepath, content, "utf-8");
console.log(`${label} -> ${filename} (${content.length} chars)`);
// Add to manifest
manifest.blocks.push({
id: block.id,
label,
filename,
limit: block.limit || 0,
value_length: content.length,
});
}
// Write manifest
const manifestPath = join(backupPath, "manifest.json");
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
console.log(` ✓ manifest.json`);
console.log(`\n✅ Backup complete: ${backupPath}`);
return backupPath;
}
// CLI Entry Point - check if this file is being run directly
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
if (isMainModule) {
const args = process.argv.slice(2);
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
console.log(`
Usage: npx tsx backup-memory.ts <agent-id> [backup-dir]
Arguments:
agent-id Agent ID to backup (can use $LETTA_AGENT_ID)
backup-dir Optional custom backup directory
Default: .letta/backups/<agent-id>/<timestamp>
Examples:
npx tsx backup-memory.ts agent-abc123
npx tsx backup-memory.ts $LETTA_AGENT_ID
npx tsx backup-memory.ts agent-abc123 .letta/backups/working
`);
process.exit(0);
}
const agentId = args[0];
const backupDir = args[1];
if (!agentId) {
console.error("Error: agent-id is required");
process.exit(1);
}
backupMemory(agentId, backupDir)
.then((path) => {
// Output just the path for easy capture in scripts
console.log(path);
})
.catch((error) => {
console.error(
"Error backing up memory:",
error instanceof Error ? error.message : String(error),
);
process.exit(1);
});
}
export { backupMemory };

View File

@@ -1,31 +1,74 @@
#!/usr/bin/env bun #!/usr/bin/env npx tsx
/** /**
* Restore Memory Blocks from Local Files * Restore Memory Blocks from Local Files
* *
* Imports memory blocks from local files back into an agent. * Imports memory blocks from local files back into an agent.
* Reads files from a backup directory and updates the agent's memory blocks. * Reads files from a backup directory and updates the agent's memory blocks.
* *
* This script is standalone and can be run outside the CLI process.
* It reads auth from LETTA_API_KEY env var or ~/.letta/settings.json.
*
* Usage: * Usage:
* bun .letta/memory-utils/restore-memory.ts <agent-id> <backup-dir> * npx tsx restore-memory.ts <agent-id> <backup-dir> [options]
* *
* Example: * Example:
* bun .letta/memory-utils/restore-memory.ts agent-abc123 .letta/backups/working * npx tsx restore-memory.ts agent-abc123 .letta/backups/working
* bun .letta/memory-utils/restore-memory.ts $LETTA_PARENT_AGENT_ID .letta/backups/working * npx tsx restore-memory.ts $LETTA_AGENT_ID .letta/backups/working --dry-run
*/ */
import { readFile, readdir } from "node:fs/promises"; import { readdirSync, readFileSync } from "node:fs";
import { join, extname } from "node:path"; import { createRequire } from "node:module";
import { getClient } from "../../src/agent/client"; import { homedir } from "node:os";
import { settingsManager } from "../../src/settings-manager"; import { extname, join } from "node:path";
import type { BackupManifest } from "./backup-memory"; import type { BackupManifest } from "./backup-memory";
// Use createRequire for @letta-ai/letta-client so NODE_PATH is respected
// (ES module imports don't respect NODE_PATH, but require does)
const require = createRequire(import.meta.url);
const Letta = require("@letta-ai/letta-client")
.default as typeof import("@letta-ai/letta-client").default;
type LettaClient = InstanceType<typeof Letta>;
/**
* Get API key from env var or settings file
*/
function getApiKey(): string {
if (process.env.LETTA_API_KEY) {
return process.env.LETTA_API_KEY;
}
const settingsPath = join(homedir(), ".letta", "settings.json");
try {
const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
if (settings.env?.LETTA_API_KEY) {
return settings.env.LETTA_API_KEY;
}
} catch {
// Settings file doesn't exist or is invalid
}
throw new Error(
"No LETTA_API_KEY found. Set the env var or run the Letta CLI to authenticate.",
);
}
/**
* Create a Letta client with auth from env/settings
*/
function createClient(): LettaClient {
return new Letta({ apiKey: getApiKey() });
}
/**
* Restore memory blocks from local files
*/
async function restoreMemory( async function restoreMemory(
agentId: string, agentId: string,
backupDir: string, backupDir: string,
options: { dryRun?: boolean } = {}, options: { dryRun?: boolean } = {},
): Promise<void> { ): Promise<void> {
await settingsManager.initialize(); const client = createClient();
const client = await getClient();
console.log(`Restoring memory blocks for agent ${agentId}...`); console.log(`Restoring memory blocks for agent ${agentId}...`);
console.log(`Source: ${backupDir}`); console.log(`Source: ${backupDir}`);
@@ -39,22 +82,34 @@ async function restoreMemory(
let manifest: BackupManifest | null = null; let manifest: BackupManifest | null = null;
try { try {
const manifestContent = await readFile(manifestPath, "utf-8"); const manifestContent = readFileSync(manifestPath, "utf-8");
manifest = JSON.parse(manifestContent); manifest = JSON.parse(manifestContent);
console.log(`Loaded manifest (${manifest.blocks.length} blocks)\n`); console.log(`Loaded manifest (${manifest?.blocks.length} blocks)\n`);
} catch (error) { } catch {
console.warn("Warning: No manifest.json found, will scan directory for .md files"); console.warn(
"Warning: No manifest.json found, will scan directory for .md files",
);
} }
// Get current agent blocks // Get current agent blocks
const blocksResponse = await client.agents.blocks.list(agentId); const blocksResponse = await client.agents.blocks.list(agentId);
const currentBlocks = Array.isArray(blocksResponse) const currentBlocks = Array.isArray(blocksResponse)
? blocksResponse ? blocksResponse
: (blocksResponse.items || blocksResponse.blocks || []); : (blocksResponse as { items?: unknown[] }).items ||
const blocksByLabel = new Map(currentBlocks.map((b) => [b.label, b])); (blocksResponse as { blocks?: unknown[] }).blocks ||
[];
const blocksByLabel = new Map(
(currentBlocks as Array<{ label: string; id: string; value?: string }>).map(
(b) => [b.label, b],
),
);
// Determine which files to restore // Determine which files to restore
let filesToRestore: Array<{ label: string; filename: string; blockId?: string }> = []; let filesToRestore: Array<{
label: string;
filename: string;
blockId?: string;
}> = [];
if (manifest) { if (manifest) {
// Use manifest // Use manifest
@@ -65,7 +120,7 @@ async function restoreMemory(
})); }));
} else { } else {
// Scan directory for .md files // Scan directory for .md files
const files = await readdir(backupDir); const files = readdirSync(backupDir);
filesToRestore = files filesToRestore = files
.filter((f) => extname(f) === ".md") .filter((f) => extname(f) === ".md")
.map((f) => ({ .map((f) => ({
@@ -78,7 +133,9 @@ async function restoreMemory(
// Detect blocks to delete (exist on agent but not in backup) // Detect blocks to delete (exist on agent but not in backup)
const backupLabels = new Set(filesToRestore.map((f) => f.label)); const backupLabels = new Set(filesToRestore.map((f) => f.label));
const blocksToDelete = currentBlocks.filter((b) => !backupLabels.has(b.label)); const blocksToDelete = (
currentBlocks as Array<{ label: string; id: string }>
).filter((b) => !backupLabels.has(b.label));
// Restore each block // Restore each block
let updated = 0; let updated = 0;
@@ -87,13 +144,17 @@ async function restoreMemory(
let deleted = 0; let deleted = 0;
// Track new blocks for later confirmation // Track new blocks for later confirmation
const blocksToCreate: Array<{ label: string; value: string; description: string }> = []; const blocksToCreate: Array<{
label: string;
value: string;
description: string;
}> = [];
for (const { label, filename } of filesToRestore) { for (const { label, filename } of filesToRestore) {
const filepath = join(backupDir, filename); const filepath = join(backupDir, filename);
try { try {
const newValue = await readFile(filepath, "utf-8"); const newValue = readFileSync(filepath, "utf-8");
const existingBlock = blocksByLabel.get(label); const existingBlock = blocksByLabel.get(label);
if (existingBlock) { if (existingBlock) {
@@ -118,7 +179,9 @@ async function restoreMemory(
const diff = newLen - oldLen; const diff = newLen - oldLen;
const diffStr = diff > 0 ? `+${diff}` : `${diff}`; const diffStr = diff > 0 ? `+${diff}` : `${diff}`;
console.log(`${label} - updated (${oldLen} -> ${newLen} chars, ${diffStr})`); console.log(
`${label} - updated (${oldLen} -> ${newLen} chars, ${diffStr})`,
);
updated++; updated++;
} else { } else {
// New block - collect for later confirmation // New block - collect for later confirmation
@@ -130,7 +193,9 @@ async function restoreMemory(
}); });
} }
} catch (error) { } catch (error) {
console.error(`${label} - error: ${error.message}`); console.error(
`${label} - error: ${error instanceof Error ? error.message : String(error)}`,
);
} }
} }
@@ -143,11 +208,13 @@ async function restoreMemory(
if (!options.dryRun) { if (!options.dryRun) {
console.log(`\nThese blocks will be CREATED on the agent.`); console.log(`\nThese blocks will be CREATED on the agent.`);
console.log(`Press Ctrl+C to cancel, or press Enter to confirm creation...`); console.log(
`Press Ctrl+C to cancel, or press Enter to confirm creation...`,
);
// Wait for user confirmation // Wait for user confirmation
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
process.stdin.once('data', () => resolve()); process.stdin.once("data", () => resolve());
}); });
console.log(); console.log();
@@ -173,7 +240,9 @@ async function restoreMemory(
console.log(`${block.label} - created and attached`); console.log(`${block.label} - created and attached`);
created++; created++;
} catch (error) { } catch (error) {
console.error(`${block.label} - error creating: ${error.message}`); console.error(
`${block.label} - error creating: ${error instanceof Error ? error.message : String(error)}`,
);
} }
} }
} else { } else {
@@ -183,18 +252,22 @@ async function restoreMemory(
// Handle deletions (blocks that exist on agent but not in backup) // Handle deletions (blocks that exist on agent but not in backup)
if (blocksToDelete.length > 0) { if (blocksToDelete.length > 0) {
console.log(`\n⚠ Found ${blocksToDelete.length} block(s) that were removed from backup:`); console.log(
`\n⚠ Found ${blocksToDelete.length} block(s) that were removed from backup:`,
);
for (const block of blocksToDelete) { for (const block of blocksToDelete) {
console.log(` - ${block.label}`); console.log(` - ${block.label}`);
} }
if (!options.dryRun) { if (!options.dryRun) {
console.log(`\nThese blocks will be DELETED from the agent.`); console.log(`\nThese blocks will be DELETED from the agent.`);
console.log(`Press Ctrl+C to cancel, or press Enter to confirm deletion...`); console.log(
`Press Ctrl+C to cancel, or press Enter to confirm deletion...`,
);
// Wait for user confirmation // Wait for user confirmation
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
process.stdin.once('data', () => resolve()); process.stdin.once("data", () => resolve());
}); });
console.log(); console.log();
@@ -206,7 +279,9 @@ async function restoreMemory(
console.log(` 🗑️ ${block.label} - deleted`); console.log(` 🗑️ ${block.label} - deleted`);
deleted++; deleted++;
} catch (error) { } catch (error) {
console.error(`${block.label} - error deleting: ${error.message}`); console.error(
`${block.label} - error deleting: ${error instanceof Error ? error.message : String(error)}`,
);
} }
} }
} else { } else {
@@ -228,25 +303,26 @@ async function restoreMemory(
} }
} }
// CLI Entry Point // CLI Entry Point - check if this file is being run directly
if (import.meta.main) { const isMainModule = import.meta.url === `file://${process.argv[1]}`;
if (isMainModule) {
const args = process.argv.slice(2); const args = process.argv.slice(2);
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") { if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
console.log(` console.log(`
Usage: bun .letta/memory-utils/restore-memory.ts <agent-id> <backup-dir> [options] Usage: npx tsx restore-memory.ts <agent-id> <backup-dir> [options]
Arguments: Arguments:
agent-id Agent ID to restore to (can use $LETTA_PARENT_AGENT_ID) agent-id Agent ID to restore to (can use $LETTA_AGENT_ID)
backup-dir Backup directory containing memory block files backup-dir Backup directory containing memory block files
Options: Options:
--dry-run Preview changes without applying them --dry-run Preview changes without applying them
Examples: Examples:
bun .letta/memory-utils/restore-memory.ts agent-abc123 .letta/backups/working npx tsx restore-memory.ts agent-abc123 .letta/backups/working
bun .letta/memory-utils/restore-memory.ts $LETTA_PARENT_AGENT_ID .letta/backups/working npx tsx restore-memory.ts $LETTA_AGENT_ID .letta/backups/working
bun .letta/memory-utils/restore-memory.ts agent-abc123 .letta/backups/working --dry-run npx tsx restore-memory.ts agent-abc123 .letta/backups/working --dry-run
`); `);
process.exit(0); process.exit(0);
} }
@@ -260,9 +336,11 @@ Examples:
process.exit(1); process.exit(1);
} }
restoreMemory(agentId, backupDir, { dryRun }) restoreMemory(agentId, backupDir, { dryRun }).catch((error) => {
.catch((error) => { console.error(
console.error("Error restoring memory:", error.message); "Error restoring memory:",
error instanceof Error ? error.message : String(error),
);
process.exit(1); process.exit(1);
}); });
} }