diff --git a/src/agent/memoryGit.ts b/src/agent/memoryGit.ts index 1d79028..659468f 100644 --- a/src/agent/memoryGit.ts +++ b/src/agent/memoryGit.ts @@ -433,3 +433,25 @@ export async function addGitMemoryTag(agentId: string): Promise { ); } } + +/** + * Remove the git-memory-enabled tag from an agent. + */ +export async function removeGitMemoryTag(agentId: string): Promise { + const client = await getClient(); + try { + const agent = await client.agents.retrieve(agentId); + const tags = agent.tags || []; + if (tags.includes(GIT_MEMORY_ENABLED_TAG)) { + await client.agents.update(agentId, { + tags: tags.filter((t) => t !== GIT_MEMORY_ENABLED_TAG), + }); + debugLog("memfs-git", `Removed ${GIT_MEMORY_ENABLED_TAG} tag`); + } + } catch (err) { + debugWarn( + "memfs-git", + `Failed to remove git-memory tag: ${err instanceof Error ? err.message : String(err)}`, + ); + } +} diff --git a/src/cli/App.tsx b/src/cli/App.tsx index c449872..9724f21 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -6877,6 +6877,15 @@ export default function App({ } if (subcommand === "enable") { + // memfs requires Letta Cloud (git memfs not supported on self-hosted) + const serverUrl = getServerUrl(); + if (!serverUrl.includes("api.letta.com")) { + cmd.fail( + "Memory filesystem is only available on Letta Cloud (api.letta.com).", + ); + return { submitted: true }; + } + updateMemorySyncCommand( cmdId, "Enabling memory filesystem...", @@ -7041,9 +7050,25 @@ export default function App({ // 3. Update settings settingsManager.setMemfsEnabled(agentId, false); + // 4. Remove git-memory-enabled tag from agent + const { removeGitMemoryTag } = await import("../agent/memoryGit"); + await removeGitMemoryTag(agentId); + + // 5. Move local memory dir to /tmp (backup, not delete) + let backupInfo = ""; + const memoryDir = getMemoryFilesystemRoot(agentId); + if (existsSync(memoryDir)) { + const backupDir = join( + tmpdir(), + `letta-memfs-disable-${agentId}-${Date.now()}`, + ); + renameSync(memoryDir, backupDir); + backupInfo = `\nLocal files backed up to ${backupDir}`; + } + updateMemorySyncCommand( cmdId, - "Memory filesystem disabled. Memory tool re-attached.\nFiles on disk have been kept.", + `Memory filesystem disabled. Memory tool re-attached.${backupInfo}`, true, msg, ); diff --git a/src/headless.ts b/src/headless.ts index f1c60ff..a39deea 100644 --- a/src/headless.ts +++ b/src/headless.ts @@ -14,7 +14,7 @@ import { isApprovalPendingError, isInvalidToolCallIdsError, } from "./agent/approval-recovery"; -import { getClient } from "./agent/client"; +import { getClient, getServerUrl } from "./agent/client"; import { setAgentContext, setConversationId } from "./agent/context"; import { createAgent } from "./agent/create"; import { ISOLATED_BLOCK_LABELS } from "./agent/memory"; @@ -236,7 +236,6 @@ export async function handleHeadlessCommand( // Resolve agent (same logic as interactive mode) let agent: AgentState | null = null; - let isNewlyCreatedAgent = false; let specifiedAgentId = values.agent as string | undefined; let specifiedConversationId = values.conversation as string | undefined; const useDefaultConv = values.default as boolean | undefined; @@ -542,7 +541,6 @@ export async function handleHeadlessCommand( } agent = result.agent; - isNewlyCreatedAgent = true; // Display extracted skills summary if (result.skills && result.skills.length > 0) { @@ -584,7 +582,6 @@ export async function handleHeadlessCommand( }; const result = await createAgent(createOptions); agent = result.agent; - isNewlyCreatedAgent = true; } // Priority 4: Try to resume from project settings (.letta/settings.local.json) @@ -681,27 +678,23 @@ export async function handleHeadlessCommand( const isSubagent = process.env.LETTA_CODE_AGENT_ROLE === "subagent"; - // Apply memfs flag if specified, or enable by default for new agents - // In headless mode, also enable for --agent since users expect full functionality + // Apply memfs flag if explicitly specified (memfs is opt-in via /memfs enable or --memfs) if (memfsFlag) { + // memfs requires Letta Cloud (git memfs not supported on self-hosted) + const serverUrl = getServerUrl(); + if (!serverUrl.includes("api.letta.com")) { + console.error( + "--memfs is only available on Letta Cloud (api.letta.com).", + ); + process.exit(1); + } settingsManager.setMemfsEnabled(agent.id, true); } else if (noMemfsFlag) { settingsManager.setMemfsEnabled(agent.id, false); - } else if (isNewlyCreatedAgent && !isSubagent) { - // Enable memfs by default for newly created agents (but not subagents) - settingsManager.setMemfsEnabled(agent.id, true); - } else if (specifiedAgentId && !isSubagent) { - // Enable memfs by default when using --agent in headless mode - settingsManager.setMemfsEnabled(agent.id, true); } // Ensure agent's system prompt includes/excludes memfs section to match setting - if ( - memfsFlag || - noMemfsFlag || - (isNewlyCreatedAgent && !isSubagent) || - (specifiedAgentId && !isSubagent) - ) { + if (memfsFlag || noMemfsFlag) { const { updateAgentSystemPromptMemfs } = await import("./agent/modify"); await updateAgentSystemPromptMemfs( agent.id, diff --git a/src/index.ts b/src/index.ts index 95a2407..3f9755e 100755 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ import { APIError } from "@letta-ai/letta-client/core/error"; import type { AgentState } from "@letta-ai/letta-client/resources/agents/agents"; import type { Message } from "@letta-ai/letta-client/resources/agents/messages"; import { getResumeData, type ResumeData } from "./agent/check-approval"; -import { getClient } from "./agent/client"; +import { getClient, getServerUrl } from "./agent/client"; import { setAgentContext, setConversationId as setContextConversationId, @@ -1477,7 +1477,6 @@ async function main(): Promise { const { getModelUpdateArgs } = await import("./agent/model"); let agent: AgentState | null = null; - let isNewlyCreatedAgent = false; // Priority 1: Import from AgentFile template (local file or registry) if (fromAfFile) { @@ -1505,7 +1504,6 @@ async function main(): Promise { } agent = result.agent; - isNewlyCreatedAgent = true; setAgentProvenance({ isNew: true, blocks: [], @@ -1608,7 +1606,6 @@ async function main(): Promise { baseTools, ); agent = result.agent; - isNewlyCreatedAgent = true; setAgentProvenance(result.provenance); } @@ -1686,28 +1683,30 @@ async function main(): Promise { // Set agent context for tools that need it (e.g., Skill tool) setAgentContext(agent.id, skillsDirectory); - // Apply memfs flag if specified, or enable by default for new agents + // Apply memfs flag if explicitly specified (memfs is opt-in via /memfs enable or --memfs) const isSubagent = process.env.LETTA_CODE_AGENT_ROLE === "subagent"; if (memfsFlag) { + // memfs requires Letta Cloud (git memfs not supported on self-hosted) + const serverUrl = getServerUrl(); + if (!serverUrl.includes("api.letta.com")) { + console.error( + "--memfs is only available on Letta Cloud (api.letta.com).", + ); + process.exit(1); + } settingsManager.setMemfsEnabled(agent.id, true); } else if (noMemfsFlag) { settingsManager.setMemfsEnabled(agent.id, false); - } else if (isNewlyCreatedAgent && !isSubagent) { - // Enable memfs by default for newly created agents (but not subagents) - settingsManager.setMemfsEnabled(agent.id, true); } - // When memfs is being enabled, detach old API-based memory tools - if ( - settingsManager.isMemfsEnabled(agent.id) && - (memfsFlag || (isNewlyCreatedAgent && !isSubagent)) - ) { + // When memfs is being enabled via flag, detach old API-based memory tools + if (settingsManager.isMemfsEnabled(agent.id) && memfsFlag) { const { detachMemoryTools } = await import("./tools/toolset"); await detachMemoryTools(agent.id); } // Ensure agent's system prompt includes/excludes memfs section to match setting - if (memfsFlag || noMemfsFlag || (isNewlyCreatedAgent && !isSubagent)) { + if (memfsFlag || noMemfsFlag) { const { updateAgentSystemPromptMemfs } = await import( "./agent/modify" );