feat: add recall subagent for searching conversation history (#472)

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
Charles Packer
2026-01-05 18:52:52 -08:00
committed by GitHub
parent ff62233fa3
commit 0834a4e1c1
5 changed files with 88 additions and 1 deletions

View File

@@ -0,0 +1,36 @@
---
name: recall
description: Search conversation history to recall past discussions, decisions, and context
tools: Skill, Bash, Read, BashOutput
model: haiku
memoryBlocks: human, persona
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.
## Instructions
### Step 1: Load the searching-messages skill
```
Skill({ command: "load", skills: ["searching-messages"] })
```
The skill content will appear in your loaded_skills block with script paths and search strategies.
### 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.
## Output Format
1. **Direct answer** - What the user asked about
2. **Key findings** - Relevant quotes or summaries from past conversations
3. **When discussed** - Timestamps of relevant discussions
4. **Outcome/Decision** - What was decided or concluded (if applicable)

View File

@@ -21,8 +21,14 @@ import { MEMORY_BLOCK_LABELS, type MemoryBlockLabel } from "../memory";
import exploreAgentMd from "./builtin/explore.md";
import generalPurposeAgentMd from "./builtin/general-purpose.md";
import planAgentMd from "./builtin/plan.md";
import recallAgentMd from "./builtin/recall.md";
const BUILTIN_SOURCES = [exploreAgentMd, generalPurposeAgentMd, planAgentMd];
const BUILTIN_SOURCES = [
exploreAgentMd,
generalPurposeAgentMd,
planAgentMd,
recallAgentMd,
];
// Re-export for convenience
export type { MemoryBlockLabel };

View File

@@ -385,12 +385,22 @@ async function executeSubagent(
// Spawn Letta Code in headless mode.
// Some environments may have a different `letta` binary earlier in PATH.
const lettaCmd = process.env.LETTA_CODE_BIN || "letta";
// Pass parent agent ID so subagents can access parent's context (e.g., search history)
let parentAgentId: string | undefined;
try {
parentAgentId = getCurrentAgentId();
} catch {
// Context not available
}
const proc = spawn(lettaCmd, cliArgs, {
cwd: process.cwd(),
env: {
...process.env,
// Tag Task-spawned agents for easy filtering.
LETTA_CODE_AGENT_ROLE: "subagent",
// Pass parent agent ID for subagents that need to access parent's context
...(parentAgentId && { LETTA_PARENT_AGENT_ID: parentAgentId }),
},
});

View File

@@ -407,6 +407,8 @@ const READ_ONLY_SUBAGENT_TYPES = new Set([
"Explore",
"plan", // Planning agent - Glob, Grep, Read, LS, BashOutput
"Plan",
"recall", // Conversation history search - Skill, Bash, Read, BashOutput
"Recall",
]);
/**

View File

@@ -61,6 +61,31 @@ const SAFE_GIT_SUBCOMMANDS = new Set([
"remote",
]);
/**
* Read-only bundled skill scripts that are safe to execute without approval.
* Only scripts from the bundled searching-messages skill are allowed.
* We check for specific path patterns to prevent malicious scripts in user directories.
*/
const BUNDLED_READ_ONLY_SCRIPTS = [
// Bundled skills path (production): /path/to/skills/searching-messages/scripts/...
"/skills/searching-messages/scripts/search-messages.ts",
"/skills/searching-messages/scripts/get-messages.ts",
// Source path (development): /path/to/src/skills/builtin/searching-messages/scripts/...
"/skills/builtin/searching-messages/scripts/search-messages.ts",
"/skills/builtin/searching-messages/scripts/get-messages.ts",
];
/**
* Check if a script path is a known read-only bundled skill script
*/
function isReadOnlySkillScript(scriptPath: string): boolean {
// Normalize path separators for cross-platform
const normalized = scriptPath.replace(/\\/g, "/");
return BUNDLED_READ_ONLY_SCRIPTS.some((pattern) =>
normalized.endsWith(pattern),
);
}
// Operators that are always dangerous (file redirects, command substitution)
// Note: &&, ||, ; are handled by splitting and checking each segment
const DANGEROUS_OPERATOR_PATTERN = /(>>|>|\$\(|`)/;
@@ -149,6 +174,14 @@ function isSafeSegment(segment: string): boolean {
if (command === "sort") {
return !/\s-o\b/.test(segment);
}
// Allow npx tsx for read-only skill scripts (searching-messages)
if (command === "npx" && tokens[1] === "tsx") {
const scriptPath = tokens[2];
if (scriptPath && isReadOnlySkillScript(scriptPath)) {
return true;
}
return false;
}
return false;
}