feat: revert default startup to use "default" conversation (#594)
Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
@@ -1456,22 +1456,33 @@ export default function App({
|
||||
? `Resuming conversation with **${agentName}**`
|
||||
: `Starting new conversation with **${agentName}**`;
|
||||
|
||||
// Command hints - for pinned agents show /memory, for unpinned show /pin
|
||||
const commandHints = isPinned
|
||||
// Command hints - vary based on agent state:
|
||||
// - Resuming: show /new (they may want a fresh conversation)
|
||||
// - New session + unpinned: show /pin (they should save their agent)
|
||||
// - New session + pinned: show /memory (they're already saved)
|
||||
const commandHints = isResumingConversation
|
||||
? [
|
||||
"→ **/agents** list all agents",
|
||||
"→ **/resume** resume a previous conversation",
|
||||
"→ **/memory** view your agent's memory blocks",
|
||||
"→ **/resume** browse all conversations",
|
||||
"→ **/new** start a new conversation",
|
||||
"→ **/init** initialize your agent's memory",
|
||||
"→ **/remember** teach your agent",
|
||||
]
|
||||
: [
|
||||
"→ **/agents** list all agents",
|
||||
"→ **/resume** resume a previous conversation",
|
||||
"→ **/pin** save + name your agent",
|
||||
"→ **/init** initialize your agent's memory",
|
||||
"→ **/remember** teach your agent",
|
||||
];
|
||||
: isPinned
|
||||
? [
|
||||
"→ **/agents** list all agents",
|
||||
"→ **/resume** resume a previous conversation",
|
||||
"→ **/memory** view your agent's memory blocks",
|
||||
"→ **/init** initialize your agent's memory",
|
||||
"→ **/remember** teach your agent",
|
||||
]
|
||||
: [
|
||||
"→ **/agents** list all agents",
|
||||
"→ **/resume** resume a previous conversation",
|
||||
"→ **/pin** save + name your agent",
|
||||
"→ **/init** initialize your agent's memory",
|
||||
"→ **/remember** teach your agent",
|
||||
];
|
||||
|
||||
// Build status lines with optional release notes above header
|
||||
const statusLines: string[] = [];
|
||||
@@ -3185,13 +3196,9 @@ export default function App({
|
||||
// Fetch new agent
|
||||
const agent = await client.agents.retrieve(targetAgentId);
|
||||
|
||||
// Always create a new conversation when switching agents
|
||||
// User can /resume to get back to a previous conversation if needed
|
||||
const newConversation = await client.conversations.create({
|
||||
agent_id: targetAgentId,
|
||||
isolated_block_labels: [...ISOLATED_BLOCK_LABELS],
|
||||
});
|
||||
const targetConversationId = newConversation.id;
|
||||
// Use the agent's default conversation when switching agents
|
||||
// User can /new to start a fresh conversation if needed
|
||||
const targetConversationId = "default";
|
||||
|
||||
// Update project settings with new agent
|
||||
await updateProjectSettings({ lastAgent: targetAgentId });
|
||||
@@ -3225,11 +3232,12 @@ export default function App({
|
||||
setLlmConfig(agent.llm_config);
|
||||
setConversationId(targetConversationId);
|
||||
|
||||
// Build success message - always a new conversation
|
||||
// Build success message - resumed default conversation
|
||||
const agentLabel = agent.name || targetAgentId;
|
||||
const successOutput = [
|
||||
`Started a new conversation with **${agentLabel}**.`,
|
||||
`⎿ Type /resume to resume a previous conversation`,
|
||||
`Resumed the default conversation with **${agentLabel}**.`,
|
||||
`⎿ Type /resume to browse all conversations`,
|
||||
`⎿ Type /new to start a new conversation`,
|
||||
].join("\n");
|
||||
const successItem: StaticItem = {
|
||||
kind: "command",
|
||||
@@ -4270,9 +4278,8 @@ export default function App({
|
||||
return { submitted: true };
|
||||
}
|
||||
|
||||
// Special handling for /clear and /new commands - start new conversation
|
||||
// (/new used to create a new agent, now it's just an alias for /clear)
|
||||
if (msg.trim() === "/clear" || msg.trim() === "/new") {
|
||||
// Special handling for /new command - start new conversation
|
||||
if (msg.trim() === "/new") {
|
||||
const cmdId = uid("cmd");
|
||||
buffersRef.current.byId.set(cmdId, {
|
||||
kind: "command",
|
||||
@@ -4339,14 +4346,14 @@ export default function App({
|
||||
return { submitted: true };
|
||||
}
|
||||
|
||||
// Special handling for /clear-messages command - reset all agent messages (destructive)
|
||||
if (msg.trim() === "/clear-messages") {
|
||||
// Special handling for /clear command - reset all agent messages (destructive)
|
||||
if (msg.trim() === "/clear") {
|
||||
const cmdId = uid("cmd");
|
||||
buffersRef.current.byId.set(cmdId, {
|
||||
kind: "command",
|
||||
id: cmdId,
|
||||
input: msg,
|
||||
output: "Resetting agent messages...",
|
||||
output: "Clearing in-context messages...",
|
||||
phase: "running",
|
||||
});
|
||||
buffersRef.current.order.push(cmdId);
|
||||
@@ -7917,11 +7924,16 @@ Plan file path: ${planFilePath}`;
|
||||
? `letta -n "${agentName}"`
|
||||
: `letta --agent ${agentId}`}
|
||||
</Text>
|
||||
<Text> </Text>
|
||||
<Text dimColor>Resume this conversation with:</Text>
|
||||
<Text color={colors.link.url}>
|
||||
{`letta --conv ${conversationId}`}
|
||||
</Text>
|
||||
{/* Only show conversation hint if not on default (default is resumed automatically) */}
|
||||
{conversationId !== "default" && (
|
||||
<>
|
||||
<Text> </Text>
|
||||
<Text dimColor>Resume this conversation with:</Text>
|
||||
<Text color={colors.link.url}>
|
||||
{`letta --conv ${conversationId}`}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
|
||||
@@ -77,29 +77,20 @@ export const commands: Record<string, Command> = {
|
||||
},
|
||||
},
|
||||
"/clear": {
|
||||
desc: "Start a new conversation (keep agent memory)",
|
||||
desc: "Clear in-context messages",
|
||||
order: 18,
|
||||
handler: () => {
|
||||
// Handled specially in App.tsx to create new conversation
|
||||
return "Starting new conversation...";
|
||||
},
|
||||
},
|
||||
"/clear-messages": {
|
||||
desc: "Reset all agent messages (destructive)",
|
||||
order: 19,
|
||||
hidden: true, // Advanced command, not shown in autocomplete
|
||||
handler: () => {
|
||||
// Handled specially in App.tsx to reset agent messages
|
||||
return "Resetting agent messages...";
|
||||
return "Clearing in-context messages...";
|
||||
},
|
||||
},
|
||||
|
||||
// === Page 2: Agent management (order 20-29) ===
|
||||
"/new": {
|
||||
desc: "Start a new conversation (same as /clear)",
|
||||
desc: "Start a new conversation (keep agent memory)",
|
||||
order: 20,
|
||||
handler: () => {
|
||||
// Handled specially in App.tsx - same as /clear
|
||||
// Handled specially in App.tsx to create new conversation
|
||||
return "Starting new conversation...";
|
||||
},
|
||||
},
|
||||
@@ -334,7 +325,6 @@ export const commands: Record<string, Command> = {
|
||||
},
|
||||
"/compact": {
|
||||
desc: "Summarize conversation history (compaction)",
|
||||
hidden: true,
|
||||
handler: () => {
|
||||
// Handled specially in App.tsx to access client and agent ID
|
||||
return "Compacting conversation...";
|
||||
|
||||
@@ -191,14 +191,8 @@ export async function handleHeadlessCommand(
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check for deprecated --new flag
|
||||
if (values.new) {
|
||||
console.error(
|
||||
"Error: --new has been renamed to --new-agent\n" +
|
||||
'Usage: letta -p "..." --new-agent',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
// --new: Create a new conversation (for concurrent sessions)
|
||||
const forceNewConversation = (values.new as boolean | undefined) ?? false;
|
||||
|
||||
// Resolve agent (same logic as interactive mode)
|
||||
let agent: AgentState | null = null;
|
||||
@@ -269,6 +263,18 @@ export async function handleHeadlessCommand(
|
||||
}
|
||||
}
|
||||
|
||||
// Validate --new flag (create new conversation)
|
||||
if (forceNewConversation) {
|
||||
if (shouldContinue) {
|
||||
console.error("Error: --new cannot be used with --continue");
|
||||
process.exit(1);
|
||||
}
|
||||
if (specifiedConversationId) {
|
||||
console.error("Error: --new cannot be used with --conversation");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate --from-af flag
|
||||
if (fromAfFile) {
|
||||
if (specifiedAgentId) {
|
||||
@@ -617,30 +623,35 @@ export async function handleHeadlessCommand(
|
||||
await client.conversations.retrieve(lastSession.conversationId);
|
||||
conversationId = lastSession.conversationId;
|
||||
} catch {
|
||||
// Conversation no longer exists, create new
|
||||
const conversation = await client.conversations.create({
|
||||
agent_id: agent.id,
|
||||
isolated_block_labels: isolatedBlockLabels,
|
||||
});
|
||||
conversationId = conversation.id;
|
||||
// Conversation no longer exists - error with helpful message
|
||||
console.error(
|
||||
`Attempting to resume conversation ${lastSession.conversationId}, but conversation was not found.`,
|
||||
);
|
||||
console.error(
|
||||
"Resume the default conversation with 'letta -p ...', view recent conversations with 'letta --resume', or start a new conversation with 'letta -p ... --new'.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No matching session, create new conversation
|
||||
const conversation = await client.conversations.create({
|
||||
agent_id: agent.id,
|
||||
isolated_block_labels: isolatedBlockLabels,
|
||||
});
|
||||
conversationId = conversation.id;
|
||||
// No matching session - error with helpful message
|
||||
console.error("No previous session found for this agent to resume.");
|
||||
console.error(
|
||||
"Resume the default conversation with 'letta -p ...', or start a new conversation with 'letta -p ... --new'.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
// Default: create a new conversation
|
||||
// This ensures isolated message history per CLI invocation
|
||||
} else if (forceNewConversation || forceNew) {
|
||||
// --new flag (new conversation) or --new-agent (new agent): create a new conversation
|
||||
// When creating a new agent, always create a new conversation alongside it
|
||||
const conversation = await client.conversations.create({
|
||||
agent_id: agent.id,
|
||||
isolated_block_labels: isolatedBlockLabels,
|
||||
});
|
||||
conversationId = conversation.id;
|
||||
} else {
|
||||
// Default: use the agent's "default" conversation (OG single-threaded behavior)
|
||||
conversationId = "default";
|
||||
}
|
||||
markMilestone("HEADLESS_CONVERSATION_READY");
|
||||
|
||||
|
||||
68
src/index.ts
68
src/index.ts
@@ -63,10 +63,11 @@ Letta Code is a general purpose CLI for interacting with Letta agents
|
||||
|
||||
USAGE
|
||||
# interactive TUI
|
||||
letta Resume from profile or create new agent (shows selector)
|
||||
letta Resume default conversation (OG single-threaded experience)
|
||||
letta --new Create a new conversation (for concurrent sessions)
|
||||
letta --continue Resume last session (agent + conversation) directly
|
||||
letta --resume Open agent selector UI to pick agent/conversation
|
||||
letta --new Create a new agent directly (skip profile selector)
|
||||
letta --new-agent Create a new agent directly (skip profile selector)
|
||||
letta --agent <id> Open a specific agent by ID
|
||||
|
||||
# headless
|
||||
@@ -81,9 +82,10 @@ OPTIONS
|
||||
--info Show current directory, skills, and pinned agents
|
||||
--continue Resume last session (agent + conversation) directly
|
||||
-r, --resume Open agent selector UI after loading
|
||||
--new Create new agent directly (skip profile selection)
|
||||
--init-blocks <list> Comma-separated memory blocks to initialize when using --new (e.g., "persona,skills")
|
||||
--base-tools <list> Comma-separated base tools to attach when using --new (e.g., "memory,web_search,conversation_search")
|
||||
--new Create new conversation (for concurrent sessions)
|
||||
--new-agent Create new agent directly (skip profile selection)
|
||||
--init-blocks <list> Comma-separated memory blocks to initialize when using --new-agent (e.g., "persona,skills")
|
||||
--base-tools <list> Comma-separated base tools to attach when using --new-agent (e.g., "memory,web_search,conversation_search")
|
||||
-a, --agent <id> Use a specific agent ID
|
||||
-n, --name <name> Resume agent by name (from pinned agents, case-insensitive)
|
||||
-m, --model <id> Model ID or handle (e.g., "opus-4.5" or "anthropic/claude-opus-4-5")
|
||||
@@ -489,14 +491,8 @@ async function main(): Promise<void> {
|
||||
specifiedConversationId = "default";
|
||||
}
|
||||
|
||||
// Check for deprecated --new flag
|
||||
if (values.new) {
|
||||
console.error(
|
||||
"Error: --new has been renamed to --new-agent\n" +
|
||||
"Usage: letta --new-agent",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
// --new: Create a new conversation (for concurrent sessions)
|
||||
const forceNewConversation = (values.new as boolean | undefined) ?? false;
|
||||
|
||||
const initBlocksRaw = values["init-blocks"] as string | undefined;
|
||||
const baseToolsRaw = values["base-tools"] as string | undefined;
|
||||
@@ -686,6 +682,22 @@ async function main(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// Validate --new flag (create new conversation)
|
||||
if (forceNewConversation) {
|
||||
if (shouldContinue) {
|
||||
console.error("Error: --new cannot be used with --continue");
|
||||
process.exit(1);
|
||||
}
|
||||
if (specifiedConversationId) {
|
||||
console.error("Error: --new cannot be used with --conversation");
|
||||
process.exit(1);
|
||||
}
|
||||
if (shouldResume) {
|
||||
console.error("Error: --new cannot be used with --resume");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate --from-af flag
|
||||
if (fromAfFile) {
|
||||
if (specifiedAgentId) {
|
||||
@@ -1687,12 +1699,14 @@ async function main(): Promise<void> {
|
||||
}
|
||||
|
||||
if (!resumedSuccessfully) {
|
||||
// No valid session to resume for this agent, or it failed - create new
|
||||
const conversation = await client.conversations.create({
|
||||
agent_id: agent.id,
|
||||
isolated_block_labels: [...ISOLATED_BLOCK_LABELS],
|
||||
});
|
||||
conversationIdToUse = conversation.id;
|
||||
// No valid session to resume - error with helpful message
|
||||
console.error(
|
||||
`Attempting to resume conversation ${lastSession?.conversationId ?? "(unknown)"}, but conversation was not found.`,
|
||||
);
|
||||
console.error(
|
||||
"Resume the default conversation with 'letta', view recent conversations with 'letta --resume', or start a new conversation with 'letta --new'.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
} else if (selectedConversationId) {
|
||||
// User selected a specific conversation from the --resume selector
|
||||
@@ -1717,14 +1731,24 @@ async function main(): Promise<void> {
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
// Default: create a new conversation on startup
|
||||
// This ensures each CLI session has isolated message history
|
||||
} else if (forceNewConversation || forceNew) {
|
||||
// --new flag (new conversation) or --new-agent (new agent): create a new conversation
|
||||
// When creating a new agent, always create a new conversation alongside it
|
||||
const conversation = await client.conversations.create({
|
||||
agent_id: agent.id,
|
||||
isolated_block_labels: [...ISOLATED_BLOCK_LABELS],
|
||||
});
|
||||
conversationIdToUse = conversation.id;
|
||||
} else {
|
||||
// Default: use the agent's "default" conversation (OG single-threaded behavior)
|
||||
conversationIdToUse = "default";
|
||||
|
||||
// Load message history from the default conversation
|
||||
setLoadingState("checking");
|
||||
const freshAgent = await client.agents.retrieve(agent.id);
|
||||
const data = await getResumeData(client, freshAgent, "default");
|
||||
setResumeData(data);
|
||||
setResumedExistingConversation(true);
|
||||
}
|
||||
|
||||
// Save the session (agent + conversation) to settings
|
||||
|
||||
@@ -17,6 +17,10 @@ export const releaseNotes: Record<string, string> = {
|
||||
// Add release notes for new versions here.
|
||||
// Keep concise - 3-4 bullet points max.
|
||||
// Use → for bullets to match the command hints below.
|
||||
"0.13.4": `🔄 **Letta Code 0.13.4: Back to the OG experience**
|
||||
→ Running **letta** now resumes your "default" conversation (instead of spawning a new one)
|
||||
→ Use **letta --new** if you want to create a new conversation for concurrent sessions
|
||||
→ Read more: https://docs.letta.com/letta-code/changelog#0134`,
|
||||
"0.13.0": `🎁 **Letta Code 0.13.0: Introducing Conversations!**
|
||||
→ Letta Code now starts a new conversation on each startup (memory is shared across all conversations)
|
||||
→ Use **/resume** to switch conversations, or run **letta --continue** to continue an existing conversation
|
||||
|
||||
@@ -46,7 +46,8 @@ async function runBidirectional(
|
||||
],
|
||||
{
|
||||
cwd: process.cwd(),
|
||||
env: { ...process.env },
|
||||
// Mark as subagent to prevent polluting user's LRU settings
|
||||
env: { ...process.env, LETTA_CODE_AGENT_ROLE: "subagent" },
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -76,7 +76,12 @@ async function runCLI(
|
||||
"-m",
|
||||
model,
|
||||
];
|
||||
const proc = Bun.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
||||
// Mark as subagent to prevent polluting user's LRU settings
|
||||
const proc = Bun.spawn(cmd, {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
env: { ...process.env, LETTA_CODE_AGENT_ROLE: "subagent" },
|
||||
});
|
||||
const out = await new Response(proc.stdout).text();
|
||||
const err = await new Response(proc.stderr).text();
|
||||
const code = await proc.exited;
|
||||
|
||||
@@ -34,7 +34,8 @@ async function runHeadlessCommand(
|
||||
],
|
||||
{
|
||||
cwd: process.cwd(),
|
||||
env: { ...process.env },
|
||||
// Mark as subagent to prevent polluting user's LRU settings
|
||||
env: { ...process.env, LETTA_CODE_AGENT_ROLE: "subagent" },
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -67,7 +67,12 @@ async function runCLI(
|
||||
"-m",
|
||||
model,
|
||||
];
|
||||
const proc = Bun.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
||||
// Mark as subagent to prevent polluting user's LRU settings
|
||||
const proc = Bun.spawn(cmd, {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
env: { ...process.env, LETTA_CODE_AGENT_ROLE: "subagent" },
|
||||
});
|
||||
const out = await new Response(proc.stdout).text();
|
||||
const err = await new Response(proc.stderr).text();
|
||||
const code = await proc.exited;
|
||||
|
||||
@@ -64,7 +64,8 @@ async function runLazyRecoveryTest(timeoutMs = 180000): Promise<{
|
||||
],
|
||||
{
|
||||
cwd: process.cwd(),
|
||||
env: { ...process.env },
|
||||
// Mark as subagent to prevent polluting user's LRU settings
|
||||
env: { ...process.env, LETTA_CODE_AGENT_ROLE: "subagent" },
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -30,7 +30,8 @@ async function runCli(
|
||||
return new Promise((resolve, reject) => {
|
||||
const proc = spawn("bun", ["run", "dev", ...args], {
|
||||
cwd: projectRoot,
|
||||
env: { ...process.env },
|
||||
// Mark as subagent to prevent polluting user's LRU settings
|
||||
env: { ...process.env, LETTA_CODE_AGENT_ROLE: "subagent" },
|
||||
});
|
||||
|
||||
let stdout = "";
|
||||
|
||||
Reference in New Issue
Block a user