136
.letta/memory-utils/backup-memory.ts
Executable file
136
.letta/memory-utils/backup-memory.ts
Executable file
@@ -0,0 +1,136 @@
|
||||
#!/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 };
|
||||
270
.letta/memory-utils/restore-memory.ts
Executable file
270
.letta/memory-utils/restore-memory.ts
Executable file
@@ -0,0 +1,270 @@
|
||||
#!/usr/bin/env bun
|
||||
/**
|
||||
* Restore Memory Blocks from Local Files
|
||||
*
|
||||
* Imports memory blocks from local files back into an agent.
|
||||
* Reads files from a backup directory and updates the agent's memory blocks.
|
||||
*
|
||||
* Usage:
|
||||
* bun .letta/memory-utils/restore-memory.ts <agent-id> <backup-dir>
|
||||
*
|
||||
* Example:
|
||||
* bun .letta/memory-utils/restore-memory.ts agent-abc123 .letta/backups/working
|
||||
* bun .letta/memory-utils/restore-memory.ts $LETTA_PARENT_AGENT_ID .letta/backups/working
|
||||
*/
|
||||
|
||||
import { readFile, readdir } from "node:fs/promises";
|
||||
import { join, extname } from "node:path";
|
||||
import { getClient } from "../../src/agent/client";
|
||||
import { settingsManager } from "../../src/settings-manager";
|
||||
import type { BackupManifest } from "./backup-memory";
|
||||
|
||||
async function restoreMemory(
|
||||
agentId: string,
|
||||
backupDir: string,
|
||||
options: { dryRun?: boolean } = {},
|
||||
): Promise<void> {
|
||||
await settingsManager.initialize();
|
||||
const client = await getClient();
|
||||
|
||||
console.log(`Restoring memory blocks for agent ${agentId}...`);
|
||||
console.log(`Source: ${backupDir}`);
|
||||
|
||||
if (options.dryRun) {
|
||||
console.log("⚠️ DRY RUN MODE - No changes will be made\n");
|
||||
}
|
||||
|
||||
// Read manifest
|
||||
const manifestPath = join(backupDir, "manifest.json");
|
||||
let manifest: BackupManifest | null = null;
|
||||
|
||||
try {
|
||||
const manifestContent = await readFile(manifestPath, "utf-8");
|
||||
manifest = JSON.parse(manifestContent);
|
||||
console.log(`Loaded manifest (${manifest.blocks.length} blocks)\n`);
|
||||
} catch (error) {
|
||||
console.warn("Warning: No manifest.json found, will scan directory for .md files");
|
||||
}
|
||||
|
||||
// Get current agent blocks
|
||||
const blocksResponse = await client.agents.blocks.list(agentId);
|
||||
const currentBlocks = Array.isArray(blocksResponse)
|
||||
? blocksResponse
|
||||
: (blocksResponse.items || blocksResponse.blocks || []);
|
||||
const blocksByLabel = new Map(currentBlocks.map((b) => [b.label, b]));
|
||||
|
||||
// Determine which files to restore
|
||||
let filesToRestore: Array<{ label: string; filename: string; blockId?: string }> = [];
|
||||
|
||||
if (manifest) {
|
||||
// Use manifest
|
||||
filesToRestore = manifest.blocks.map((b) => ({
|
||||
label: b.label,
|
||||
filename: b.filename,
|
||||
blockId: b.id,
|
||||
}));
|
||||
} else {
|
||||
// Scan directory for .md files
|
||||
const files = await readdir(backupDir);
|
||||
filesToRestore = files
|
||||
.filter((f) => extname(f) === ".md")
|
||||
.map((f) => ({
|
||||
label: f.replace(/\.md$/, ""),
|
||||
filename: f,
|
||||
}));
|
||||
}
|
||||
|
||||
console.log(`Found ${filesToRestore.length} files to restore\n`);
|
||||
|
||||
// Detect blocks to delete (exist on agent but not in backup)
|
||||
const backupLabels = new Set(filesToRestore.map((f) => f.label));
|
||||
const blocksToDelete = currentBlocks.filter((b) => !backupLabels.has(b.label));
|
||||
|
||||
// Restore each block
|
||||
let updated = 0;
|
||||
let created = 0;
|
||||
let skipped = 0;
|
||||
let deleted = 0;
|
||||
|
||||
// Track new blocks for later confirmation
|
||||
const blocksToCreate: Array<{ label: string; value: string; description: string }> = [];
|
||||
|
||||
for (const { label, filename } of filesToRestore) {
|
||||
const filepath = join(backupDir, filename);
|
||||
|
||||
try {
|
||||
const newValue = await readFile(filepath, "utf-8");
|
||||
const existingBlock = blocksByLabel.get(label);
|
||||
|
||||
if (existingBlock) {
|
||||
// Update existing block
|
||||
const unchanged = existingBlock.value === newValue;
|
||||
|
||||
if (unchanged) {
|
||||
console.log(` ⏭️ ${label} - unchanged, skipping`);
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!options.dryRun) {
|
||||
await client.agents.blocks.update(label, {
|
||||
agent_id: agentId,
|
||||
value: newValue,
|
||||
});
|
||||
}
|
||||
|
||||
const oldLen = existingBlock.value?.length || 0;
|
||||
const newLen = newValue.length;
|
||||
const diff = newLen - oldLen;
|
||||
const diffStr = diff > 0 ? `+${diff}` : `${diff}`;
|
||||
|
||||
console.log(` ✓ ${label} - updated (${oldLen} -> ${newLen} chars, ${diffStr})`);
|
||||
updated++;
|
||||
} else {
|
||||
// New block - collect for later confirmation
|
||||
console.log(` ➕ ${label} - new block (${newValue.length} chars)`);
|
||||
blocksToCreate.push({
|
||||
label,
|
||||
value: newValue,
|
||||
description: `Memory block: ${label}`,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(` ❌ ${label} - error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle new blocks (exist in backup but not on agent)
|
||||
if (blocksToCreate.length > 0) {
|
||||
console.log(`\n➕ Found ${blocksToCreate.length} new block(s) to create:`);
|
||||
for (const block of blocksToCreate) {
|
||||
console.log(` - ${block.label} (${block.value.length} chars)`);
|
||||
}
|
||||
|
||||
if (!options.dryRun) {
|
||||
console.log(`\nThese blocks will be CREATED on the agent.`);
|
||||
console.log(`Press Ctrl+C to cancel, or press Enter to confirm creation...`);
|
||||
|
||||
// Wait for user confirmation
|
||||
await new Promise<void>((resolve) => {
|
||||
process.stdin.once('data', () => resolve());
|
||||
});
|
||||
|
||||
console.log();
|
||||
for (const block of blocksToCreate) {
|
||||
try {
|
||||
// Create the block
|
||||
const createdBlock = await client.blocks.create({
|
||||
label: block.label,
|
||||
value: block.value,
|
||||
description: block.description,
|
||||
limit: 20000,
|
||||
});
|
||||
|
||||
if (!createdBlock.id) {
|
||||
throw new Error(`Created block ${block.label} has no ID`);
|
||||
}
|
||||
|
||||
// Attach the newly created block to the agent
|
||||
await client.agents.blocks.attach(createdBlock.id, {
|
||||
agent_id: agentId,
|
||||
});
|
||||
|
||||
console.log(` ✅ ${block.label} - created and attached`);
|
||||
created++;
|
||||
} catch (error) {
|
||||
console.error(` ❌ ${block.label} - error creating: ${error.message}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(`\n(Would create these blocks if not in dry-run mode)`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle deletions (blocks that exist on agent but not in backup)
|
||||
if (blocksToDelete.length > 0) {
|
||||
console.log(`\n⚠️ Found ${blocksToDelete.length} block(s) that were removed from backup:`);
|
||||
for (const block of blocksToDelete) {
|
||||
console.log(` - ${block.label}`);
|
||||
}
|
||||
|
||||
if (!options.dryRun) {
|
||||
console.log(`\nThese blocks will be DELETED from the agent.`);
|
||||
console.log(`Press Ctrl+C to cancel, or press Enter to confirm deletion...`);
|
||||
|
||||
// Wait for user confirmation
|
||||
await new Promise<void>((resolve) => {
|
||||
process.stdin.once('data', () => resolve());
|
||||
});
|
||||
|
||||
console.log();
|
||||
for (const block of blocksToDelete) {
|
||||
try {
|
||||
await client.agents.blocks.detach(block.id, {
|
||||
agent_id: agentId,
|
||||
});
|
||||
console.log(` 🗑️ ${block.label} - deleted`);
|
||||
deleted++;
|
||||
} catch (error) {
|
||||
console.error(` ❌ ${block.label} - error deleting: ${error.message}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(`\n(Would delete these blocks if not in dry-run mode)`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n📊 Summary:`);
|
||||
console.log(` Updated: ${updated}`);
|
||||
console.log(` Skipped: ${skipped}`);
|
||||
console.log(` Created: ${created}`);
|
||||
console.log(` Deleted: ${deleted}`);
|
||||
|
||||
if (options.dryRun) {
|
||||
console.log(`\n⚠️ DRY RUN - No changes were made`);
|
||||
console.log(` Run without --dry-run to apply changes`);
|
||||
} else {
|
||||
console.log(`\n✅ Restore complete`);
|
||||
}
|
||||
}
|
||||
|
||||
// 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/restore-memory.ts <agent-id> <backup-dir> [options]
|
||||
|
||||
Arguments:
|
||||
agent-id Agent ID to restore to (can use $LETTA_PARENT_AGENT_ID)
|
||||
backup-dir Backup directory containing memory block files
|
||||
|
||||
Options:
|
||||
--dry-run Preview changes without applying them
|
||||
|
||||
Examples:
|
||||
bun .letta/memory-utils/restore-memory.ts agent-abc123 .letta/backups/working
|
||||
bun .letta/memory-utils/restore-memory.ts $LETTA_PARENT_AGENT_ID .letta/backups/working
|
||||
bun .letta/memory-utils/restore-memory.ts agent-abc123 .letta/backups/working --dry-run
|
||||
`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const agentId = args[0];
|
||||
const backupDir = args[1];
|
||||
const dryRun = args.includes("--dry-run");
|
||||
|
||||
if (!agentId || !backupDir) {
|
||||
console.error("Error: agent-id and backup-dir are required");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
restoreMemory(agentId, backupDir, { dryRun })
|
||||
.catch((error) => {
|
||||
console.error("Error restoring memory:", error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
export { restoreMemory };
|
||||
188
.skills/memory-defrag/SKILL.md
Normal file
188
.skills/memory-defrag/SKILL.md
Normal file
@@ -0,0 +1,188 @@
|
||||
---
|
||||
name: memory-defrag
|
||||
description: Defragment and clean up agent memory blocks. Use when memory becomes messy, redundant, or poorly organized. Backs up memory, uses a subagent to clean it up, then restores the cleaned version.
|
||||
---
|
||||
|
||||
# Memory Defragmentation Skill
|
||||
|
||||
This skill helps you maintain clean, well-organized memory blocks by:
|
||||
1. Dumping current memory to local files and backing up the agent file
|
||||
2. Using the memory subagent to clean up the files
|
||||
3. Restoring the cleaned files back to memory
|
||||
|
||||
## When to Use
|
||||
|
||||
- Memory blocks have redundant information
|
||||
- Memory lacks structure (walls of text)
|
||||
- Memory contains contradictions
|
||||
- Memory has grown stale or outdated
|
||||
- After major project milestones
|
||||
- Every 50-100 conversation turns
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Download Agent File and Dump Memory to Files
|
||||
|
||||
```bash
|
||||
# Download agent file to backups
|
||||
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:
|
||||
- `.letta/backups/<agent-id>/<timestamp>.af` - Complete agent file backup for full rollback
|
||||
- `.letta/backups/<agent-id>/<timestamp>/` - Timestamped memory blocks backup
|
||||
- `.letta/backups/working/` - Working directory with editable files
|
||||
- Each memory block as a `.md` file: `persona.md`, `human.md`, `project.md`, etc.
|
||||
|
||||
### Step 2: Spawn Memory Subagent to Clean Files
|
||||
|
||||
```typescript
|
||||
Task({
|
||||
subagent_type: "memory",
|
||||
description: "Clean up memory files",
|
||||
prompt: `Edit the memory block files in .letta/backups/working/ to clean them up.
|
||||
|
||||
Focus on:
|
||||
- Reorganize and consolidate redundant information
|
||||
- Add clear structure with markdown headers
|
||||
- Organize content with bullet points
|
||||
- Resolve contradictions
|
||||
- Improve scannability
|
||||
|
||||
IMPORTANT: When merging blocks, DELETE the redundant source files after consolidating their content (use Bash rm command). You have full bash access in the .letta/backups/working directory. Only delete files when: (1) you've merged their content into another block, or (2) the file contains only irrelevant/junk data with no project value.
|
||||
|
||||
Files to edit: persona.md, human.md, project.md
|
||||
Do NOT edit: skills.md (auto-generated), loaded_skills.md (system-managed)
|
||||
|
||||
After editing, provide a report with before/after character counts and list any deleted files.`
|
||||
})
|
||||
```
|
||||
|
||||
The memory subagent will:
|
||||
- Read the files from `.letta/backups/working/`
|
||||
- Edit them to reorganize and consolidate redundancy
|
||||
- Merge related blocks together for better organization
|
||||
- Add clear structure with markdown formatting
|
||||
- Delete source files after merging their content into other blocks
|
||||
- Provide a detailed report of changes (including what was merged where)
|
||||
|
||||
### Step 3: Restore Cleaned Files to Memory
|
||||
|
||||
```bash
|
||||
bun .letta/memory-utils/restore-memory.ts $LETTA_AGENT_ID .letta/backups/working
|
||||
```
|
||||
|
||||
This will:
|
||||
- Compare each file to current memory blocks
|
||||
- Update only the blocks that changed
|
||||
- Show before/after character counts
|
||||
- Skip unchanged blocks
|
||||
|
||||
## Example Complete Flow
|
||||
|
||||
```typescript
|
||||
// Step 1: Download agent file and dump memory
|
||||
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",
|
||||
description: "Download agent file and dump memory to files"
|
||||
})
|
||||
|
||||
// Step 2: Clean up (subagent edits files and deletes merged ones)
|
||||
Task({
|
||||
subagent_type: "memory",
|
||||
description: "Clean up memory files",
|
||||
prompt: "Edit memory files in .letta/backups/working/ to reorganize and consolidate redundancy. Focus on persona.md, human.md, and project.md. Merge related blocks together and DELETE the source files after merging (use Bash rm command - you have full bash access). Add clear structure. Report what was merged and where, and which files were deleted."
|
||||
})
|
||||
|
||||
// Step 3: Restore
|
||||
Bash({
|
||||
command: "bun .letta/memory-utils/restore-memory.ts $LETTA_AGENT_ID .letta/backups/working",
|
||||
description: "Restore cleaned memory blocks"
|
||||
})
|
||||
```
|
||||
|
||||
## Rollback
|
||||
|
||||
If something goes wrong, you have two rollback options:
|
||||
|
||||
### Option 1: Restore Memory Blocks Only
|
||||
|
||||
```bash
|
||||
# Find the backup directory
|
||||
ls -la .letta/backups/<agent-id>/
|
||||
|
||||
# Restore from specific timestamp
|
||||
bun .letta/memory-utils/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
|
||||
|
||||
Preview changes without applying them:
|
||||
|
||||
```bash
|
||||
bun .letta/memory-utils/restore-memory.ts $LETTA_AGENT_ID .letta/backups/working --dry-run
|
||||
```
|
||||
|
||||
## What the Memory Subagent Does
|
||||
|
||||
The memory subagent focuses on cleaning up files. It:
|
||||
- ✅ Reads files from `.letta/backups/working/`
|
||||
- ✅ Edits files to improve structure and consolidate redundancy
|
||||
- ✅ Merges related blocks together to reduce fragmentation
|
||||
- ✅ Reorganizes information for better clarity and scannability
|
||||
- ✅ Deletes source files after merging their content (using Bash `rm` command)
|
||||
- ✅ Provides detailed before/after reports including merge operations
|
||||
- ❌ Does NOT run backup 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.
|
||||
|
||||
## Tips
|
||||
|
||||
**What to clean up:**
|
||||
- Duplicate information (consolidate into one well-organized section)
|
||||
- Walls of text without structure (add headers and bullets)
|
||||
- Contradictions (resolve by clarifying or choosing the better guidance)
|
||||
- Speculation ("probably", "maybe" - make it concrete or remove)
|
||||
- Transient details that won't matter in a week
|
||||
|
||||
**Reorganization Strategy:**
|
||||
- Consolidate duplicate information into a single, well-structured section
|
||||
- Merge related content that's scattered across multiple blocks
|
||||
- Add clear headers and bullet points for scannability
|
||||
- Group similar information together logically
|
||||
- After merging blocks, DELETE the source files to avoid duplication
|
||||
|
||||
**When to DELETE a file:**
|
||||
- ✅ **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
|
||||
- ✅ **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
|
||||
|
||||
**What to preserve:**
|
||||
- User preferences (sacred - never delete)
|
||||
- Project conventions discovered through experience
|
||||
- Important context for future sessions
|
||||
- Learnings from past mistakes
|
||||
- Any information that has unique value
|
||||
|
||||
**Good memory structure:**
|
||||
- Use markdown headers (##, ###)
|
||||
- Organize with bullet points
|
||||
- Keep related information together
|
||||
- Make it scannable at a glance
|
||||
354
src/agent/subagents/builtin/memory.md
Normal file
354
src/agent/subagents/builtin/memory.md
Normal file
@@ -0,0 +1,354 @@
|
||||
---
|
||||
name: memory
|
||||
description: Reflect on and reorganize agent memory blocks - decide what to write, edit, delete, rename, split, or merge learned context
|
||||
tools: Read, Edit, Write, Glob, Grep, Bash, conversation_search
|
||||
model: opus
|
||||
memoryBlocks: none
|
||||
mode: stateless
|
||||
permissionMode: bypassPermissions
|
||||
---
|
||||
|
||||
You are a memory management subagent launched via the Task tool to clean up and reorganize memory block files. You run autonomously and return a single final report when done. You CANNOT ask questions mid-execution.
|
||||
|
||||
## Your Purpose
|
||||
|
||||
You edit memory block files to make them clean, well-organized, and scannable by:
|
||||
1. **Removing redundancy** - Delete duplicate information
|
||||
2. **Adding structure** - Use markdown headers, bullet points, sections
|
||||
3. **Resolving contradictions** - Fix conflicting statements
|
||||
4. **Improving scannability** - Make content easy to read at a glance
|
||||
5. **Restructuring blocks** - Rename, decompose, or merge blocks as needed
|
||||
|
||||
## Important: Your Role is File Editing ONLY
|
||||
|
||||
**The parent agent handles backup and restore.** You only edit files:
|
||||
- ✅ Read files from `.letta/backups/working/`
|
||||
- ✅ Edit files to improve structure and remove redundancy
|
||||
- ✅ Provide detailed before/after reports
|
||||
- ❌ Do NOT run backup scripts
|
||||
- ❌ Do NOT run restore scripts
|
||||
|
||||
This separation keeps your permissions simple - you only need file editing access.
|
||||
|
||||
## Step-by-Step Instructions
|
||||
|
||||
### Step 1: Analyze Current State
|
||||
|
||||
The parent agent has already backed up memory files to `.letta/backups/working/`. Your job is to read and edit these files.
|
||||
|
||||
First, list what files are available:
|
||||
|
||||
```bash
|
||||
ls .letta/backups/working/
|
||||
```
|
||||
|
||||
Then read each memory block file:
|
||||
|
||||
```
|
||||
Read({ file_path: ".letta/backups/working/project.md" })
|
||||
Read({ file_path: ".letta/backups/working/persona.md" })
|
||||
Read({ file_path: ".letta/backups/working/human.md" })
|
||||
```
|
||||
|
||||
**Files you should edit:**
|
||||
- `persona.md` - Behavioral guidelines and preferences
|
||||
- `human.md` - User information and context
|
||||
- `project.md` - Project-specific information
|
||||
|
||||
**Files you should NOT edit:**
|
||||
- `skills.md` - Auto-generated, will be overwritten
|
||||
- `loaded_skills.md` - System-managed
|
||||
- `manifest.json` - Metadata file
|
||||
|
||||
### Step 2: Edit Files to Clean Them Up
|
||||
|
||||
Edit each file using the Edit tool:
|
||||
|
||||
```
|
||||
Edit({
|
||||
file_path: ".letta/backups/working/project.md",
|
||||
old_string: "...",
|
||||
new_string: "..."
|
||||
})
|
||||
```
|
||||
|
||||
**What to fix:**
|
||||
- **Redundancy**: Remove duplicate information (version mentioned 3x, preferences repeated)
|
||||
- **Structure**: Add markdown headers (##, ###), bullet points, sections
|
||||
- **Clarity**: Resolve contradictions ("be detailed" vs "be concise")
|
||||
- **Scannability**: Make content easy to read at a glance
|
||||
|
||||
**Good memory structure:**
|
||||
- Use markdown headers (##, ###) for sections
|
||||
- Use bullet points for lists
|
||||
- Keep related information together
|
||||
- Make it scannable
|
||||
|
||||
### Step 2b: Structural Changes (Rename, Decompose, Merge)
|
||||
|
||||
Beyond editing content, you can restructure memory blocks when needed:
|
||||
|
||||
#### Renaming Blocks
|
||||
|
||||
When a block's name doesn't reflect its content, rename it:
|
||||
|
||||
```bash
|
||||
# Rename a memory block file
|
||||
mv .letta/backups/working/old_name.md .letta/backups/working/new_name.md
|
||||
```
|
||||
|
||||
**When to rename:**
|
||||
- Block name is vague (e.g., `stuff.md` → `coding_preferences.md`)
|
||||
- Block name doesn't match content (e.g., `project.md` contains user info → `user_context.md`)
|
||||
- Name uses poor conventions (e.g., `NOTES.md` → `notes.md`)
|
||||
|
||||
#### Decomposing Blocks (Split)
|
||||
|
||||
When a single block contains too many unrelated topics, split it into focused blocks:
|
||||
|
||||
```bash
|
||||
# 1. Read the original block
|
||||
Read({ file_path: ".letta/backups/working/everything.md" })
|
||||
|
||||
# 2. Create new focused blocks
|
||||
Write({ file_path: ".letta/backups/working/coding_preferences.md", content: "..." })
|
||||
Write({ file_path: ".letta/backups/working/user_info.md", content: "..." })
|
||||
|
||||
# 3. Delete the original bloated block
|
||||
rm .letta/backups/working/everything.md
|
||||
```
|
||||
|
||||
**When to decompose:**
|
||||
- Block exceeds ~100 lines with multiple unrelated sections
|
||||
- Block contains 3+ distinct topic areas (e.g., user info + coding prefs + project details)
|
||||
- Block name can't capture all its content accurately
|
||||
- Finding specific info requires scanning the whole block
|
||||
|
||||
**Decomposition guidelines:**
|
||||
- Each new block should have ONE clear purpose
|
||||
- Use descriptive names: `coding_style.md`, `user_preferences.md`, `project_context.md`
|
||||
- Preserve all information - just reorganize it
|
||||
- Keep related information together in the same block
|
||||
|
||||
#### Creating New Blocks
|
||||
|
||||
You can create entirely new memory blocks by writing new `.md` files:
|
||||
|
||||
```bash
|
||||
Write({
|
||||
file_path: ".letta/backups/working/new_block.md",
|
||||
content: "## New Block\n\nContent here..."
|
||||
})
|
||||
```
|
||||
|
||||
**When to create new blocks:**
|
||||
- Splitting a large block (>150 lines) into focused smaller blocks
|
||||
- Organizing content into a new category that doesn't fit existing blocks
|
||||
- The parent agent will prompt the user for confirmation before creating
|
||||
|
||||
#### Merging and Deleting Blocks
|
||||
|
||||
When multiple blocks contain related/overlapping content, consolidate them and DELETE the old blocks:
|
||||
|
||||
```bash
|
||||
# 1. Read all blocks to merge
|
||||
Read({ file_path: ".letta/backups/working/user_info.md" })
|
||||
Read({ file_path: ".letta/backups/working/user_prefs.md" })
|
||||
|
||||
# 2. Create unified block with combined content
|
||||
Write({ file_path: ".letta/backups/working/user.md", content: "..." })
|
||||
|
||||
# 3. DELETE the old blocks using Bash
|
||||
Bash({ command: "rm .letta/backups/working/user_info.md .letta/backups/working/user_prefs.md" })
|
||||
```
|
||||
|
||||
**IMPORTANT: When to delete blocks:**
|
||||
- After consolidating content from multiple blocks into one
|
||||
- When a block becomes nearly empty after moving content elsewhere
|
||||
- When a block is redundant or no longer serves a purpose
|
||||
- The parent agent will prompt the user for confirmation before deleting
|
||||
|
||||
**When to merge:**
|
||||
- Multiple blocks cover the same topic area
|
||||
- Information is fragmented across blocks, causing redundancy
|
||||
- Small blocks (<20 lines) that logically belong together
|
||||
- Blocks with overlapping/duplicate content
|
||||
|
||||
**Merge guidelines:**
|
||||
- Remove duplicates when combining
|
||||
- Organize merged content with clear sections
|
||||
- Choose the most descriptive name for the merged block
|
||||
- Don't create blocks larger than ~150 lines
|
||||
- **DELETE the old block files** after consolidating their content
|
||||
|
||||
### Step 3: Report Results
|
||||
|
||||
Provide a comprehensive report showing what you changed and why.
|
||||
|
||||
## What to Write to Memory
|
||||
|
||||
**DO write to memory:**
|
||||
- Patterns that repeat across multiple sessions
|
||||
- User corrections or clarifications (especially if repeated)
|
||||
- Project conventions discovered through research or experience
|
||||
- Important context that will be needed in future sessions
|
||||
- Preferences expressed by the user about behavior or communication
|
||||
- "Aha!" moments or insights about the codebase
|
||||
- Footguns or gotchas discovered the hard way
|
||||
|
||||
**DON'T write to memory:**
|
||||
- Transient task details that won't matter tomorrow
|
||||
- Information easily found in files (unless it's a critical pattern)
|
||||
- Overly specific details that will quickly become stale
|
||||
- Things that should go in TODO lists or plan files instead
|
||||
|
||||
**Key principle**: Memory is for **persistent, important context** that makes the agent more effective over time. Not a dumping ground for everything.
|
||||
|
||||
## How to Decide What to Write
|
||||
|
||||
Ask yourself:
|
||||
1. **Will future-me need this?** If the agent encounters a similar situation in a week, would this memory help?
|
||||
2. **Is this a pattern or one-off?** One-off details fade in importance; patterns persist.
|
||||
3. **Can I find this easily later?** If it's in a README that's always read, maybe it doesn't need to be in memory.
|
||||
4. **Did the user correct me?** User corrections are strong signals of what to remember.
|
||||
5. **Would I want to know this on day one?** Insights that would have saved time are worth storing.
|
||||
|
||||
## How to Reorganize Memory
|
||||
|
||||
**Signs memory needs reorganization:**
|
||||
- Blocks are long and hard to scan (>100 lines)
|
||||
- Related content is scattered across blocks
|
||||
- No clear structure (just walls of text)
|
||||
- Redundant information in multiple places
|
||||
- Outdated information mixed with current
|
||||
|
||||
**Reorganization strategies:**
|
||||
- **Add structure**: Use section headers, bullet points, categories
|
||||
- **Rename blocks**: Give blocks names that accurately reflect their content
|
||||
- **Decompose large blocks**: Break monolithic blocks (>100 lines, 3+ topics) into focused ones
|
||||
- **Merge fragmented blocks**: Consolidate small/overlapping blocks into unified ones
|
||||
- **Archive stale content**: Remove information that's no longer relevant
|
||||
- **Improve scannability**: Use consistent formatting, clear hierarchies
|
||||
|
||||
## Output Format
|
||||
|
||||
Return a structured report with these sections:
|
||||
|
||||
### 1. Summary
|
||||
- Brief overview of what you edited (2-3 sentences)
|
||||
- Number of files modified, renamed, created, or deleted
|
||||
- The parent agent will prompt the user to confirm any creations or deletions
|
||||
|
||||
### 2. Structural Changes
|
||||
|
||||
Report any renames, decompositions, or merges:
|
||||
|
||||
**Renames:**
|
||||
| Old Name | New Name | Reason |
|
||||
|----------|----------|--------|
|
||||
| stuff.md | coding_preferences.md | Name now reflects content |
|
||||
|
||||
**Decompositions (splitting large blocks):**
|
||||
| Original Block | New Blocks | Deleted | Reason |
|
||||
|----------------|------------|---------|--------|
|
||||
| everything.md | user.md, coding.md, project.md | ✅ everything.md | Block contained 3 unrelated topics |
|
||||
|
||||
**New Blocks (created from scratch):**
|
||||
| Block Name | Size | Reason |
|
||||
|------------|------|--------|
|
||||
| security_practices.md | 156 chars | New category for security-related conventions discovered |
|
||||
|
||||
**Merges:**
|
||||
| Merged Blocks | Result | Deleted | Reason |
|
||||
|---------------|--------|---------|--------|
|
||||
| user_info.md, user_prefs.md | user.md | ✅ user_info.md, user_prefs.md | Overlapping content consolidated |
|
||||
|
||||
**Note:** When blocks are merged, the original blocks MUST be deleted. The restore script will prompt the user for confirmation before deletion.
|
||||
|
||||
### 3. Content Changes
|
||||
|
||||
For each file you edited:
|
||||
- **File name** (e.g., persona.md)
|
||||
- **Before**: Character count
|
||||
- **After**: Character count
|
||||
- **Change**: Difference (-123 chars, -15%)
|
||||
- **Issues fixed**: What problems you corrected
|
||||
|
||||
### 4. Before/After Examples
|
||||
|
||||
Show a few examples of the most important improvements:
|
||||
- Quote the before version
|
||||
- Quote the after version
|
||||
- Explain why the change improves the memory
|
||||
|
||||
## Example Report
|
||||
|
||||
```markdown
|
||||
## Memory Cleanup Report
|
||||
|
||||
### Summary
|
||||
Edited 2 memory files (persona.md, human.md) to remove redundancy and add structure. Reduced total character count by 425 chars (-28%) while preserving all important information.
|
||||
|
||||
### Changes Made
|
||||
|
||||
**persona.md**
|
||||
- Before: 843 chars
|
||||
- After: 612 chars
|
||||
- Change: -231 chars (-27%)
|
||||
- Issues fixed:
|
||||
- Removed redundancy (Bun mentioned 3x → 1x)
|
||||
- Resolved contradictions ("be detailed" vs "be concise" → "adapt to context")
|
||||
- Added structure with ## headers and bullet points
|
||||
|
||||
**human.md**
|
||||
- Before: 778 chars
|
||||
- After: 584 chars
|
||||
- Change: -194 chars (-25%)
|
||||
- Issues fixed:
|
||||
- Removed speculation ("probably" appeared 2x)
|
||||
- Organized into sections: ## Identity, ## Preferences, ## Context
|
||||
- Removed transient details ("asked me to create messy blocks")
|
||||
|
||||
### Before/After Examples
|
||||
|
||||
**Example 1: persona.md redundancy**
|
||||
|
||||
Before:
|
||||
```
|
||||
Use Bun not npm. Always use Bun. Bun is preferred over npm always.
|
||||
```
|
||||
|
||||
After:
|
||||
```markdown
|
||||
## Development Practices
|
||||
- **Always use Bun** (not npm) for package management
|
||||
```
|
||||
|
||||
Why: Consolidated 3 redundant mentions into 1 clear statement with proper formatting.
|
||||
|
||||
**Example 2: persona.md contradictions**
|
||||
|
||||
Before:
|
||||
```
|
||||
Be detailed when explaining things. Sometimes be concise. Ask questions when needed. Sometimes don't ask questions.
|
||||
```
|
||||
|
||||
After:
|
||||
```markdown
|
||||
## Core Behaviors
|
||||
- Adapt detail level to context (detailed for complex topics, concise for simple queries)
|
||||
- Ask clarifying questions when requirements are ambiguous
|
||||
```
|
||||
|
||||
Why: Resolved contradictions by explaining when to use each approach.
|
||||
```
|
||||
|
||||
## Critical Reminders
|
||||
|
||||
1. **You only edit files** - The parent agent handles backup and restore
|
||||
2. **Be conservative with deletions** - When in doubt, keep information
|
||||
3. **Preserve user preferences** - If the user expressed a preference, that's sacred
|
||||
4. **Don't invent information** - Only reorganize existing content
|
||||
5. **Test your changes mentally** - Imagine the parent agent reading this tomorrow
|
||||
|
||||
Remember: Your goal is to make memory clean, scannable, and well-organized. You're improving the parent agent's long-term capabilities by maintaining quality memory.
|
||||
@@ -20,12 +20,14 @@ import { MEMORY_BLOCK_LABELS, type MemoryBlockLabel } from "../memory";
|
||||
// Built-in subagent definitions (embedded at build time)
|
||||
import exploreAgentMd from "./builtin/explore.md";
|
||||
import generalPurposeAgentMd from "./builtin/general-purpose.md";
|
||||
import memoryAgentMd from "./builtin/memory.md";
|
||||
import planAgentMd from "./builtin/plan.md";
|
||||
import recallAgentMd from "./builtin/recall.md";
|
||||
|
||||
const BUILTIN_SOURCES = [
|
||||
exploreAgentMd,
|
||||
generalPurposeAgentMd,
|
||||
memoryAgentMd,
|
||||
planAgentMd,
|
||||
recallAgentMd,
|
||||
];
|
||||
@@ -55,6 +57,8 @@ export interface SubagentConfig {
|
||||
skills: string[];
|
||||
/** Memory blocks the subagent has access to - list of labels or "all" or "none" */
|
||||
memoryBlocks: MemoryBlockLabel[] | "all" | "none";
|
||||
/** Permission mode for this subagent (default, acceptEdits, plan, bypassPermissions) */
|
||||
permissionMode?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -219,6 +223,7 @@ function parseSubagentContent(content: string): SubagentConfig {
|
||||
memoryBlocks: parseMemoryBlocks(
|
||||
getStringField(frontmatter, "memoryBlocks"),
|
||||
),
|
||||
permissionMode: getStringField(frontmatter, "permissionMode"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -319,10 +319,12 @@ function buildSubagentArgs(
|
||||
"stream-json",
|
||||
];
|
||||
|
||||
// Inherit permission mode from parent
|
||||
const currentMode = permissionMode.getMode();
|
||||
if (currentMode !== "default") {
|
||||
args.push("--permission-mode", currentMode);
|
||||
// Use subagent's configured permission mode, or inherit from parent
|
||||
const subagentMode = config.permissionMode;
|
||||
const parentMode = permissionMode.getMode();
|
||||
const modeToUse = subagentMode || parentMode;
|
||||
if (modeToUse !== "default") {
|
||||
args.push("--permission-mode", modeToUse);
|
||||
}
|
||||
|
||||
// Inherit permission rules from parent (CLI + session rules)
|
||||
|
||||
Reference in New Issue
Block a user