diff --git a/src/agent/subagents/builtin/recall.md b/src/agent/subagents/builtin/recall.md index ada9958..6e5ab15 100644 --- a/src/agent/subagents/builtin/recall.md +++ b/src/agent/subagents/builtin/recall.md @@ -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 `` 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 diff --git a/src/agent/subagents/manager.ts b/src/agent/subagents/manager.ts index 34e1ca8..12289c7 100644 --- a/src/agent/subagents/manager.ts +++ b/src/agent/subagents/manager.ts @@ -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. diff --git a/src/tools/impl/Skill.ts b/src/tools/impl/Skill.ts index 238d903..37aa07b 100644 --- a/src/tools/impl/Skill.ts +++ b/src/tools/impl/Skill.ts @@ -410,3 +410,46 @@ export async function skill(args: SkillArgs): Promise { 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 { + 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 placeholder with actual path + const processedContent = hasExtras + ? skillContent.replace(//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."; +}