feat: implement skills frontmatter pre-loading for subagents (#581)
Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
@@ -1,32 +1,30 @@
|
||||
---
|
||||
name: recall
|
||||
description: Search conversation history to recall past discussions, decisions, and context
|
||||
tools: Skill, Bash, Read, BashOutput
|
||||
model: haiku
|
||||
memoryBlocks: human, persona, skills, loaded_skills
|
||||
tools: Bash, Read, BashOutput
|
||||
model: opus
|
||||
memoryBlocks: skills, loaded_skills
|
||||
skills: searching-messages
|
||||
mode: stateless
|
||||
---
|
||||
|
||||
You are a subagent launched via the Task tool to search conversation history. You run autonomously and return a single final report when done. You CANNOT ask questions mid-execution.
|
||||
|
||||
## CRITICAL WARNINGS
|
||||
|
||||
1. **NEVER use `conversation_search`** - It only searches YOUR empty history, not the parent's.
|
||||
2. **NEVER invent commands** - There is NO `letta messages search` or `letta messages list`. These don't exist.
|
||||
|
||||
## Instructions
|
||||
|
||||
### Step 1: Load the searching-messages skill
|
||||
```
|
||||
Skill({ command: "load", skills: ["searching-messages"] })
|
||||
```
|
||||
The `searching-messages` skill is pre-loaded in your `<loaded_skills>` memory block below. Read it carefully - it contains:
|
||||
- `# Skill Directory:` - the exact path to use in commands
|
||||
- Multiple search strategies (needle + expand, date-bounded, broad discovery)
|
||||
- Command options and examples
|
||||
|
||||
After loading, your `loaded_skills` memory block contains the full instructions with ready-to-use bash commands. Follow them directly - do not search for files or guess at commands.
|
||||
**Follow the skill's strategies thoroughly.** Use multiple searches if needed to gather comprehensive context. Always add `--agent-id $LETTA_PARENT_AGENT_ID` to search the parent agent's history.
|
||||
|
||||
### Step 2: Search the parent agent's history
|
||||
|
||||
**CRITICAL - Two rules:**
|
||||
|
||||
1. **DO NOT use `conversation_search`** - That tool only searches YOUR history (empty). You MUST use the Bash scripts from the skill.
|
||||
|
||||
2. **ALWAYS add `--agent-id $LETTA_PARENT_AGENT_ID`** - This searches the parent agent's history. The only exception is `--all-agents` searches.
|
||||
|
||||
Follow the strategies documented in the loaded skill.
|
||||
After gathering results, compile a comprehensive report.
|
||||
|
||||
## Output Format
|
||||
|
||||
|
||||
@@ -18,10 +18,12 @@ import { cliPermissions } from "../../permissions/cli";
|
||||
import { permissionMode } from "../../permissions/mode";
|
||||
import { sessionPermissions } from "../../permissions/session";
|
||||
import { settingsManager } from "../../settings-manager";
|
||||
import { preloadSkillsContent } from "../../tools/impl/Skill";
|
||||
import { getErrorMessage } from "../../utils/error";
|
||||
import { getClient } from "../client";
|
||||
import { getCurrentAgentId } from "../context";
|
||||
import { resolveModelByLlmConfig } from "../model";
|
||||
import { SKILLS_DIR } from "../skills";
|
||||
import { getAllSubagentConfigs, type SubagentConfig } from ".";
|
||||
|
||||
// ============================================================================
|
||||
@@ -306,6 +308,7 @@ function buildSubagentArgs(
|
||||
config: SubagentConfig,
|
||||
model: string,
|
||||
userPrompt: string,
|
||||
preloadedSkillsContent?: string,
|
||||
): string[] {
|
||||
const args: string[] = [
|
||||
"--new-agent",
|
||||
@@ -360,6 +363,11 @@ function buildSubagentArgs(
|
||||
args.push("--tools", config.allowedTools.join(","));
|
||||
}
|
||||
|
||||
// Add pre-loaded skills content if provided
|
||||
if (preloadedSkillsContent) {
|
||||
args.push("--block-value", `loaded_skills=${preloadedSkillsContent}`);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
@@ -390,7 +398,22 @@ async function executeSubagent(
|
||||
updateSubagent(subagentId, { model });
|
||||
|
||||
try {
|
||||
const cliArgs = buildSubagentArgs(type, config, model, userPrompt);
|
||||
// Pre-load skills if configured
|
||||
let preloadedSkillsContent: string | undefined;
|
||||
if (config.skills && config.skills.length > 0) {
|
||||
preloadedSkillsContent = await preloadSkillsContent(
|
||||
config.skills,
|
||||
SKILLS_DIR,
|
||||
);
|
||||
}
|
||||
|
||||
const cliArgs = buildSubagentArgs(
|
||||
type,
|
||||
config,
|
||||
model,
|
||||
userPrompt,
|
||||
preloadedSkillsContent,
|
||||
);
|
||||
|
||||
// Spawn Letta Code in headless mode.
|
||||
// Some environments may have a different `letta` binary earlier in PATH.
|
||||
|
||||
@@ -410,3 +410,46 @@ export async function skill(args: SkillArgs): Promise<SkillResult> {
|
||||
throw new Error(`Failed to ${command} skill(s): ${String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-load skills and return formatted content for the loaded_skills block.
|
||||
* This is used by subagent manager to pre-populate skills before the agent starts.
|
||||
*/
|
||||
export async function preloadSkillsContent(
|
||||
skillIds: string[],
|
||||
skillsDir: string,
|
||||
): Promise<string> {
|
||||
if (skillIds.length === 0) {
|
||||
return "No skills currently loaded.";
|
||||
}
|
||||
|
||||
let content = "";
|
||||
|
||||
for (const skillId of skillIds) {
|
||||
try {
|
||||
const { content: skillContent, path: skillPath } = await readSkillContent(
|
||||
skillId,
|
||||
skillsDir,
|
||||
);
|
||||
|
||||
const skillDir = dirname(skillPath);
|
||||
const hasExtras = hasAdditionalFiles(skillPath);
|
||||
const pathLine = hasExtras ? `# Skill Directory: ${skillDir}\n\n` : "";
|
||||
|
||||
// Replace <SKILL_DIR> placeholder with actual path
|
||||
const processedContent = hasExtras
|
||||
? skillContent.replace(/<SKILL_DIR>/g, skillDir)
|
||||
: skillContent;
|
||||
|
||||
const separator = content ? "\n\n---\n\n" : "";
|
||||
content = `${content}${separator}# Skill: ${skillId}\n${pathLine}${processedContent}`;
|
||||
} catch (error) {
|
||||
// Skip skills that can't be loaded
|
||||
console.error(
|
||||
`Warning: Could not pre-load skill "${skillId}": ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return content || "No skills currently loaded.";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user