fix: make memfs opt-in and cloud-only (#915)
Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
@@ -433,3 +433,25 @@ export async function addGitMemoryTag(agentId: string): Promise<void> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the git-memory-enabled tag from an agent.
|
||||
*/
|
||||
export async function removeGitMemoryTag(agentId: string): Promise<void> {
|
||||
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)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
27
src/index.ts
27
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<void> {
|
||||
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<void> {
|
||||
}
|
||||
|
||||
agent = result.agent;
|
||||
isNewlyCreatedAgent = true;
|
||||
setAgentProvenance({
|
||||
isNew: true,
|
||||
blocks: [],
|
||||
@@ -1608,7 +1606,6 @@ async function main(): Promise<void> {
|
||||
baseTools,
|
||||
);
|
||||
agent = result.agent;
|
||||
isNewlyCreatedAgent = true;
|
||||
setAgentProvenance(result.provenance);
|
||||
}
|
||||
|
||||
@@ -1686,28 +1683,30 @@ async function main(): Promise<void> {
|
||||
// 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"
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user