From 135057dc8db4f3ccac862fa6529b889059141148 Mon Sep 17 00:00:00 2001 From: Charles Packer Date: Wed, 11 Feb 2026 15:46:06 -0800 Subject: [PATCH] fix: pre-load skills for subagents and fix dev-mode spawning (#918) Co-authored-by: Letta --- src/agent/subagents/builtin/reflection.md | 6 ++--- src/agent/subagents/manager.ts | 18 +++++++++++---- src/headless.ts | 28 +++++++++++++++++++++++ src/index.ts | 1 + 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/agent/subagents/builtin/reflection.md b/src/agent/subagents/builtin/reflection.md index 536d8a0..4a51e2d 100644 --- a/src/agent/subagents/builtin/reflection.md +++ b/src/agent/subagents/builtin/reflection.md @@ -75,9 +75,9 @@ All subsequent file operations target the worktree: ### Phase 2: Review Recent Conversation History -Use the `searching-messages` skill (pre-loaded in -``) to search via `letta messages search` -and `letta messages list`. +Use `letta messages search` and `letta messages list` +(documented in `` below) to search the +parent agent's conversation history. **Sliding window through recent history:** diff --git a/src/agent/subagents/manager.ts b/src/agent/subagents/manager.ts index 5879180..e6f4db9 100644 --- a/src/agent/subagents/manager.ts +++ b/src/agent/subagents/manager.ts @@ -517,6 +517,11 @@ function buildSubagentArgs( args.push("--max-turns", String(maxTurns)); } + // Pre-load skills specified in the subagent config + if (config.skills.length > 0) { + args.push("--pre-load-skills", config.skills.join(",")); + } + return args; } @@ -552,7 +557,7 @@ async function executeSubagent( } try { - const cliArgs = buildSubagentArgs( + let cliArgs = buildSubagentArgs( type, config, model, @@ -566,14 +571,19 @@ async function executeSubagent( // Use the same binary as the current process, with fallbacks: // 1. LETTA_CODE_BIN env var (explicit override) // 2. Current process argv[1] if it's a .js file (built letta.js) - // 3. ./letta.js if running from dev (src/index.ts) + // 3. Dev mode: use process.execPath (bun) with the .ts script as first arg // 4. "letta" (global install) const currentScript = process.argv[1] || ""; - const lettaCmd = + let lettaCmd = process.env.LETTA_CODE_BIN || (currentScript.endsWith(".js") ? currentScript : null) || - (currentScript.includes("src/index.ts") ? "./letta.js" : null) || "letta"; + // In dev mode (running .ts file via bun), use the runtime binary directly + // and prepend the script path to the CLI args + if (currentScript.endsWith(".ts") && !process.env.LETTA_CODE_BIN) { + lettaCmd = process.execPath; // e.g., /path/to/bun + cliArgs = [currentScript, ...cliArgs]; + } // Pass parent agent ID so subagents can access parent's context (e.g., search history) let parentAgentId: string | undefined; try { diff --git a/src/headless.ts b/src/headless.ts index a39deea..aceecaa 100644 --- a/src/headless.ts +++ b/src/headless.ts @@ -118,6 +118,7 @@ export async function handleHeadlessCommand( "permission-mode": { type: "string" }, yolo: { type: "boolean" }, skills: { type: "string" }, + "pre-load-skills": { type: "string" }, sleeptime: { type: "boolean" }, "init-blocks": { type: "string" }, "base-tools": { type: "string" }, @@ -264,6 +265,7 @@ export async function handleHeadlessCommand( const memfsFlag = values.memfs as boolean | undefined; const noMemfsFlag = values["no-memfs"] as boolean | undefined; const fromAfFile = values["from-af"] as string | undefined; + const preLoadSkillsRaw = values["pre-load-skills"] as string | undefined; const maxTurnsRaw = values["max-turns"] as string | undefined; // Parse and validate max-turns if provided @@ -1104,6 +1106,32 @@ ${SYSTEM_REMINDER_CLOSE} if (skillsReminder) { pushPart(skillsReminder); } + + // Pre-load specific skills' full content (used by subagents with skills: field) + if (preLoadSkillsRaw) { + const { readFile: readFileAsync } = await import("node:fs/promises"); + const skillIds = preLoadSkillsRaw + .split(",") + .map((s) => s.trim()) + .filter(Boolean); + const loadedContents: string[] = []; + for (const skillId of skillIds) { + const skill = skills.find((s) => s.id === skillId); + if (skill?.path) { + try { + const content = await readFileAsync(skill.path, "utf-8"); + loadedContents.push(`<${skillId}>\n${content}\n`); + } catch { + // Skill file not readable, skip + } + } + } + if (loadedContents.length > 0) { + pushPart( + `\n${loadedContents.join("\n\n")}\n`, + ); + } + } } catch { // Skills discovery failed, skip } diff --git a/src/index.ts b/src/index.ts index 534c169..092d1a9 100755 --- a/src/index.ts +++ b/src/index.ts @@ -437,6 +437,7 @@ async function main(): Promise { "include-partial-messages": { type: "boolean" }, "from-agent": { type: "string" }, skills: { type: "string" }, + "pre-load-skills": { type: "string" }, sleeptime: { type: "boolean" }, "from-af": { type: "string" }, import: { type: "string" },