feat: add system prompt and memory block configuration for headless mode (#450)

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
Charles Packer
2026-01-02 15:32:07 -08:00
committed by GitHub
parent 321519a1b7
commit a956a15db4
10 changed files with 399 additions and 139 deletions

View File

@@ -78,7 +78,7 @@ if (existsSync(bundledSkillsSrc)) {
// Generate type declarations for wire types export
console.log("📝 Generating type declarations...");
await Bun.$`bunx tsc -p tsconfig.types.json`;
console.log(" Output: dist/types/wire.d.ts");
console.log(" Output: dist/types/protocol.d.ts");
console.log("✅ Build complete!");
console.log(` Output: letta.js`);

View File

@@ -17,8 +17,8 @@
],
"exports": {
".": "./letta.js",
"./wire-types": {
"types": "./dist/types/wire.d.ts"
"./protocol": {
"types": "./dist/types/protocol.d.ts"
}
},
"repository": {

View File

@@ -26,7 +26,7 @@ import { discoverSkills, formatSkillsForMemory, SKILLS_DIR } from "./skills";
*/
export interface BlockProvenance {
label: string;
source: "global" | "project" | "new";
source: "global" | "project" | "new" | "shared";
}
/**
@@ -45,24 +45,75 @@ export interface CreateAgentResult {
provenance: AgentProvenance;
}
export interface CreateAgentOptions {
name?: string;
model?: string;
embeddingModel?: string;
updateArgs?: Record<string, unknown>;
skillsDirectory?: string;
parallelToolCalls?: boolean;
enableSleeptime?: boolean;
/** System prompt preset (e.g., 'default', 'letta-claude', 'letta-codex') */
systemPromptPreset?: string;
/** Raw system prompt string (mutually exclusive with systemPromptPreset) */
systemPromptCustom?: string;
/** Additional text to append to the resolved system prompt */
systemPromptAppend?: string;
/** Block labels to initialize (from default blocks) */
initBlocks?: string[];
/** Base tools to include */
baseTools?: string[];
/** Custom memory blocks (overrides default blocks) */
memoryBlocks?: Array<
{ label: string; value: string; description?: string } | { blockId: string }
>;
/** Override values for preset blocks (label → value) */
blockValues?: Record<string, string>;
}
export async function createAgent(
name = DEFAULT_AGENT_NAME,
nameOrOptions: string | CreateAgentOptions = DEFAULT_AGENT_NAME,
model?: string,
embeddingModel = "openai/text-embedding-3-small",
updateArgs?: Record<string, unknown>,
skillsDirectory?: string,
parallelToolCalls = true,
enableSleeptime = false,
systemPromptId?: string,
systemPromptPreset?: string,
initBlocks?: string[],
baseTools?: string[],
) {
// Support both old positional args and new options object
let options: CreateAgentOptions;
if (typeof nameOrOptions === "object") {
options = nameOrOptions;
} else {
options = {
name: nameOrOptions,
model,
embeddingModel,
updateArgs,
skillsDirectory,
parallelToolCalls,
enableSleeptime,
systemPromptPreset,
initBlocks,
baseTools,
};
}
const name = options.name ?? DEFAULT_AGENT_NAME;
const embeddingModelVal =
options.embeddingModel ?? "openai/text-embedding-3-small";
const parallelToolCallsVal = options.parallelToolCalls ?? true;
const enableSleeptimeVal = options.enableSleeptime ?? false;
// Resolve model identifier to handle
let modelHandle: string;
if (model) {
const resolved = resolveModel(model);
if (options.model) {
const resolved = resolveModel(options.model);
if (!resolved) {
console.error(`Error: Unknown model "${model}"`);
console.error(`Error: Unknown model "${options.model}"`);
console.error("Available models:");
console.error(formatAvailableModels());
process.exit(1);
@@ -79,14 +130,12 @@ export async function createAgent(
// Map internal names to server names so the agent sees the correct tool names
const { getServerToolName } = await import("../tools/manager");
const internalToolNames = getToolNames();
const serverToolNames = internalToolNames.map((name) =>
getServerToolName(name),
);
const serverToolNames = internalToolNames.map((n) => getServerToolName(n));
const baseMemoryTool = modelHandle.startsWith("openai/gpt-5")
? "memory_apply_patch"
: "memory";
const defaultBaseTools = baseTools ?? [
const defaultBaseTools = options.baseTools ?? [
baseMemoryTool,
"web_search",
"conversation_search",
@@ -122,14 +171,41 @@ export async function createAgent(
}
}
// Determine which memory blocks to use:
// 1. If options.memoryBlocks is provided, use those (custom blocks and/or block references)
// 2. Otherwise, use default blocks filtered by options.initBlocks
// Separate block references from blocks to create
const referencedBlockIds: string[] = [];
let filteredMemoryBlocks: Array<{
label: string;
value: string;
description?: string | null;
limit?: number;
}>;
if (options.memoryBlocks !== undefined) {
// Separate blockId references from CreateBlock items
const createBlocks: typeof filteredMemoryBlocks = [];
for (const item of options.memoryBlocks) {
if ("blockId" in item) {
referencedBlockIds.push(item.blockId);
} else {
createBlocks.push(item as (typeof filteredMemoryBlocks)[0]);
}
}
filteredMemoryBlocks = createBlocks;
} else {
// Load memory blocks from .mdx files
const defaultMemoryBlocks =
initBlocks && initBlocks.length === 0 ? [] : await getDefaultMemoryBlocks();
options.initBlocks && options.initBlocks.length === 0
? []
: await getDefaultMemoryBlocks();
// Optional filter: only initialize a subset of memory blocks on creation
const allowedBlockLabels = initBlocks
const allowedBlockLabels = options.initBlocks
? new Set(
initBlocks.map((name) => name.trim()).filter((name) => name.length > 0),
options.initBlocks.map((n) => n.trim()).filter((n) => n.length > 0),
)
: undefined;
@@ -145,14 +221,29 @@ export async function createAgent(
}
}
const filteredMemoryBlocks =
filteredMemoryBlocks =
allowedBlockLabels && allowedBlockLabels.size > 0
? defaultMemoryBlocks.filter((b) => allowedBlockLabels.has(b.label))
: defaultMemoryBlocks;
}
// Apply blockValues overrides to preset blocks
if (options.blockValues) {
for (const [label, value] of Object.entries(options.blockValues)) {
const block = filteredMemoryBlocks.find((b) => b.label === label);
if (block) {
block.value = value;
} else {
console.warn(
`Ignoring --block-value for "${label}" - block not included in memory config`,
);
}
}
}
// Resolve absolute path for skills directory
const resolvedSkillsDirectory =
skillsDirectory || join(process.cwd(), SKILLS_DIR);
options.skillsDirectory || join(process.cwd(), SKILLS_DIR);
// Discover skills from .skills directory and populate skills memory block
try {
@@ -198,12 +289,31 @@ export async function createAgent(
}
}
// Add any referenced block IDs (existing blocks to attach)
for (const blockId of referencedBlockIds) {
blockIds.push(blockId);
blockProvenance.push({ label: blockId, source: "shared" });
}
// Get the model's context window from its configuration
const modelUpdateArgs = getModelUpdateArgs(modelHandle);
const contextWindow = (modelUpdateArgs?.context_window as number) || 200_000;
// Resolve system prompt ID to content
const systemPromptContent = await resolveSystemPrompt(systemPromptId);
// Resolve system prompt content:
// 1. If systemPromptCustom is provided, use it as-is
// 2. Otherwise, resolve systemPromptPreset to content
// 3. If systemPromptAppend is provided, append it to the result
let systemPromptContent: string;
if (options.systemPromptCustom) {
systemPromptContent = options.systemPromptCustom;
} else {
systemPromptContent = await resolveSystemPrompt(options.systemPromptPreset);
}
// Append additional instructions if provided
if (options.systemPromptAppend) {
systemPromptContent = `${systemPromptContent}\n\n${options.systemPromptAppend}`;
}
// Create agent with all block IDs (existing + newly created)
const tags = ["origin:letta-code"];
@@ -216,7 +326,7 @@ export async function createAgent(
system: systemPromptContent,
name,
description: `Letta Code agent created in ${process.cwd()}`,
embedding: embeddingModel,
embedding: embeddingModelVal,
model: modelHandle,
context_window_limit: contextWindow,
tools: toolNames,
@@ -226,8 +336,8 @@ export async function createAgent(
include_base_tools: false,
include_base_tool_rules: false,
initial_message_sequence: [],
parallel_tool_calls: parallelToolCalls,
enable_sleeptime: enableSleeptime,
parallel_tool_calls: parallelToolCallsVal,
enable_sleeptime: enableSleeptimeVal,
});
// Note: Preflight check above falls back to 'memory' when 'memory_apply_patch' is unavailable.
@@ -235,8 +345,8 @@ export async function createAgent(
// Apply updateArgs if provided (e.g., context_window, reasoning_effort, verbosity, etc.)
// We intentionally pass context_window through so updateAgentLLMConfig can set
// context_window_limit using the latest server API, avoiding any fallback.
if (updateArgs && Object.keys(updateArgs).length > 0) {
await updateAgentLLMConfig(agent.id, modelHandle, updateArgs);
if (options.updateArgs && Object.keys(options.updateArgs).length > 0) {
await updateAgentLLMConfig(agent.id, modelHandle, options.updateArgs);
}
// Always retrieve the agent to ensure we get the full state with populated memory blocks
@@ -245,7 +355,7 @@ export async function createAgent(
});
// Update persona block for sleeptime agent
if (enableSleeptime && fullAgent.managed_group) {
if (enableSleeptimeVal && fullAgent.managed_group) {
// Find the sleeptime agent in the managed group by checking agent_type
for (const groupAgentId of fullAgent.managed_group.agent_ids) {
try {

View File

@@ -56,47 +56,48 @@ export const SYSTEM_PROMPTS: SystemPromptOption[] = [
{
id: "default",
label: "Default",
description: "Standard Letta Code system prompt (Claude-optimized)",
description: "Alias for letta-claude",
content: lettaAnthropicPrompt,
isDefault: true,
isFeatured: true,
},
{
id: "legacy",
label: "Legacy",
description: "Original system prompt",
content: systemPrompt,
id: "letta-claude",
label: "Letta Claude",
description: "Full Letta Code system prompt (Claude-optimized)",
content: lettaAnthropicPrompt,
isFeatured: true,
},
{
id: "letta-codex",
label: "Codex",
description: "For Codex models",
label: "Letta Codex",
description: "Full Letta Code system prompt (Codex-optimized)",
content: lettaCodexPrompt,
isFeatured: true,
},
{
id: "letta-gemini",
label: "Gemini",
description: "For Gemini models",
label: "Letta Gemini",
description: "Full Letta Code system prompt (Gemini-optimized)",
content: lettaGeminiPrompt,
isFeatured: true,
},
{
id: "anthropic",
id: "claude",
label: "Claude (basic)",
description: "For Claude models (no skills/memory instructions)",
description: "Basic Claude prompt (no skills/memory instructions)",
content: anthropicPrompt,
},
{
id: "codex",
label: "Codex (basic)",
description: "For Codex models (no skills/memory instructions)",
description: "Basic Codex prompt (no skills/memory instructions)",
content: codexPrompt,
},
{
id: "gemini",
label: "Gemini (basic)",
description: "For Gemini models (no skills/memory instructions)",
description: "Basic Gemini prompt (no skills/memory instructions)",
content: geminiPrompt,
},
];
@@ -109,19 +110,19 @@ export const SYSTEM_PROMPTS: SystemPromptOption[] = [
* 2. If it matches a subagent name, use that subagent's system prompt
* 3. Otherwise, use the default system prompt
*
* @param systemPromptId - The system prompt ID (e.g., "codex") or subagent name (e.g., "explore")
* @param systemPromptPreset - The system prompt preset (e.g., "letta-claude") or subagent name (e.g., "explore")
* @returns The resolved system prompt content
*/
export async function resolveSystemPrompt(
systemPromptId: string | undefined,
systemPromptPreset: string | undefined,
): Promise<string> {
// No input - use default
if (!systemPromptId) {
if (!systemPromptPreset) {
return SYSTEM_PROMPT;
}
// 1. Check if it matches a system prompt ID
const matchedPrompt = SYSTEM_PROMPTS.find((p) => p.id === systemPromptId);
const matchedPrompt = SYSTEM_PROMPTS.find((p) => p.id === systemPromptPreset);
if (matchedPrompt) {
return matchedPrompt.content;
}
@@ -129,7 +130,7 @@ export async function resolveSystemPrompt(
// 2. Check if it matches a subagent name
const { getAllSubagentConfigs } = await import("./subagents");
const subagentConfigs = await getAllSubagentConfigs();
const matchedSubagent = subagentConfigs[systemPromptId];
const matchedSubagent = subagentConfigs[systemPromptPreset];
if (matchedSubagent) {
return matchedSubagent.systemPrompt;
}

View File

@@ -48,7 +48,7 @@ import type {
RetryMessage,
StreamEvent,
SystemInitMessage,
} from "./types/wire";
} from "./types/protocol";
// Maximum number of times to retry a turn when the backend
// reports an `llm_api_error` stop reason. This helps smooth
@@ -74,6 +74,10 @@ export async function handleHeadlessCommand(
agent: { type: "string", short: "a" },
model: { type: "string", short: "m" },
system: { type: "string", short: "s" },
"system-custom": { type: "string" },
"system-append": { type: "string" },
"memory-blocks": { type: "string" },
"block-value": { type: "string", multiple: true },
toolset: { type: "string" },
prompt: { type: "boolean", short: "p" },
"output-format": { type: "string" },
@@ -169,7 +173,11 @@ export async function handleHeadlessCommand(
const specifiedAgentId = values.agent as string | undefined;
const shouldContinue = values.continue as boolean | undefined;
const forceNew = values.new as boolean | undefined;
const systemPromptId = values.system as string | undefined;
const systemPromptPreset = values.system as string | undefined;
const systemCustom = values["system-custom"] as string | undefined;
const systemAppend = values["system-append"] as string | undefined;
const memoryBlocksJson = values["memory-blocks"] as string | undefined;
const blockValueArgs = values["block-value"] as string[] | undefined;
const initBlocksRaw = values["init-blocks"] as string | undefined;
const baseToolsRaw = values["base-tools"] as string | undefined;
const sleeptimeFlag = (values.sleeptime as boolean | undefined) ?? undefined;
@@ -231,6 +239,84 @@ export async function handleHeadlessCommand(
}
}
// Validate system prompt options (--system and --system-custom are mutually exclusive)
if (systemPromptPreset && systemCustom) {
console.error(
"Error: --system and --system-custom are mutually exclusive. Use one or the other.",
);
process.exit(1);
}
// Parse memory blocks JSON if provided
// Supports two formats:
// - CreateBlock: { label: string, value: string, description?: string }
// - BlockReference: { blockId: string }
let memoryBlocks:
| Array<
| { label: string; value: string; description?: string }
| { blockId: string }
>
| undefined;
if (memoryBlocksJson !== undefined) {
if (!forceNew) {
console.error(
"Error: --memory-blocks can only be used together with --new to provide initial memory blocks.",
);
process.exit(1);
}
try {
memoryBlocks = JSON.parse(memoryBlocksJson);
if (!Array.isArray(memoryBlocks)) {
throw new Error("memory-blocks must be a JSON array");
}
// Validate each block has required fields
for (const block of memoryBlocks) {
const hasBlockId =
"blockId" in block && typeof block.blockId === "string";
const hasLabelValue =
"label" in block &&
"value" in block &&
typeof block.label === "string" &&
typeof block.value === "string";
if (!hasBlockId && !hasLabelValue) {
throw new Error(
"Each memory block must have either 'blockId' (string) or 'label' and 'value' (strings)",
);
}
}
} catch (error) {
console.error(
`Error: Invalid --memory-blocks JSON: ${error instanceof Error ? error.message : String(error)}`,
);
process.exit(1);
}
}
// Parse --block-value args (format: label=value)
let blockValues: Record<string, string> | undefined;
if (blockValueArgs && blockValueArgs.length > 0) {
if (!forceNew) {
console.error(
"Error: --block-value can only be used together with --new to set block values.",
);
process.exit(1);
}
blockValues = {};
for (const arg of blockValueArgs) {
const eqIndex = arg.indexOf("=");
if (eqIndex === -1) {
console.error(
`Error: Invalid --block-value format "${arg}". Expected format: label=value`,
);
process.exit(1);
}
const label = arg.slice(0, eqIndex);
const value = arg.slice(eqIndex + 1);
blockValues[label] = value;
}
}
// Priority 1: Import from AgentFile template
if (fromAfFile) {
const { importAgentFromFile } = await import("./agent/import");
@@ -254,36 +340,28 @@ export async function handleHeadlessCommand(
// Priority 3: Check if --new flag was passed (skip all resume logic)
if (!agent && forceNew) {
const updateArgs = getModelUpdateArgs(model);
try {
const result = await createAgent(
undefined,
const createOptions = {
model,
undefined,
updateArgs,
skillsDirectory,
true, // parallelToolCalls always enabled
sleeptimeFlag ?? settings.enableSleeptime,
systemPromptId,
parallelToolCalls: true,
enableSleeptime: sleeptimeFlag ?? settings.enableSleeptime,
systemPromptPreset,
systemPromptCustom: systemCustom,
systemPromptAppend: systemAppend,
initBlocks,
baseTools,
);
memoryBlocks,
blockValues,
};
try {
const result = await createAgent(createOptions);
agent = result.agent;
} catch (err) {
if (isToolsNotFoundError(err)) {
console.warn("Tools missing on server, re-uploading and retrying...");
await forceUpsertTools(client, baseURL);
const result = await createAgent(
undefined,
model,
undefined,
updateArgs,
skillsDirectory,
true,
sleeptimeFlag ?? settings.enableSleeptime,
systemPromptId,
initBlocks,
baseTools,
);
const result = await createAgent(createOptions);
agent = result.agent;
} else {
throw err;
@@ -320,36 +398,23 @@ export async function handleHeadlessCommand(
// Priority 6: Create a new agent
if (!agent) {
const updateArgs = getModelUpdateArgs(model);
try {
const result = await createAgent(
undefined,
const createOptions = {
model,
undefined,
updateArgs,
skillsDirectory,
true, // parallelToolCalls always enabled
sleeptimeFlag ?? settings.enableSleeptime,
systemPromptId,
undefined,
undefined,
);
parallelToolCalls: true,
enableSleeptime: sleeptimeFlag ?? settings.enableSleeptime,
systemPromptPreset,
// Note: systemCustom, systemAppend, and memoryBlocks only apply with --new flag
};
try {
const result = await createAgent(createOptions);
agent = result.agent;
} catch (err) {
if (isToolsNotFoundError(err)) {
console.warn("Tools missing on server, re-uploading and retrying...");
await forceUpsertTools(client, baseURL);
const result = await createAgent(
undefined,
model,
undefined,
updateArgs,
skillsDirectory,
true,
sleeptimeFlag ?? settings.enableSleeptime,
systemPromptId,
undefined,
undefined,
);
const result = await createAgent(createOptions);
agent = result.agent;
} else {
throw err;
@@ -365,7 +430,7 @@ export async function handleHeadlessCommand(
);
// If resuming and a model or system prompt was specified, apply those changes
if (isResumingAgent && (model || systemPromptId)) {
if (isResumingAgent && (model || systemPromptPreset)) {
if (model) {
const { resolveModel } = await import("./agent/model");
const modelHandle = resolveModel(model);
@@ -388,9 +453,12 @@ export async function handleHeadlessCommand(
}
}
if (systemPromptId) {
if (systemPromptPreset) {
const { updateAgentSystemPrompt } = await import("./agent/modify");
const result = await updateAgentSystemPrompt(agent.id, systemPromptId);
const result = await updateAgentSystemPrompt(
agent.id,
systemPromptPreset,
);
if (!result.success || !result.agent) {
console.error(`Failed to update system prompt: ${result.message}`);
process.exit(1);

View File

@@ -332,6 +332,10 @@ async function main(): Promise<void> {
name: { type: "string", short: "n" },
model: { type: "string", short: "m" },
system: { type: "string", short: "s" },
"system-custom": { type: "string" },
"system-append": { type: "string" },
"memory-blocks": { type: "string" },
"block-value": { type: "string", multiple: true },
toolset: { type: "string" },
prompt: { type: "boolean", short: "p" },
run: { type: "boolean" },
@@ -406,7 +410,12 @@ async function main(): Promise<void> {
let specifiedAgentId = (values.agent as string | undefined) ?? null;
const specifiedAgentName = (values.name as string | undefined) ?? null;
const specifiedModel = (values.model as string | undefined) ?? undefined;
const systemPromptId = (values.system as string | undefined) ?? undefined;
const systemPromptPreset = (values.system as string | undefined) ?? undefined;
const systemCustom =
(values["system-custom"] as string | undefined) ?? undefined;
// Note: systemAppend is also parsed but only used in headless mode (headless.ts handles it)
const memoryBlocksJson =
(values["memory-blocks"] as string | undefined) ?? undefined;
const specifiedToolset = (values.toolset as string | undefined) ?? undefined;
const skillsDirectory = (values.skills as string | undefined) ?? undefined;
const sleeptimeFlag = (values.sleeptime as boolean | undefined) ?? undefined;
@@ -476,8 +485,16 @@ async function main(): Promise<void> {
process.exit(1);
}
// Validate system prompt if provided (can be a system prompt ID or subagent name)
if (systemPromptId) {
// Validate system prompt options (--system and --system-custom are mutually exclusive)
if (systemPromptPreset && systemCustom) {
console.error(
"Error: --system and --system-custom are mutually exclusive. Use one or the other.",
);
process.exit(1);
}
// Validate system prompt preset if provided (can be a system prompt ID or subagent name)
if (systemPromptPreset) {
const { SYSTEM_PROMPTS } = await import("./agent/promptAssets");
const { getAllSubagentConfigs } = await import("./agent/subagents");
@@ -485,13 +502,42 @@ async function main(): Promise<void> {
const subagentConfigs = await getAllSubagentConfigs();
const validSubagentNames = Object.keys(subagentConfigs);
const isValidSystemPrompt = validSystemPrompts.includes(systemPromptId);
const isValidSubagent = validSubagentNames.includes(systemPromptId);
const isValidSystemPrompt = validSystemPrompts.includes(systemPromptPreset);
const isValidSubagent = validSubagentNames.includes(systemPromptPreset);
if (!isValidSystemPrompt && !isValidSubagent) {
const allValid = [...validSystemPrompts, ...validSubagentNames];
console.error(
`Error: Invalid system prompt "${systemPromptId}". Must be one of: ${allValid.join(", ")}.`,
`Error: Invalid system prompt "${systemPromptPreset}". Must be one of: ${allValid.join(", ")}.`,
);
process.exit(1);
}
}
// Parse memory blocks JSON if provided
let memoryBlocks:
| Array<{ label: string; value: string; description?: string }>
| undefined;
if (memoryBlocksJson) {
try {
memoryBlocks = JSON.parse(memoryBlocksJson);
if (!Array.isArray(memoryBlocks)) {
throw new Error("memory-blocks must be a JSON array");
}
// Validate each block has required fields
for (const block of memoryBlocks) {
if (
typeof block.label !== "string" ||
typeof block.value !== "string"
) {
throw new Error(
"Each memory block must have 'label' and 'value' string fields",
);
}
}
} catch (error) {
console.error(
`Error: Invalid --memory-blocks JSON: ${error instanceof Error ? error.message : String(error)}`,
);
process.exit(1);
}
@@ -745,7 +791,7 @@ async function main(): Promise<void> {
baseTools,
agentIdArg,
model,
systemPromptId,
systemPromptPreset,
toolset,
skillsDirectory,
fromAfFile,
@@ -756,7 +802,7 @@ async function main(): Promise<void> {
baseTools?: string[];
agentIdArg: string | null;
model?: string;
systemPromptId?: string;
systemPromptPreset?: string;
toolset?: "codex" | "default" | "gemini";
skillsDirectory?: string;
fromAfFile?: string;
@@ -1027,13 +1073,13 @@ async function main(): Promise<void> {
agent = await client.agents.retrieve(agentIdArg);
// Apply --system flag to existing agent if provided
if (systemPromptId) {
if (systemPromptPreset) {
const { updateAgentSystemPrompt } = await import(
"./agent/modify"
);
const result = await updateAgentSystemPrompt(
agent.id,
systemPromptId,
systemPromptPreset,
);
if (!result.success || !result.agent) {
console.error(
@@ -1067,7 +1113,7 @@ async function main(): Promise<void> {
skillsDirectory,
true, // parallelToolCalls always enabled
sleeptimeFlag ?? settings.enableSleeptime,
systemPromptId,
systemPromptPreset,
initBlocks,
baseTools,
);
@@ -1089,7 +1135,7 @@ async function main(): Promise<void> {
skillsDirectory,
true,
sleeptimeFlag ?? settings.enableSleeptime,
systemPromptId,
systemPromptPreset,
initBlocks,
baseTools,
);
@@ -1144,7 +1190,7 @@ async function main(): Promise<void> {
skillsDirectory,
true, // parallelToolCalls always enabled
sleeptimeFlag ?? settings.enableSleeptime,
systemPromptId,
systemPromptPreset,
undefined,
undefined,
);
@@ -1166,7 +1212,7 @@ async function main(): Promise<void> {
skillsDirectory,
true,
sleeptimeFlag ?? settings.enableSleeptime,
systemPromptId,
systemPromptPreset,
undefined,
undefined,
);
@@ -1247,7 +1293,7 @@ async function main(): Promise<void> {
setIsResumingSession(resuming);
// If resuming and a model or system prompt was specified, apply those changes
if (resuming && (model || systemPromptId)) {
if (resuming && (model || systemPromptPreset)) {
if (model) {
const { resolveModel } = await import("./agent/model");
const modelHandle = resolveModel(model);
@@ -1271,11 +1317,11 @@ async function main(): Promise<void> {
}
}
if (systemPromptId) {
if (systemPromptPreset) {
const { updateAgentSystemPrompt } = await import("./agent/modify");
const result = await updateAgentSystemPrompt(
agent.id,
systemPromptId,
systemPromptPreset,
);
if (!result.success || !result.agent) {
console.error(`Error: ${result.message}`);
@@ -1312,7 +1358,7 @@ async function main(): Promise<void> {
forceNew,
agentIdArg,
model,
systemPromptId,
systemPromptPreset,
fromAfFile,
loadingState,
selectedGlobalAgentId,
@@ -1384,7 +1430,7 @@ async function main(): Promise<void> {
baseTools: baseTools,
agentIdArg: specifiedAgentId,
model: specifiedModel,
systemPromptId: systemPromptId,
systemPromptPreset: systemPromptPreset,
toolset: specifiedToolset as "codex" | "default" | "gemini" | undefined,
skillsDirectory: skillsDirectory,
fromAfFile: fromAfFile,

View File

@@ -7,7 +7,7 @@ import type {
StreamEvent,
SystemInitMessage,
WireMessage,
} from "../types/wire";
} from "../types/protocol";
/**
* Tests for --input-format stream-json bidirectional communication.

View File

@@ -4,7 +4,7 @@ import type {
ResultMessage,
StreamEvent,
SystemInitMessage,
} from "../types/wire";
} from "../types/protocol";
/**
* Tests for stream-json output format.

View File

@@ -1,9 +1,9 @@
/**
* Wire Format Types
* Protocol Types for Letta Code
*
* These types define the JSON structure emitted by headless.ts when running
* in stream-json mode. They enable typed consumption of the bidirectional
* JSON protocol.
* These types define:
* 1. The JSON structure emitted by headless.ts in stream-json mode (wire protocol)
* 2. Configuration types for session options (used internally and by SDK)
*
* Design principle: Compose from @letta-ai/letta-client types where possible.
*/
@@ -16,6 +16,7 @@ import type {
ToolCallMessage as LettaToolCallMessage,
ToolCall,
} from "@letta-ai/letta-client/resources/agents/messages";
import type { CreateBlock } from "@letta-ai/letta-client/resources/blocks/blocks";
import type { StopReasonType } from "@letta-ai/letta-client/resources/runs/runs";
import type { ToolReturnMessage as LettaToolReturnMessage } from "@letta-ai/letta-client/resources/tools";
@@ -26,8 +27,42 @@ export type {
StopReasonType,
MessageCreate,
LettaToolReturnMessage,
CreateBlock,
};
// ═══════════════════════════════════════════════════════════════
// CONFIGURATION TYPES (session options)
// Used internally by headless.ts/App.tsx, also exported for SDK
// ═══════════════════════════════════════════════════════════════
/**
* System prompt preset configuration.
* Use this to select a built-in system prompt with optional appended text.
*
* Available presets (validated at runtime by CLI):
* - 'default' - Alias for letta-claude
* - 'letta-claude' - Full Letta Code prompt (Claude-optimized)
* - 'letta-codex' - Full Letta Code prompt (Codex-optimized)
* - 'letta-gemini' - Full Letta Code prompt (Gemini-optimized)
* - 'claude' - Basic Claude (no skills/memory instructions)
* - 'codex' - Basic Codex (no skills/memory instructions)
* - 'gemini' - Basic Gemini (no skills/memory instructions)
*/
export interface SystemPromptPresetConfig {
type: "preset";
/** Preset ID (e.g., 'default', 'letta-codex'). Validated at runtime. */
preset: string;
/** Additional instructions to append to the preset */
append?: string;
}
/**
* System prompt configuration - either a raw string or preset config.
* - string: Use as the complete system prompt
* - SystemPromptPresetConfig: Use a preset, optionally with appended text
*/
export type SystemPromptConfig = string | SystemPromptPresetConfig;
// ═══════════════════════════════════════════════════════════════
// BASE ENVELOPE
// All wire messages include these fields

View File

@@ -7,5 +7,5 @@
"declarationMap": true,
"outDir": "./dist/types"
},
"include": ["src/types/wire.ts"]
"include": ["src/types/protocol.ts"]
}