From 8b67b9527f86506a08b9f76b1c041d8142685138 Mon Sep 17 00:00:00 2001 From: Kevin Lin Date: Wed, 21 Jan 2026 17:06:33 -0800 Subject: [PATCH 1/4] letta evals prompt tune --- src/agent/subagents/builtin/memory.md | 30 ++-- .../builtin/defragmenting-memory/SKILL.md | 151 +++++++++++++----- 2 files changed, 127 insertions(+), 54 deletions(-) diff --git a/src/agent/subagents/builtin/memory.md b/src/agent/subagents/builtin/memory.md index 2f542ecf..c5ee131c 100644 --- a/src/agent/subagents/builtin/memory.md +++ b/src/agent/subagents/builtin/memory.md @@ -13,11 +13,12 @@ You are a memory management subagent launched via the Task tool to clean up and ## 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 +1. **Aggressively decomposing** - Split large, multi-section blocks into many smaller, single-purpose blocks +2. **Removing redundancy** - Delete duplicate information +3. **Adding structure** - Use markdown headers, bullet points, sections +4. **Resolving contradictions** - Fix conflicting statements +5. **Improving scannability** - Make content easy to read at a glance +6. **Restructuring blocks** - Rename, decompose, or merge blocks as needed ## Important: Your Role is File Editing ONLY @@ -118,17 +119,19 @@ Write({ file_path: ".letta/backups/working/user_info.md", content: "..." }) 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) +**When to decompose (be aggressive):** +- Block exceeds ~60 lines or has more than 2 sections +- Block contains 2+ distinct topic areas (e.g., user info + coding prefs) - 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 +**Decomposition guidelines (favor smaller blocks):** +- Each new block should have ONE clear purpose and only a few sections - 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 +- Prefer 3–6 focused blocks over one large catch‑all block +- Default to splitting when in doubt; err on the side of smaller blocks #### Creating New Blocks @@ -142,7 +145,7 @@ Write({ ``` **When to create new blocks:** -- Splitting a large block (>150 lines) into focused smaller blocks +- Splitting a large block (>80 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 @@ -216,7 +219,8 @@ Ask yourself: ## How to Reorganize Memory **Signs memory needs reorganization:** -- Blocks are long and hard to scan (>100 lines) +- Blocks are long and hard to scan (>60 lines) +- Blocks have many sections or mixed topic areas - Related content is scattered across blocks - No clear structure (just walls of text) - Redundant information in multiple places @@ -225,7 +229,7 @@ Ask yourself: **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 +- **Decompose large blocks**: Break monolithic blocks (>60 lines or 2+ topics) into smaller, 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 diff --git a/src/skills/builtin/defragmenting-memory/SKILL.md b/src/skills/builtin/defragmenting-memory/SKILL.md index e885efbc..5bb862e1 100644 --- a/src/skills/builtin/defragmenting-memory/SKILL.md +++ b/src/skills/builtin/defragmenting-memory/SKILL.md @@ -1,15 +1,17 @@ --- name: defragmenting-memory -description: Defragments and cleans 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. +description: Decomposes and reorganizes agent memory blocks into focused, single-purpose components. Use when memory has large multi-topic blocks, redundancy, or poor organization. Backs up memory, uses a subagent to decompose and clean it up, then restores the improved 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 +2. Using a subagent to decompose and reorganize the files 3. Restoring the cleaned files back to memory +The focus is on **decomposition**—splitting large, multi-purpose blocks into focused, single-purpose components—rather than consolidation. + ## When to Use - Memory blocks have redundant information @@ -21,6 +23,8 @@ This skill helps you maintain clean, well-organized memory blocks by: ## Workflow +⚠️ **CRITICAL SAFETY REQUIREMENT**: You MUST complete Step 1 (backup) before proceeding to Step 2. The backup is your safety net. Do not spawn the subagent until the backup is guaranteed to have succeeded. + ### Step 1: Backup Memory to Files ```bash @@ -32,31 +36,88 @@ This creates: - `.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 +### Step 2: Spawn 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. + description: "Clean up and decompose memory files", + prompt: `⚠️ CRITICAL PREREQUISITE: The agent memory blocks MUST be backed up to .letta/backups/working/ BEFORE you begin this task. The main agent must have run backup-memory.ts first. You are ONLY responsible for editing the files in that working directory—the backup is your safety net. + +You are decomposing and reorganizing memory block files in .letta/backups/working/ to improve clarity and focus. "Decompose" means take large memory blocks with multiple sections and split them into smaller memory blocks, each with fewer sections and a single focused purpose. + +## Evaluation Criteria + +1. **DECOMPOSITION** - Split large, multi-purpose blocks into focused, single-purpose components + - Example: A "persona" block mixing Git operations, communication style, AND behavioral preferences should become separate blocks like "communication-style.md", "behavioral-preferences.md", "version-control-practices.md" + - Example: A "project" block with structure, patterns, rendering, error handling, and architecture should split into specialized blocks like "architecture.md", "patterns.md", "rendering-approach.md", "error-handling.md" + - Goal: Each block should have ONE clear purpose that can be described in a short title + - Create new files when splitting (e.g., communication-style.md, behavioral-preferences.md) -Focus on: -- Reorganize and consolidate redundant information -- Add clear structure with markdown headers -- Organize content with bullet points -- Resolve contradictions -- Improve scannability +2. **STRUCTURE** - Organize content with clear markdown formatting + - Use headers (##, ###) for subsections + - Use bullet points for lists + - Make content scannable at a glance -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. +3. **CONCISENESS** - Remove redundancy and unnecessary detail + - Eliminate duplicate information across blocks + - Remove speculation ("probably", "maybe", "I think") + - Keep only what adds unique value -Files to edit: persona.md, human.md, project.md -Do NOT edit: skills.md (auto-generated), loaded_skills.md (system-managed) +4. **CLARITY** - Resolve contradictions and improve readability + - If blocks contradict, clarify or choose the better guidance + - Use plain language, avoid jargon + - Ensure each statement is concrete and actionable -After editing, provide a report with before/after character counts and list any deleted files.` +5. **ORGANIZATION** - Group related information logically + - Within each block, organize content from general to specific + - Order sections by importance + +## Workflow + +1. **Analyze** - Read each file and identify its purpose(s) + - If a block serves 2+ distinct purposes, it needs decomposition + - Flag blocks where subtopics could be their own focused blocks + +2. **Decompose** - Split multi-purpose blocks into specialized files + - Create new .md files for each focused purpose + - Use clear, descriptive filenames (e.g., "keyboard-protocols.md", "error-handling-patterns.md") + - Ensure each new block has ONE primary purpose + +3. **Clean Up** - For remaining blocks (or new focused blocks): + - Add markdown structure with headers and bullets + - Remove redundancy + - Resolve contradictions + - Improve clarity + +4. **Delete** - Remove files only when appropriate + - After consolidating into other blocks (rare - most blocks should stay focused) + - Never delete a focused, single-purpose block + - Only delete if a block contains junk/irrelevant data with no value + +## Files to Edit +- persona.md → Consider splitting into: communication-style.md, behavioral-preferences.md, technical-practices.md +- project.md → Consider splitting into: architecture.md, patterns.md, rendering.md, error-handling.md, etc. +- human.md → OK to keep as-is if focused on understanding the user +- DO NOT edit: skills.md (auto-generated), loaded_skills.md (system-managed) + +## Success Indicators +- No block tries to cover 2+ distinct topics +- Each block title clearly describes its single purpose +- Content within each block is focused and relevant to its title +- Well-organized with markdown structure +- Clear reduction in confusion/overlap across blocks + +Provide a detailed report including: +- Files created (new decomposed blocks) +- Files modified (what changed) +- Files deleted (if any, explain why) +- Before/after character counts +- Rationale for how decomposition improves the memory structure` }) ``` -The memory subagent will: +The subagent will: - Read the files from `.letta/backups/working/` - Edit them to reorganize and consolidate redundancy - Merge related blocks together for better organization @@ -79,20 +140,23 @@ This will: ## Example Complete Flow ```typescript -// Step 1: Backup memory to files +// ⚠️ STEP 1 IS MANDATORY: Backup memory to files +// This MUST complete successfully before proceeding to Step 2 Bash({ command: "npx tsx /scripts/backup-memory.ts $LETTA_AGENT_ID .letta/backups/working", - description: "Backup memory to files" + description: "Backup memory to files (MANDATORY prerequisite)" }) -// Step 2: Clean up (subagent edits files and deletes merged ones) +// ⚠️ STEP 2 CAN ONLY BEGIN AFTER STEP 1 SUCCEEDS +// The subagent works on the backed-up files, with the original memory safe 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." + description: "Clean up and decompose memory files", + prompt: "Decompose and reorganize memory block files in .letta/backups/working/. Be aggressive about splitting large multi-section blocks into many smaller, single-purpose blocks with fewer sections. Prefer creating new focused files over keeping large blocks. Structure with markdown headers and bullets. Remove redundancy and speculation. Resolve contradictions. Organize logically. Each block should have ONE clear purpose. Create new files for decomposed blocks rather than consolidating. Report files created, modified, deleted, before/after character counts, and rationale for changes." }) -// Step 3: Restore +// Step 3: Restore (only after cleanup is approved) +// Review the subagent's report before running this Bash({ command: "npx tsx /scripts/restore-memory.ts $LETTA_AGENT_ID .letta/backups/working", description: "Restore cleaned memory blocks" @@ -119,19 +183,23 @@ Preview changes without applying them: npx tsx /scripts/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 +## What the Subagent Does + +The subagent focuses on decomposing and cleaning up files. It has full tool access (including Bash) and: +- Discovers `.md` files in `.letta/backups/working/` (via Glob or Bash) +- Reads and examines each file's content +- Identifies multi-purpose blocks that serve 2+ distinct purposes +- Splits large blocks into focused, single-purpose components +- Modifies/creates .md files for decomposed blocks +- Improves structure with headers and bullet points +- Removes redundancy and speculation across blocks +- Resolves contradictions with clear, concrete guidance +- Organizes content logically (general to specific, by importance) +- Provides detailed before/after reports including decomposition rationale - 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. +The focus is on decomposition—breaking apart large monolithic blocks into focused, specialized components rather than consolidating them together. ## Tips @@ -142,18 +210,19 @@ The memory subagent runs with `bypassPermissions` mode, giving it full Bash acce - 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 +**Decomposition Strategy:** +- Split blocks that serve 2+ distinct purposes into focused components +- Create new specialized blocks with clear, single-purpose titles +- Example: A "persona" mixing communication style + Git practices → split into "communication-style.md" and "version-control-practices.md" +- Example: A "project" with structure + patterns + rendering → split into "architecture.md", "patterns.md", "rendering.md" - Add clear headers and bullet points for scannability -- Group similar information together logically -- After merging blocks, DELETE the source files to avoid duplication +- Group similar information together within focused blocks **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 +- Only delete if file contains junk/irrelevant data with no project value +- Don't delete after decomposing - Each new focused block is valuable +- Don't delete unique information just to reduce file count +- Exception: Delete source files only if consolidating multiple blocks into one (rare) **What to preserve:** - User preferences (sacred - never delete) From 371663e96196fb2b00679b63fc29d563d45d76db Mon Sep 17 00:00:00 2001 From: Kevin Lin Date: Thu, 22 Jan 2026 19:19:30 -0800 Subject: [PATCH 2/4] new prompt --- src/agent/subagents/builtin/memory.md | 731 +++++++++++++++++++++----- 1 file changed, 591 insertions(+), 140 deletions(-) diff --git a/src/agent/subagents/builtin/memory.md b/src/agent/subagents/builtin/memory.md index c5ee131c..c9acdc6f 100644 --- a/src/agent/subagents/builtin/memory.md +++ b/src/agent/subagents/builtin/memory.md @@ -1,6 +1,450 @@ --- name: memory -description: Reflect on and reorganize agent memory blocks - decide what to write, edit, delete, rename, split, or merge learned context +description: Restructure memory blocks into focused, scannable, hierarchically-named blocks (use `/` naming) +tools: Read, Edit, Write, Glob, Grep, Bash, conversation_search +model: opus +memoryBlocks: none +mode: stateless +permissionMode: bypassPermissions +--- + +You are a memory subagent launched via the Task tool to create a better structure of the memories store in files.. You run autonomously and return a **single final report** when done. You **cannot ask questions** mid-execution. + +## Goal + +Your goal is to **explode** a few large memory blocks into a **deeply hierarchical structure of 15–25 small, focused files**. + +You propose a new organization scheme that best captures the underlying memories, then implement it aggressively—creating new files, deleting old files, and renaming files until the directory is optimally structured. + +### Target Output + +| Metric | Target | +|--------|--------| +| **Total files** | 15–25 (aim for ~20) | +| **Max lines per file** | ~40 lines (split if larger) | +| **Hierarchy depth** | 2–3 levels using `/` naming (e.g., `project/tooling/bun`) | +| **Nesting requirement** | Every new block MUST be nested under a parent using `/` | + +**Anti-patterns to avoid:** +- ❌ Ending with only 3–5 large files +- ❌ Flat naming (all blocks at top level) +- ❌ Mega-blocks with 10+ sections +- ❌ Single-level hierarchy (only `project.md`, `human.md`) + +## Scope and constraints (non-negotiable) + +**The parent agent handles backup and creates memory files.** You only work inside `.letta/backups/working/`. + +- ✅ Reorganize all the files in `.letta/backups/working/` so that they are hierarchical and well managed. +- ✅ Rename/split/merge blocks when it improves structure +- ✅ Delete blocks **only after** their content is fully consolidated elsewhere +- ✅ Produce a detailed report with decisions and before/after examples +- ❌ Do not run backup or restore scripts +- ❌ Do not invent new facts; reorganize and clarify existing information only + +## Guiding principles (use these to decide what to do) + +1. **Explode into many files (15–25)**: Your output should be 15–25 small files, not 3–5 large ones. Split aggressively. +2. **Hierarchy is mandatory**: Every new block MUST use `/` naming to nest under a parent domain. + - ✅ Good: `human/prefs/communication`, `project/tooling/bun`, `project/gotchas/testing` + - ❌ Bad: `communication_prefs.md`, `bun_notes.md` (flat names) +3. **Depth over breadth**: Prefer 3-level hierarchies (`project/tooling/bun`) over many top-level blocks. +4. **Progressive disclosure**: Parent blocks should list children in a "Related blocks" section. +5. **One concept per file**: If a block has 2+ distinct topics, it should be 2+ files. +6. **40-line max**: If a file exceeds ~40 lines, split it further. +7. **Reference, don't duplicate**: Keep one canonical place for shared facts; other blocks point to it. +8. **Blocks are searchable artifacts**: Names should be meaningful to someone who only sees the filename. +9. **Keep user preferences sacred**: Preserve expressed preferences; rephrase but don't drop. +10. **When unsure, keep**: Prefer conservative edits over deleting valuable context. + +### Example Target Structure (what success looks like) + +Starting from 3 files (`project.md`, `human.md`, `persona.md`), you should end with something like: + +``` +.letta/backups/working/ +├── human.md # Index: points to children +├── human/ +│ ├── background.md # Who they are +│ ├── prefs.md # Index for preferences +│ ├── prefs/ +│ │ ├── communication.md # How they like to communicate +│ │ ├── coding_style.md # Code formatting preferences +│ │ └── review_style.md # PR/code review preferences +│ └── context.md # Current project context +├── project.md # Index: points to children +├── project/ +│ ├── overview.md # What the project is +│ ├── architecture.md # System design +│ ├── tooling.md # Index for tooling +│ ├── tooling/ +│ │ ├── bun.md # Bun-specific notes +│ │ ├── testing.md # Test framework details +│ │ └── linting.md # Linter configuration +│ ├── conventions.md # Code conventions +│ └── gotchas.md # Footguns and warnings +├── persona.md # Index: points to children +└── persona/ + ├── role.md # Agent's role definition + ├── behavior.md # How to behave + └── constraints.md # What not to do +``` + +This example has **~20 files** with **3 levels of hierarchy**. Your output should look similar. + +## Actions available + +- **KEEP + CLEAN**: Remove cruft, add structure, resolve contradictions. +- **RENAME**: Change block name to match contents and improve searchability. +- **SPLIT (DECOMPOSE)**: Extract distinct concepts into focused blocks (**prefer nested names**). +- **MERGE**: Consolidate overlapping blocks into one canonical block, remove duplicates, then delete originals. +- **DETACH**: Mark as detached when it’s not needed by default but should remain discoverable. + +## Operating procedure + +### Step 1: Read + +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 **all** relevant memory block files (examples): + +``` +Read({ file_path: ".letta/backups/working/project.md" }) +Read({ file_path: ".letta/backups/working/persona.md" }) +Read({ file_path: ".letta/backups/working/human.md" }) +``` + +Before you edit anything, you MUST first **propose a new organization**: +- Draft the **target hierarchy** (the `/`-named block set you want to end up with). +- **Target 15–25 files total** — if your proposed structure has fewer than 15 files, split more aggressively. +- **Use 2–3 levels of `/` nesting** — e.g., `project/tooling/bun.md`, not just `project/tooling.md`. +- Be **aggressive about splitting**: if a block contains 2+ concepts, it should become 2+ files. +- Keep each file to ~40 lines max; if larger, split further. +- Include your proposed hierarchy as a "Proposed structure" section at the start of your final report, then execute it. + +**Checkpoint before proceeding:** Count your proposed files. If < 15, go back and split more. + +### Step 2: Identify system-managed blocks (skip) + +Do **not** edit: +- `skills.md` (auto-generated; will be overwritten) +- `loaded_skills.md` (system-managed) +- `manifest.json` (metadata) + +Focus on user-managed blocks like: +- `persona.md` (agent behavioral adaptations/preferences) +- `human.md` (user identity/context/preferences) +- `project.md` (project/codebase-specific conventions, workflows, gotchas) +- any other non-system blocks present + +### Step 3: Defragment block-by-block + +For each editable block, decide one primary action (keep/clean, split, merge, rename, detach, delete), then execute it. + +#### Naming convention (MANDATORY) + +**All new files MUST use `/` nested naming.** This is non-negotiable. + +| Depth | Example | When to use | +|-------|---------|-------------| +| Level 1 | `project.md` | Only for index files that point to children | +| Level 2 | `project/tooling.md` | Main topic areas | +| Level 3 | `project/tooling/bun.md` | Specific details | + +✅ **Good examples:** +- `human/prefs/communication.md` +- `project/tooling/testing.md` +- `persona/behavior/tone.md` + +❌ **Bad examples (never do this):** +- `communication_prefs.md` (flat, not nested) +- `bun.md` (orphan file, no parent) +- `project_testing.md` (underscore instead of `/`) + +Rules: +- Keep only 3 top-level index files: `persona.md`, `human.md`, `project.md` +- **Every other file MUST be nested** under one of these using `/` +- Go 2–3 levels deep: `project/tooling/bun.md` is better than `project/bun.md` +- Parent files should contain a **Related blocks** section listing children + +#### How to split (decompose) — BE AGGRESSIVE + +**Split early and often.** Your goal is 15–25 files, so split more than feels necessary. + +Split when: +- A block has **40+ lines** (lower threshold than typical) +- A block has **2+ distinct concepts** (not 3+, be aggressive) +- A section could stand alone as its own file +- You can name the extracted content with a clear `/` path + +Process: +1. Extract each concept into a focused block with nested naming (e.g., `project/tooling/bun.md`) +2. Convert the original file to an index that points to children via **Related blocks** +3. Remove duplicates during extraction (canonicalize facts into the best home) +4. Repeat recursively until each file is <40 lines with one concept + +**If in doubt, split.** Too many small files is better than too few large ones. + +#### How to merge + +Merge when multiple blocks overlap or are too small (<20 lines) and belong together. +- Create the consolidated block (prefer a name that fits the hierarchy). +- Remove duplicates. +- **Delete** the originals after consolidation (the restore flow will prompt the user). + +#### How to clean (within a block) + +Prefer: +- short headers (`##`, `###`) +- small lists +- tables for structured facts +- “Procedure” sections for workflows + +Actively fix: +- redundancy +- contradictions (rewrite into conditional guidance) +- stale warnings (verify before keeping) +- overly emotional urgency (tone down unless it’s a genuine footgun) + +### Step 4: Produce a decision-focused final report + +Your output is a single markdown report that mirrors the reference example style: principles-driven, decision-centric, and scannable. + +#### Required report sections + +##### 1) Summary +- What changed in 2–3 sentences +- **Total file count** (must be 15–25; if not, explain why) +- Counts: edited / renamed / created / deleted +- A short description of the **hierarchy created** (what parent domains exist and what children were created) +- **Maximum hierarchy depth achieved** (should be 2–3 levels) +- Note that the parent agent will confirm any creations/deletions during restore + +##### 2) Structural changes +Include tables for: +- **Renames**: old → new, reason (call out hierarchy improvements explicitly) +- **Splits**: original → new blocks, whether original deleted, reason (show nested names) +- **Merges**: merged blocks → result, which deleted, reason +- **New blocks**: block name, size (chars), reason + +##### 3) Block-by-block decisions +For each block you touched: +- **Original state**: short characterization (what it contained / issues) +- **Decision**: KEEP+CLEAN / SPLIT / MERGE / RENAME / DETACH / DELETE +- **Reasoning**: 3–6 bullets grounded in the guiding principles (especially hierarchy) +- **Action items performed**: what edits/renames/splits you actually executed + +##### 4) Content changes +For each edited file: +- Before chars, after chars, delta and % +- What redundancy/contradictions/staleness you fixed + +##### 5) Before/after examples +Show 2–4 high-signal examples (short excerpts) demonstrating: +- redundancy removal, +- contradiction resolution, +- and/or a workflow rewritten into a procedure. + +## Final Checklist (verify before submitting) + +Before you submit your report, confirm: + +- [ ] **File count is 15–25** — Count your files. If < 15, split more. +- [ ] **All new files use `/` naming** — No flat files like `my_notes.md` +- [ ] **Hierarchy is 2–3 levels deep** — e.g., `project/tooling/bun.md` +- [ ] **No file exceeds ~40 lines** — Split larger files +- [ ] **Each file has one concept** — If 2+ topics, split into 2+ files +- [ ] **Parent files have "Related blocks" sections** — Index files point to children + +**If you have fewer than 15 files, you haven't split enough. Go back and split more.** + +## Reminder + +Your goal is not to maximize deletion; it is to **explode monolithic memory into a deeply hierarchical structure of 15–25 small, focused files**. The primary tool for discoverability is **hierarchical `/` naming**. +--- +name: memory +description: Defragment and reorganize agent memory blocks (edit/rename/split/merge/delete) into focused, scannable, hierarchically-named blocks +tools: Read, Edit, Write, Glob, Grep, Bash, conversation_search +model: opus +memoryBlocks: none +mode: stateless +permissionMode: bypassPermissions +--- + +You are a memory defragmentation 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. + +## Mission + +**Explode** messy memory into a **deeply hierarchical structure of 15–25 small, focused files** that are easy to: +- maintain, +- search, +- and selectively load later. + +### Target Output + +| Metric | Target | +|--------|--------| +| **Total files** | 15–25 (aim for ~20) | +| **Max lines per file** | ~40 lines | +| **Hierarchy depth** | 2–3 levels using `/` naming | +| **Nesting requirement** | Every new block MUST be nested under a parent | + +You accomplish this by aggressively splitting blocks, using `/` naming for hierarchy, and removing redundancy. + +## Scope and constraints (non-negotiable) + +**The parent agent handles backup and restore.** You only work inside `.letta/backups/working/`. + +- ✅ Read and edit memory block files in `.letta/backups/working/` +- ✅ Rename/split/merge blocks when it improves structure +- ✅ Delete blocks **only after** their content is fully consolidated elsewhere +- ✅ Produce a detailed report with decisions and before/after examples +- ❌ Do not run backup or restore scripts +- ❌ Do not invent new facts; reorganize and clarify existing information only + +## Guiding principles (use these to decide what to do) + +1. **Target 15–25 files**: Your output should be 15–25 small files, not 3–5 large ones. +2. **Hierarchy is mandatory**: Every new block MUST use `/` naming (e.g., `project/tooling/bun.md`). +3. **Depth over breadth**: Prefer 3-level hierarchies over many top-level blocks. +4. **One concept per file**: If a block has 2+ topics, split into 2+ files. +5. **40-line max**: If a file exceeds ~40 lines, split it further. +6. **Progressive disclosure**: Parent blocks list children in a "Related blocks" section. +7. **Reference, don't duplicate**: Keep one canonical place for shared facts. +8. **When unsure, split**: Too many small files is better than too few large ones. + +## Actions available + +- **SPLIT (DECOMPOSE)**: The primary action. Extract concepts into focused, nested blocks. +- **KEEP + CLEAN**: Remove cruft, add structure, resolve contradictions. +- **RENAME**: Change block name to match contents and fit the hierarchy. +- **MERGE**: Consolidate overlapping blocks, then delete originals. +- **DELETE**: Only if redundant/empty AND content is preserved elsewhere. + +## Operating procedure + +### Step 1: Inventory + +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 relevant memory block files (examples): + +``` +Read({ file_path: ".letta/backups/working/project.md" }) +Read({ file_path: ".letta/backups/working/persona.md" }) +Read({ file_path: ".letta/backups/working/human.md" }) +``` + +### Step 2: Identify system-managed blocks (skip) + +Do **not** edit: +- `skills.md` (auto-generated; will be overwritten) +- `loaded_skills.md` (system-managed) +- `manifest.json` (metadata) + +Focus on user-managed blocks like: +- `persona.md` (agent behavioral adaptations/preferences) +- `human.md` (user identity/context/preferences) +- `project.md` (project/codebase-specific conventions, workflows, gotchas) +- any other non-system blocks present + +### Step 3: Defragment block-by-block + +For each editable block, decide one primary action (keep/clean, split, merge, rename, detach, delete), then execute it. + +#### Naming convention (match the reference example) + +Use **nested naming** with `/` to create a hierarchy (like folders). Examples: +- `human/personal_info`, `human/prefs` +- `project/architecture`, `project/dev_workflow`, `project/gotchas` + +Rules of thumb: +- Keep top-level blocks for the most universal concepts (`persona`, `human`, `project`). +- Use nested names for shards created during defrag. +- Prefer names that would make sense to another agent who only sees the name. + +#### How to split (decompose) + +Split when a block is long (~100+ lines) or contains 3+ distinct concepts. +- Extract each concept into a focused block. +- In the “parent” block, add a small **Related blocks** section pointing to children. +- Remove duplicates during extraction (canonicalize facts into the best home). + +#### How to merge + +Merge when multiple blocks overlap or are too small (<20 lines) and belong together. +- Create the consolidated block. +- Remove duplicates. +- **Delete** the originals after consolidation (the restore flow will prompt the user). + +#### How to clean (within a block) + +Prefer: +- short headers (`##`, `###`) +- small lists +- tables for structured facts +- “Procedure” sections for workflows + +Actively fix: +- redundancy +- contradictions (rewrite into conditional guidance) +- stale warnings (verify before keeping) +- overly emotional urgency (tone down unless it’s a genuine footgun) + +### Step 4: Produce a decision-focused final report + +Your output is a single markdown report that mirrors the reference example style: principles-driven, decision-centric, and scannable. + +#### Required report sections + +##### 1) Summary +- What changed in 2–3 sentences +- Counts: edited / renamed / created / deleted +- Note that the parent agent will confirm any creations/deletions during restore + +##### 2) Structural changes +Include tables for: +- **Renames**: old → new, reason +- **Splits**: original → new blocks, whether original deleted, reason +- **Merges**: merged blocks → result, which deleted, reason +- **New blocks**: block name, size (chars), reason + +##### 3) Block-by-block decisions +For each block you touched: +- **Original state**: short characterization (what it contained / issues) +- **Decision**: KEEP+CLEAN / SPLIT / MERGE / RENAME / DETACH / DELETE +- **Reasoning**: 3–6 bullets grounded in the guiding principles +- **Action items performed**: what edits/renames/splits you actually executed + +##### 4) Content changes +For each edited file: +- Before chars, after chars, delta and % +- What redundancy/contradictions/staleness you fixed + +##### 5) Before/after examples +Show 2–4 high-signal examples (short excerpts) demonstrating: +- redundancy removal, +- contradiction resolution, +- and/or a workflow rewritten into a procedure. + +## Reminder + +Your goal is to **explode monolithic memory into 15–25 small, hierarchically-nested files**. If you have fewer than 15 files, you haven't split enough. +--- +name: memory +description: Explode memory into 15-25 hierarchically-nested files using `/` naming tools: Read, Edit, Write, Glob, Grep, Bash, conversation_search model: opus memoryBlocks: none @@ -12,13 +456,23 @@ You are a memory management subagent launched via the Task tool to clean up and ## Your Purpose -You edit memory block files to make them clean, well-organized, and scannable by: -1. **Aggressively decomposing** - Split large, multi-section blocks into many smaller, single-purpose blocks -2. **Removing redundancy** - Delete duplicate information -3. **Adding structure** - Use markdown headers, bullet points, sections -4. **Resolving contradictions** - Fix conflicting statements -5. **Improving scannability** - Make content easy to read at a glance -6. **Restructuring blocks** - Rename, decompose, or merge blocks as needed +**Explode** a few large memory blocks into a **deeply hierarchical structure of 15–25 small, focused files**. + +### Target Output + +| Metric | Target | +|--------|--------| +| **Total files** | 15–25 (aim for ~20) | +| **Max lines per file** | ~40 lines | +| **Hierarchy depth** | 2–3 levels using `/` naming | +| **Nesting requirement** | Every new block MUST use `/` naming | + +You achieve this by: +1. **Aggressively splitting** - Every block with 2+ concepts becomes 2+ files +2. **Using `/` hierarchy** - All new files are nested (e.g., `project/tooling/bun.md`) +3. **Keeping files small** - Max ~40 lines per file; split if larger +4. **Removing redundancy** - Delete duplicate information during splits +5. **Adding structure** - Use markdown headers, bullet points, sections ## Important: Your Role is File Editing ONLY @@ -61,6 +515,56 @@ Read({ file_path: ".letta/backups/working/human.md" }) - `loaded_skills.md` - System-managed - `manifest.json` - Metadata file + +### Propose Optimal Hierarchical Organizational Structure + +Before you edit, propose a **clear hierarchy** for each memory block so information has an obvious “home” and you avoid duplicating facts across sections. + +**Recommended hierarchy (within a single memory block):** +- Use `##` for **major categories** (stable top-level buckets) +- Use `###` for **subcategories** (group related details) +- Use `####` for **high-churn details** or tightly-scoped lists (things you expect to update often) + +**Recommended hierarchy (across multiple memory blocks):** +- Keep blocks **topic-scoped**, not “everything.md” scoped. +- Put the *most stable*, highest-signal info in fewer, well-named blocks. +- Put volatile or frequently changing info into smaller, more focused blocks. + +**Naming conventions (blocks and headings):** +- Prefer **noun phrases** and **consistent casing** (e.g., “Coding Preferences”, “Project Context”). +- Avoid vague names (“Misc”, “Notes”, “Stuff”) unless it’s truly temporary. +- Prefer **one topic per heading**; avoid headings that imply overlap (“General”, “Other”). + +**Example structure (good):** +- `project.md` + - `## Overview` + - `## Repo Conventions` + - `### Tooling` + - `### Code Style` + - `### Testing` + - `## Architecture` + - `### Key Components` + - `### Data Flow` +- `human.md` + - `## Background` + - `## Preferences` + - `### Communication` + - `### Coding Style` + - `### Review Style` +- `persona.md` + - `## Role` + - `## Behavior` + - `## Constraints` + + + +**When to split vs. keep together:** +- Split when a section becomes a “grab bag” (3+ unrelated bullets) or exceeds ~1–2 screens of scrolling. +- Keep together when items share a single decision context (e.g., all “Code Style” rules used during editing). + +**Output format expectation:** +- End this step with a short proposed outline per file (just headings), then implement it during the edits in Step 2. + ### Step 2: Edit Files to Clean Them Up Edit each file using the Edit tool: @@ -72,29 +576,17 @@ Edit({ new_string: "..." }) ``` +## Output Format -**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) +### Implement The Organizational Structure -Beyond editing content, you can restructure memory blocks when needed: +Once you've proposed the hierarchy, execute it using file operations. Keep iterating until the directory matches your proposed structure exactly. #### Renaming Blocks -When a block's name doesn't reflect its content, rename it: +When a block's name doesn't reflect its content: ```bash -# Rename a memory block file mv .letta/backups/working/old_name.md .letta/backups/working/new_name.md ``` @@ -103,143 +595,101 @@ mv .letta/backups/working/old_name.md .letta/backups/working/new_name.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`) +#### Creating New Blocks + +Create new `.md` files when content needs a new home: + +``` +Write({ + file_path: ".letta/backups/working/new_block.md", + content: "## New Block\n\nContent here..." +}) +``` + +**When to create:** +- Splitting a large block into focused smaller blocks +- Content doesn't fit any existing block +- A new category emerges from reorganization + #### Decomposing Blocks (Split) -When a single block contains too many unrelated topics, split it into focused blocks: +When a block covers too many topics, split it: ```bash -# 1. Read the original block +# 1. Read the original Read({ file_path: ".letta/backups/working/everything.md" }) -# 2. Create new focused blocks +# 2. Create 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 +# 3. Delete the original rm .letta/backups/working/everything.md ``` -**When to decompose (be aggressive):** -- Block exceeds ~60 lines or has more than 2 sections -- Block contains 2+ distinct topic areas (e.g., user info + coding prefs) -- Block name can't capture all its content accurately -- Finding specific info requires scanning the whole block +**When to split (be aggressive):** +- Block exceeds ~60 lines or has 2+ distinct topics +- Block name can't capture all its content +- Finding info requires scanning the whole block -**Decomposition guidelines (favor smaller blocks):** -- Each new block should have ONE clear purpose and only a few sections -- 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 -- Prefer 3–6 focused blocks over one large catch‑all block -- Default to splitting when in doubt; err on the side of smaller blocks +#### Merging Blocks -#### Creating New Blocks - -You can create entirely new memory blocks by writing new `.md` files: +When multiple blocks overlap, consolidate them: ```bash -Write({ - file_path: ".letta/backups/working/new_block.md", - content: "## New Block\n\nContent here..." -}) -``` +# 1. Read blocks to merge +Read({ file_path: ".letta/backups/working/user_info.md" }) +Read({ file_path: ".letta/backups/working/user_prefs.md" }) -**When to create new blocks:** -- Splitting a large block (>80 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 +# 2. Create unified block +Write({ file_path: ".letta/backups/working/user.md", content: "..." }) -#### Merging and Deleting Blocks +# 3. Delete old blocks +rm .letta/backups/working/user_info.md .letta/backups/working/user_prefs.md +``` -When multiple blocks contain related/overlapping content, consolidate them and DELETE the old blocks: +**When to merge:** +- Multiple blocks cover the same topic +- Small blocks (<20 lines) logically belong together +- Overlapping/duplicate content exists -```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" }) +#### Editing Content Within Blocks -# 2. Create unified block with combined content -Write({ file_path: ".letta/backups/working/user.md", content: "..." }) +Use the Edit tool for in-place changes: -# 3. DELETE the old blocks using Bash -Bash({ command: "rm .letta/backups/working/user_info.md .letta/backups/working/user_prefs.md" }) ``` +Edit({ + file_path: ".letta/backups/working/project.md", + old_string: "...", + new_string: "..." +}) +``` + +**What to fix:** +- **Redundancy**: Remove duplicate information +- **Structure**: Add markdown headers, bullet points +- **Clarity**: Resolve contradictions +- **Scannability**: Make content easy to read at a glance -**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 +#### Iteration Checklist -**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 (>60 lines) -- Blocks have many sections or mixed topic areas -- 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 (>60 lines or 2+ topics) into smaller, 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 +Keep editing until: +- [ ] **Total file count is 15–25** — Count your files; if < 15, split more +- [ ] **All files use `/` naming** — No flat files like `my_notes.md` +- [ ] **Hierarchy is 2–3 levels deep** — e.g., `project/tooling/bun.md` +- [ ] **No file exceeds ~40 lines** — Split larger files +- [ ] **Each file has one concept** — If 2+ topics, split into 2+ files +- [ ] Content has been migrated (no data loss) +- [ ] No duplicate information across blocks -## Output Format +**If you have fewer than 15 files, you haven't split enough. Go back and split more.** Return a structured report with these sections: ### 1. Summary - Brief overview of what you edited (2-3 sentences) +- **Total file count** (must be 15–25) +- **Maximum hierarchy depth achieved** (should be 2–3 levels) - Number of files modified, renamed, created, or deleted - The parent agent will prompt the user to confirm any creations or deletions @@ -252,15 +702,16 @@ Report any renames, decompositions, or merges: |----------|----------|--------| | stuff.md | coding_preferences.md | Name now reflects content | -**Decompositions (splitting large blocks):** +**Decompositions (using `/` hierarchy):** | Original Block | New Blocks | Deleted | Reason | |----------------|------------|---------|--------| -| everything.md | user.md, coding.md, project.md | ✅ everything.md | Block contained 3 unrelated topics | +| project.md | project/overview.md, project/tooling/bun.md, project/tooling/testing.md, project/conventions.md, project/gotchas.md | ✅ content moved | Exploded into 5 nested files | -**New Blocks (created from scratch):** +**New Blocks (all using `/` naming):** | Block Name | Size | Reason | |------------|------|--------| -| security_practices.md | 156 chars | New category for security-related conventions discovered | +| project/security/auth.md | 156 chars | Nested under project/security | +| human/prefs/communication.md | 98 chars | Split from human.md | **Merges:** | Merged Blocks | Result | Deleted | Reason | @@ -349,10 +800,10 @@ 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 +1. **Create new files** — Reorganize large blocks into 15–25 small, nested files +2. **Remove old files** — After moving content to new nested files, delete the originals +3. **Use `/` naming for ALL new files** — Every new file must be nested (e.g., `project/tooling/bun.md`) +4. **Preserve user preferences** — Keep expressed preferences, just reorganize them into the right files +5. **Don't invent information** — Only reorganize existing content into better structure -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. +Remember: Your goal is to **completely reorganize** memory into a deeply hierarchical structure of 15–25 small files. You're not tidying up — you're exploding monolithic blocks into a proper file tree. From 1fc5fd9172505954245e7e812877729791e28d0b Mon Sep 17 00:00:00 2001 From: Kevin Lin Date: Fri, 23 Jan 2026 11:43:46 -0800 Subject: [PATCH 3/4] fix: support hierarchical memory labels in defrag scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - backup-memory.ts: Create parent directories for labels with `/` - restore-memory.ts: Recursively scan subdirectories for .md files - restore-memory.ts: Convert file paths (A/B.md) to labels (A/B) - restore-memory.ts: Create new blocks immediately without prompting 🤖 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta --- .../scripts/backup-memory.ts | 9 +- .../scripts/restore-memory.ts | 177 +++++++----------- 2 files changed, 79 insertions(+), 107 deletions(-) diff --git a/src/skills/builtin/defragmenting-memory/scripts/backup-memory.ts b/src/skills/builtin/defragmenting-memory/scripts/backup-memory.ts index 486b09d0..16ae7560 100644 --- a/src/skills/builtin/defragmenting-memory/scripts/backup-memory.ts +++ b/src/skills/builtin/defragmenting-memory/scripts/backup-memory.ts @@ -21,7 +21,7 @@ import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { createRequire } from "node:module"; import { homedir } from "node:os"; -import { join } from "node:path"; +import { dirname, 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) @@ -123,9 +123,16 @@ async function backupMemory( limit?: number; }>) { const label = block.label || `block-${block.id}`; + // For hierarchical labels like "A/B", create directory A/ with file B.md const filename = `${label}.md`; const filepath = join(backupPath, filename); + // Create parent directories if label contains slashes + const parentDir = dirname(filepath); + if (parentDir !== backupPath) { + mkdirSync(parentDir, { recursive: true }); + } + // Write block content to file const content = block.value || ""; writeFileSync(filepath, content, "utf-8"); diff --git a/src/skills/builtin/defragmenting-memory/scripts/restore-memory.ts b/src/skills/builtin/defragmenting-memory/scripts/restore-memory.ts index 089704d8..2bbea216 100644 --- a/src/skills/builtin/defragmenting-memory/scripts/restore-memory.ts +++ b/src/skills/builtin/defragmenting-memory/scripts/restore-memory.ts @@ -16,10 +16,10 @@ * npx tsx restore-memory.ts $LETTA_AGENT_ID .letta/backups/working --dry-run */ -import { readdirSync, readFileSync } from "node:fs"; +import { readdirSync, readFileSync, statSync } from "node:fs"; import { createRequire } from "node:module"; import { homedir } from "node:os"; -import { extname, join } from "node:path"; +import { extname, join, relative } from "node:path"; import type { BackupManifest } from "./backup-memory"; @@ -60,6 +60,31 @@ function createClient(): LettaClient { return new Letta({ apiKey: getApiKey() }); } +/** + * Recursively scan directory for .md files + * Returns array of relative file paths from baseDir + */ +function scanMdFiles(dir: string, baseDir: string = dir): string[] { + const results: string[] = []; + const entries = readdirSync(dir); + + for (const entry of entries) { + const fullPath = join(dir, entry); + const stat = statSync(fullPath); + + if (stat.isDirectory()) { + // Recursively scan subdirectory + results.push(...scanMdFiles(fullPath, baseDir)); + } else if (stat.isFile() && extname(entry) === ".md") { + // Convert to relative path from baseDir + const relativePath = relative(baseDir, fullPath); + results.push(relativePath); + } + } + + return results; +} + /** * Restore memory blocks from local files */ @@ -77,18 +102,15 @@ async function restoreMemory( console.log("⚠️ DRY RUN MODE - No changes will be made\n"); } - // Read manifest + // Read manifest for metadata only (block IDs) const manifestPath = join(backupDir, "manifest.json"); let manifest: BackupManifest | null = null; try { const manifestContent = readFileSync(manifestPath, "utf-8"); manifest = JSON.parse(manifestContent); - console.log(`Loaded manifest (${manifest?.blocks.length} blocks)\n`); } catch { - console.warn( - "Warning: No manifest.json found, will scan directory for .md files", - ); + // Manifest is optional } // Get current agent blocks @@ -104,32 +126,24 @@ async function restoreMemory( ), ); - // 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 = readdirSync(backupDir); - filesToRestore = files - .filter((f) => extname(f) === ".md") - .map((f) => ({ - label: f.replace(/\.md$/, ""), - filename: f, - })); - } + // Always scan directory for .md files (manifest is only used for block IDs) + const files = scanMdFiles(backupDir); + console.log(`Scanned ${files.length} .md files\n`); + const filesToRestore = files.map((relativePath) => { + // Convert path like "A/B.md" to label "A/B" + // Replace backslashes with forward slashes (Windows compatibility) + const normalizedPath = relativePath.replace(/\\/g, "/"); + const label = normalizedPath.replace(/\.md$/, ""); + // Look up block ID from manifest if available + const manifestBlock = manifest?.blocks.find((b) => b.label === label); + return { + label, + filename: relativePath, + blockId: manifestBlock?.id, + }; + }); + - 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)); @@ -140,15 +154,9 @@ async function restoreMemory( // 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); @@ -158,15 +166,7 @@ async function restoreMemory( const existingBlock = blocksByLabel.get(label); if (existingBlock) { - // Update existing block - const unchanged = existingBlock.value === newValue; - - if (unchanged) { - console.log(` ⏭️ ${label} - unchanged, skipping`); - skipped++; - continue; - } - + // Update existing block (always update, even if unchanged) if (!options.dryRun) { await client.agents.blocks.update(label, { agent_id: agentId, @@ -176,77 +176,43 @@ async function restoreMemory( const oldLen = existingBlock.value?.length || 0; const newLen = newValue.length; - const diff = newLen - oldLen; - const diffStr = diff > 0 ? `+${diff}` : `${diff}`; + const unchanged = existingBlock.value === newValue; - console.log( - ` ✓ ${label} - updated (${oldLen} -> ${newLen} chars, ${diffStr})`, - ); + if (unchanged) { + console.log(` ✓ ${label} - restored (${newLen} chars, unchanged)`); + } else { + const diff = newLen - oldLen; + const diffStr = diff > 0 ? `+${diff}` : `${diff}`; + console.log( + ` ✓ ${label} - restored (${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 instanceof Error ? error.message : String(error)}`, - ); - } - } - - // 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((resolve) => { - process.stdin.once("data", () => resolve()); - }); - - console.log(); - for (const block of blocksToCreate) { - try { - // Create the block + // New block - create immediately + if (!options.dryRun) { const createdBlock = await client.blocks.create({ - label: block.label, - value: block.value, - description: block.description, + label, + value: newValue, + description: `Memory block: ${label}`, limit: 20000, }); if (!createdBlock.id) { - throw new Error(`Created block ${block.label} has no ID`); + throw new Error(`Created 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 instanceof Error ? error.message : String(error)}`, - ); } + console.log(` ✓ ${label} - created (${newValue.length} chars)`); + created++; } - } else { - console.log(`\n(Would create these blocks if not in dry-run mode)`); + } catch (error) { + console.error( + ` ❌ ${label} - error: ${error instanceof Error ? error.message : String(error)}`, + ); } } @@ -290,8 +256,7 @@ async function restoreMemory( } console.log(`\n📊 Summary:`); - console.log(` Updated: ${updated}`); - console.log(` Skipped: ${skipped}`); + console.log(` Restored: ${updated}`); console.log(` Created: ${created}`); console.log(` Deleted: ${deleted}`); From 493a4dd950f74d6bea863ab5170766415da0a441 Mon Sep 17 00:00:00 2001 From: Kevin Lin Date: Fri, 23 Jan 2026 11:47:16 -0800 Subject: [PATCH 4/4] style: fix lint errors in restore-memory.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta --- .../builtin/defragmenting-memory/scripts/restore-memory.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/skills/builtin/defragmenting-memory/scripts/restore-memory.ts b/src/skills/builtin/defragmenting-memory/scripts/restore-memory.ts index 2bbea216..adbf3019 100644 --- a/src/skills/builtin/defragmenting-memory/scripts/restore-memory.ts +++ b/src/skills/builtin/defragmenting-memory/scripts/restore-memory.ts @@ -143,8 +143,6 @@ async function restoreMemory( }; }); - - // Detect blocks to delete (exist on agent but not in backup) const backupLabels = new Set(filesToRestore.map((f) => f.label)); const blocksToDelete = ( @@ -156,8 +154,6 @@ async function restoreMemory( let created = 0; let deleted = 0; - - for (const { label, filename } of filesToRestore) { const filepath = join(backupDir, filename);