From db33f942b37b04895957464ace7bb78c89dbed73 Mon Sep 17 00:00:00 2001 From: Charles Packer Date: Wed, 14 Jan 2026 18:12:37 -0800 Subject: [PATCH] fix: isolate skills blocks per conversation (#545) Co-authored-by: Letta --- bun.lock | 4 ++-- package.json | 2 +- src/agent/memory.ts | 7 +++++++ src/cli/App.tsx | 5 +++++ src/headless.ts | 2 ++ src/index.ts | 3 +++ 6 files changed, 20 insertions(+), 3 deletions(-) diff --git a/bun.lock b/bun.lock index 47a26fe..f30a062 100644 --- a/bun.lock +++ b/bun.lock @@ -4,7 +4,7 @@ "": { "name": "@letta-ai/letta-code", "dependencies": { - "@letta-ai/letta-client": "1.6.5", + "@letta-ai/letta-client": "1.6.6", "glob": "^13.0.0", "ink-link": "^5.0.0", "open": "^10.2.0", @@ -36,7 +36,7 @@ "@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="], - "@letta-ai/letta-client": ["@letta-ai/letta-client@1.6.5", "", {}, "sha512-LrJz7xqqYXaoAC6XXUZ/K+YgrCLzoEIRwPCXoOsiN/ehl7hl9kiz1Mw/iYSCK7ZpXS6fnyaIbIwGfxWeCl8z5g=="], + "@letta-ai/letta-client": ["@letta-ai/letta-client@1.6.6", "", {}, "sha512-kD0x5SvUrSSLLG3aF0wcXp6iA52UxyEXPA+kr9LV7PLhLwys5pYC00QWflvHmPB5MPnnmYA9oBaEbP3SHIEv/w=="], "@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="], diff --git a/package.json b/package.json index e6d174d..403799a 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "access": "public" }, "dependencies": { - "@letta-ai/letta-client": "1.6.5", + "@letta-ai/letta-client": "1.6.6", "glob": "^13.0.0", "ink-link": "^5.0.0", "open": "^10.2.0" diff --git a/src/agent/memory.ts b/src/agent/memory.ts index d789e86..56dbe3f 100644 --- a/src/agent/memory.ts +++ b/src/agent/memory.ts @@ -39,6 +39,13 @@ export type MemoryBlockLabel = (typeof MEMORY_BLOCK_LABELS)[number]; */ export const READ_ONLY_BLOCK_LABELS = ["skills", "loaded_skills"] as const; +/** + * Block labels that should be isolated per-conversation. + * When creating a conversation, these blocks are copied from the agent's blocks + * to create conversation-specific versions, preventing cross-conversation state pollution. + */ +export const ISOLATED_BLOCK_LABELS = ["skills", "loaded_skills"] as const; + /** * Check if a block label is a project-level block */ diff --git a/src/cli/App.tsx b/src/cli/App.tsx index 0126b89..47e6307 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -37,6 +37,7 @@ import { getResumeData } from "../agent/check-approval"; import { getClient } from "../agent/client"; import { getCurrentAgentId, setCurrentAgentId } from "../agent/context"; import { type AgentProvenance, createAgent } from "../agent/create"; +import { ISOLATED_BLOCK_LABELS } from "../agent/memory"; import { sendMessageStream } from "../agent/message"; import { getModelDisplayName, getModelInfo } from "../agent/model"; import { SessionStats } from "../agent/stats"; @@ -2893,6 +2894,7 @@ export default function App({ // 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; @@ -3936,6 +3938,7 @@ export default function App({ // Create a new conversation for the current agent const conversation = await client.conversations.create({ agent_id: agentId, + isolated_block_labels: [...ISOLATED_BLOCK_LABELS], }); // Update conversationId state @@ -4008,6 +4011,7 @@ export default function App({ // Also create a new conversation since messages were cleared const conversation = await client.conversations.create({ agent_id: agentId, + isolated_block_labels: [...ISOLATED_BLOCK_LABELS], }); setConversationId(conversation.id); settingsManager.setLocalLastSession( @@ -7554,6 +7558,7 @@ Plan file path: ${planFilePath}`; const client = await getClient(); const conversation = await client.conversations.create({ agent_id: agentId, + isolated_block_labels: [...ISOLATED_BLOCK_LABELS], }); setConversationId(conversation.id); settingsManager.setLocalLastSession( diff --git a/src/headless.ts b/src/headless.ts index 5147a5a..0a614c9 100644 --- a/src/headless.ts +++ b/src/headless.ts @@ -15,6 +15,7 @@ import { import { getClient } from "./agent/client"; import { initializeLoadedSkillsFlag, setAgentContext } from "./agent/context"; import { createAgent } from "./agent/create"; +import { ISOLATED_BLOCK_LABELS } from "./agent/memory"; import { sendMessageStream } from "./agent/message"; import { getModelUpdateArgs } from "./agent/model"; import { SessionStats } from "./agent/stats"; @@ -446,6 +447,7 @@ export async function handleHeadlessCommand( // This ensures isolated message history per CLI invocation const conversation = await client.conversations.create({ agent_id: agent.id, + isolated_block_labels: [...ISOLATED_BLOCK_LABELS], }); const conversationId = conversation.id; diff --git a/src/index.ts b/src/index.ts index 0f0d574..59c5403 100755 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ import { getResumeData, type ResumeData } from "./agent/check-approval"; import { getClient } from "./agent/client"; import { initializeLoadedSkillsFlag, setAgentContext } from "./agent/context"; import type { AgentProvenance } from "./agent/create"; +import { ISOLATED_BLOCK_LABELS } from "./agent/memory"; import { LETTA_CLOUD_API_URL } from "./auth/oauth"; import type { ApprovalRequest } from "./cli/helpers/stream"; import { ProfileSelectionInline } from "./cli/profile-selection"; @@ -1331,6 +1332,7 @@ async function main(): Promise { // No valid session to resume for this agent, create new const conversation = await client.conversations.create({ agent_id: agent.id, + isolated_block_labels: [...ISOLATED_BLOCK_LABELS], }); conversationIdToUse = conversation.id; } @@ -1339,6 +1341,7 @@ async function main(): Promise { // This ensures each CLI session has isolated message history const conversation = await client.conversations.create({ agent_id: agent.id, + isolated_block_labels: [...ISOLATED_BLOCK_LABELS], }); conversationIdToUse = conversation.id; }