fix: pre-load skills for subagents and fix dev-mode spawning (#918)

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
Charles Packer
2026-02-11 15:46:06 -08:00
committed by GitHub
parent 394aa58dc1
commit 135057dc8d
4 changed files with 46 additions and 7 deletions

View File

@@ -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
`<loaded_skills>`) to search via `letta messages search`
and `letta messages list`.
Use `letta messages search` and `letta messages list`
(documented in `<loaded_skills>` below) to search the
parent agent's conversation history.
**Sliding window through recent history:**

View File

@@ -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 {

View File

@@ -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</${skillId}>`);
} catch {
// Skill file not readable, skip
}
}
}
if (loadedContents.length > 0) {
pushPart(
`<loaded_skills>\n${loadedContents.join("\n\n")}\n</loaded_skills>`,
);
}
}
} catch {
// Skills discovery failed, skip
}

View File

@@ -437,6 +437,7 @@ async function main(): Promise<void> {
"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" },