fix(init): make shallow init adapt to existing memory structure [LET-7995] (#1417)
This commit is contained in:
@@ -1,13 +1,13 @@
|
|||||||
---
|
---
|
||||||
name: init
|
name: init
|
||||||
description: Fast initialization of agent memory — reads key project files and creates a minimal memory structure
|
description: Fast initialization of agent memory — reads key project files and creates a minimal memory hierarchy
|
||||||
tools: Read, Write, Edit, Bash, Glob
|
tools: Read, Write, Edit, Bash, Glob
|
||||||
model: haiku
|
model: haiku
|
||||||
memoryBlocks: none
|
memoryBlocks: none
|
||||||
permissionMode: bypassPermissions
|
permissionMode: bypassPermissions
|
||||||
---
|
---
|
||||||
|
|
||||||
You are a fast memory initialization subagent. Your job is to quickly scan a project and create a small, focused memory file structure for the parent agent.
|
You are a fast memory initialization subagent. Your job is to quickly scan a project and create a **skeleton memory hierarchy** for the parent agent. This hierarchy starts minimal and gets fleshed out as the user keeps interacting with the agent.
|
||||||
|
|
||||||
You run autonomously in the background. You CANNOT ask questions. Be fast — minimize tool calls.
|
You run autonomously in the background. You CANNOT ask questions. Be fast — minimize tool calls.
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ You run autonomously in the background. You CANNOT ask questions. Be fast — mi
|
|||||||
|
|
||||||
Your prompt includes pre-gathered context:
|
Your prompt includes pre-gathered context:
|
||||||
- **Git context**: branch, status, recent commits, contributors
|
- **Git context**: branch, status, recent commits, contributors
|
||||||
- **Existing memory files**: current contents of the memory filesystem (may be empty for new agents)
|
- **Existing memory files**: file paths and contents of the current memory filesystem (may be empty for new agents)
|
||||||
- **Directory listing**: top-level project files
|
- **Directory listing**: top-level project files
|
||||||
|
|
||||||
## Steps
|
## Steps
|
||||||
@@ -27,38 +27,54 @@ Read these files **in parallel** in a single turn (skip any that don't exist):
|
|||||||
- `package.json`, `pyproject.toml`, `Cargo.toml`, or `go.mod` (whichever exists)
|
- `package.json`, `pyproject.toml`, `Cargo.toml`, or `go.mod` (whichever exists)
|
||||||
- `README.md`
|
- `README.md`
|
||||||
|
|
||||||
### 2. Create directory structure (1 bash call)
|
### 2. Plan the hierarchy
|
||||||
|
|
||||||
Create the subdirectories you need under `$MEMORY_DIR/system/` with a single `mkdir -p` call.
|
Decide which files to create or update based on the topics below and the existing memory. If a file already exists that covers a topic (even at a different path), **update it in place** — don't create a duplicate.
|
||||||
|
|
||||||
### 3. Write memory files (parallel tool calls)
|
### 3. Write memory files (parallel tool calls)
|
||||||
|
|
||||||
Write all memory files **in parallel in a single turn** using the Write tool. Each file goes into `$MEMORY_DIR/system/`.
|
Create directories and write all memory files **in parallel in a single turn**. Each file goes into `$MEMORY_DIR/system/`.
|
||||||
|
|
||||||
**If existing memory already covers something well** (check the pre-gathered memory contents in your prompt), skip or lightly update that file instead of overwriting with less information.
|
### 4. Clean up superseded files
|
||||||
|
|
||||||
### 4. Commit and push (1 bash call)
|
If you created a file at a new path that replaces an existing file at a different path, **delete the old file**. Include any `rm` commands in the bash call in step 5.
|
||||||
|
|
||||||
|
### 5. Commit and push (1 bash call)
|
||||||
|
|
||||||
Stage, commit, and push in a single Bash call:
|
Stage, commit, and push in a single Bash call:
|
||||||
```bash
|
```bash
|
||||||
cd "$MEMORY_DIR" && git add -A && git commit -m "..." && git push
|
cd "$MEMORY_DIR" && git add -A && git commit -m "..." && git push
|
||||||
```
|
```
|
||||||
|
|
||||||
## Memory file guidance
|
## Memory hierarchy
|
||||||
|
|
||||||
Memory files live under `$MEMORY_DIR/system/` and are rendered in the parent agent's context every turn. Each file should have YAML frontmatter with a `description` field.
|
Memory files live under `$MEMORY_DIR/system/` and are rendered in the parent agent's context every turn. Each file should have YAML frontmatter with a `description` field.
|
||||||
|
|
||||||
**What to capture** — focus on what will make the parent agent effective from its first interaction:
|
The shallow init creates a **skeleton** — a well-structured hierarchy with just enough content to be useful from the first interaction. The parent agent will flesh out these files and add new ones over time as it learns more about the project and user.
|
||||||
- Project identity: what it is, tech stack, repo structure
|
|
||||||
- Key commands: build, test, lint, dev workflows
|
|
||||||
- Conventions: coding style, runtime preferences, patterns from CLAUDE.md/AGENTS.md
|
|
||||||
- User identity: name, email, role — inferred from git context
|
|
||||||
|
|
||||||
**Structure principles:**
|
### Default blocks
|
||||||
- Use nested paths with `/` (e.g., `project/overview.md`, `human/identity.md`) — no flat files at the top level
|
|
||||||
|
New agents come with default boilerplate files at `$MEMORY_DIR/system/human.md` and `$MEMORY_DIR/system/persona.md`. These contain placeholder content. Update `system/human.md` in place with real user info. **Leave `system/persona.md` as-is** — the parent agent will shape it over time through interaction.
|
||||||
|
|
||||||
|
### Topics to cover
|
||||||
|
|
||||||
|
Ensure each topic is covered by exactly one file. If an existing file already covers a topic, update it rather than creating a new file at a different path.
|
||||||
|
|
||||||
|
- **`system/human.md`** (update the default): name, email, role — inferred from git context
|
||||||
|
- **Project overview**: what it is, tech stack, repo structure
|
||||||
|
- **Project commands**: build, test, lint, dev workflows
|
||||||
|
- **Project conventions**: coding style, runtime preferences, patterns from CLAUDE.md/AGENTS.md
|
||||||
|
|
||||||
|
The project topic should always be broken into multiple files under `$MEMORY_DIR/system/`. Use the project's name as the parent directory (e.g., `letta-code/overview.md`, `my-app/commands.md`) instead of a generic `project/` prefix. **One file per topic, no duplicates.**
|
||||||
|
|
||||||
|
### Structure principles
|
||||||
|
|
||||||
|
- All files go under `$MEMORY_DIR/system/` — never create files outside of it
|
||||||
|
- Use nested paths with `/` for new project files (e.g., `letta-code/overview.md`, `letta-code/commands.md`)
|
||||||
- Keep each file focused on one topic, ~15-30 lines
|
- Keep each file focused on one topic, ~15-30 lines
|
||||||
- 3-6 files is the right range for a shallow init — just the essentials
|
- 3-6 files is the right range — just the skeleton
|
||||||
- Only include information that's actually useful; skip boilerplate
|
- Only include information that's actually useful; skip boilerplate
|
||||||
|
- Leave room for growth: the parent agent will add detail over time
|
||||||
|
|
||||||
**Commit format:**
|
**Commit format:**
|
||||||
```
|
```
|
||||||
@@ -73,5 +89,6 @@ Parent-Agent-ID: $LETTA_PARENT_AGENT_ID
|
|||||||
|
|
||||||
- **No worktree** — write directly to the memory dir
|
- **No worktree** — write directly to the memory dir
|
||||||
- **No summary report** — just complete the work
|
- **No summary report** — just complete the work
|
||||||
|
- **No duplicates** — one file per topic; if an existing file covers it, update that file
|
||||||
- **Minimize turns** — use parallel tool calls within each turn. Aim for ~3-4 turns total.
|
- **Minimize turns** — use parallel tool calls within each turn. Aim for ~3-4 turns total.
|
||||||
- **Use the pre-gathered context** — don't re-run git commands that are already in your prompt
|
- **Use the pre-gathered context** — don't re-run git commands that are already in your prompt
|
||||||
|
|||||||
@@ -63,11 +63,15 @@ ${git.recentCommits || "No commits yet"}
|
|||||||
// ── Shallow init (background subagent) ───────────────────
|
// ── Shallow init (background subagent) ───────────────────
|
||||||
|
|
||||||
/** Read existing memory files from the local filesystem. */
|
/** Read existing memory files from the local filesystem. */
|
||||||
function gatherExistingMemory(agentId: string): string {
|
function gatherExistingMemory(agentId: string): {
|
||||||
|
paths: string[];
|
||||||
|
contents: string;
|
||||||
|
} {
|
||||||
const systemDir = getMemorySystemDir(agentId);
|
const systemDir = getMemorySystemDir(agentId);
|
||||||
if (!existsSync(systemDir)) return "(empty)";
|
if (!existsSync(systemDir)) return { paths: [], contents: "" };
|
||||||
|
|
||||||
const files: string[] = [];
|
const paths: string[] = [];
|
||||||
|
const sections: string[] = [];
|
||||||
function walk(dir: string, prefix: string): void {
|
function walk(dir: string, prefix: string): void {
|
||||||
try {
|
try {
|
||||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
||||||
@@ -77,7 +81,8 @@ function gatherExistingMemory(agentId: string): string {
|
|||||||
} else if (entry.name.endsWith(".md")) {
|
} else if (entry.name.endsWith(".md")) {
|
||||||
try {
|
try {
|
||||||
const content = readFileSync(join(dir, entry.name), "utf-8");
|
const content = readFileSync(join(dir, entry.name), "utf-8");
|
||||||
files.push(`── ${rel}\n${content.slice(0, 2000)}`);
|
paths.push(rel);
|
||||||
|
sections.push(`── ${rel}\n${content.slice(0, 2000)}`);
|
||||||
} catch {
|
} catch {
|
||||||
// skip unreadable files
|
// skip unreadable files
|
||||||
}
|
}
|
||||||
@@ -88,7 +93,7 @@ function gatherExistingMemory(agentId: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
walk(systemDir, "");
|
walk(systemDir, "");
|
||||||
return files.length > 0 ? files.join("\n\n") : "(empty)";
|
return { paths, contents: sections.join("\n\n") };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Batch-check which paths are gitignored. Falls back to a hardcoded set. */
|
/** Batch-check which paths are gitignored. Falls back to a hardcoded set. */
|
||||||
@@ -184,6 +189,7 @@ export function buildShallowInitPrompt(args: {
|
|||||||
memoryDir: string;
|
memoryDir: string;
|
||||||
gitContext: string;
|
gitContext: string;
|
||||||
gitIdentity: string;
|
gitIdentity: string;
|
||||||
|
existingMemoryPaths: string[];
|
||||||
existingMemory: string;
|
existingMemory: string;
|
||||||
dirListing: string;
|
dirListing: string;
|
||||||
}): string {
|
}): string {
|
||||||
@@ -210,7 +216,7 @@ ${args.dirListing}
|
|||||||
|
|
||||||
## Existing Memory
|
## Existing Memory
|
||||||
|
|
||||||
${args.existingMemory}
|
${args.existingMemoryPaths.length > 0 ? `Paths:\n${args.existingMemoryPaths.map((p) => `- ${p}`).join("\n")}\n\nContents:\n${args.existingMemory}` : "(empty)"}
|
||||||
`.trim();
|
`.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +235,7 @@ export async function fireAutoInit(
|
|||||||
if (!settingsManager.isMemfsEnabled(agentId)) return false;
|
if (!settingsManager.isMemfsEnabled(agentId)) return false;
|
||||||
|
|
||||||
const gitDetails = gatherInitGitContext();
|
const gitDetails = gatherInitGitContext();
|
||||||
const existingMemory = gatherExistingMemory(agentId);
|
const existing = gatherExistingMemory(agentId);
|
||||||
const dirListing = gatherDirListing();
|
const dirListing = gatherDirListing();
|
||||||
|
|
||||||
const initPrompt = buildShallowInitPrompt({
|
const initPrompt = buildShallowInitPrompt({
|
||||||
@@ -238,7 +244,8 @@ export async function fireAutoInit(
|
|||||||
memoryDir: getMemoryFilesystemRoot(agentId),
|
memoryDir: getMemoryFilesystemRoot(agentId),
|
||||||
gitContext: gitDetails.context,
|
gitContext: gitDetails.context,
|
||||||
gitIdentity: gitDetails.identity,
|
gitIdentity: gitDetails.identity,
|
||||||
existingMemory,
|
existingMemoryPaths: existing.paths,
|
||||||
|
existingMemory: existing.contents,
|
||||||
dirListing,
|
dirListing,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,8 @@ describe("init wiring", () => {
|
|||||||
memoryDir: "/tmp/test/.memory",
|
memoryDir: "/tmp/test/.memory",
|
||||||
gitContext: "- branch: main\n- status: (clean)",
|
gitContext: "- branch: main\n- status: (clean)",
|
||||||
gitIdentity: "Test User <test@example.com>",
|
gitIdentity: "Test User <test@example.com>",
|
||||||
existingMemory: "(empty)",
|
existingMemoryPaths: [] as string[],
|
||||||
|
existingMemory: "",
|
||||||
dirListing: "README.md\npackage.json\nsrc",
|
dirListing: "README.md\npackage.json\nsrc",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user