diff --git a/src/cli/App.tsx b/src/cli/App.tsx
index 985f983..4907ed0 100644
--- a/src/cli/App.tsx
+++ b/src/cli/App.tsx
@@ -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}`}
-
- Resume this conversation with:
-
- {`letta --conv ${conversationId}`}
-
+ {/* Only show conversation hint if not on default (default is resumed automatically) */}
+ {conversationId !== "default" && (
+ <>
+
+ Resume this conversation with:
+
+ {`letta --conv ${conversationId}`}
+
+ >
+ )}
)}
diff --git a/src/cli/commands/registry.ts b/src/cli/commands/registry.ts
index ac373c4..0dc44fd 100644
--- a/src/cli/commands/registry.ts
+++ b/src/cli/commands/registry.ts
@@ -77,29 +77,20 @@ export const commands: Record = {
},
},
"/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 = {
},
"/compact": {
desc: "Summarize conversation history (compaction)",
- hidden: true,
handler: () => {
// Handled specially in App.tsx to access client and agent ID
return "Compacting conversation...";
diff --git a/src/headless.ts b/src/headless.ts
index 4dcbbf4..7a0a0d4 100644
--- a/src/headless.ts
+++ b/src/headless.ts
@@ -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");
diff --git a/src/index.ts b/src/index.ts
index 3480340..1bd4ef3 100755
--- a/src/index.ts
+++ b/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 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 Comma-separated memory blocks to initialize when using --new (e.g., "persona,skills")
- --base-tools 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 Comma-separated memory blocks to initialize when using --new-agent (e.g., "persona,skills")
+ --base-tools Comma-separated base tools to attach when using --new-agent (e.g., "memory,web_search,conversation_search")
-a, --agent Use a specific agent ID
-n, --name Resume agent by name (from pinned agents, case-insensitive)
-m, --model Model ID or handle (e.g., "opus-4.5" or "anthropic/claude-opus-4-5")
@@ -489,14 +491,8 @@ async function main(): Promise {
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 {
}
}
+ // 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 {
}
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 {
}
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
diff --git a/src/release-notes.ts b/src/release-notes.ts
index dbd3a57..bc572a3 100644
--- a/src/release-notes.ts
+++ b/src/release-notes.ts
@@ -17,6 +17,10 @@ export const releaseNotes: Record = {
// 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
diff --git a/src/tests/headless-input-format.test.ts b/src/tests/headless-input-format.test.ts
index 1148bb2..7c3045e 100644
--- a/src/tests/headless-input-format.test.ts
+++ b/src/tests/headless-input-format.test.ts
@@ -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" },
},
);
diff --git a/src/tests/headless-scenario.ts b/src/tests/headless-scenario.ts
index 7a73e39..437d1be 100644
--- a/src/tests/headless-scenario.ts
+++ b/src/tests/headless-scenario.ts
@@ -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;
diff --git a/src/tests/headless-stream-json-format.test.ts b/src/tests/headless-stream-json-format.test.ts
index 1953bee..231ce70 100644
--- a/src/tests/headless-stream-json-format.test.ts
+++ b/src/tests/headless-stream-json-format.test.ts
@@ -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" },
},
);
diff --git a/src/tests/headless-windows.ts b/src/tests/headless-windows.ts
index 5ed71c5..37d829f 100644
--- a/src/tests/headless-windows.ts
+++ b/src/tests/headless-windows.ts
@@ -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;
diff --git a/src/tests/lazy-approval-recovery.test.ts b/src/tests/lazy-approval-recovery.test.ts
index 3273227..f498ec5 100644
--- a/src/tests/lazy-approval-recovery.test.ts
+++ b/src/tests/lazy-approval-recovery.test.ts
@@ -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" },
},
);
diff --git a/src/tests/startup-flow.test.ts b/src/tests/startup-flow.test.ts
index 5ba9c03..47c2775 100644
--- a/src/tests/startup-flow.test.ts
+++ b/src/tests/startup-flow.test.ts
@@ -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 = "";