--- a/src/agent/memoryGit.ts +++ b/src/agent/memoryGit.ts @@ -308,6 +308,45 @@ export function isGitRepo(agentId: string): boolean { return existsSync(join(getMemoryRepoDir(agentId), ".git")); } +/** + * Initialize a local memory repository without remote. + * Used for self-hosted or Gitea backends where cloud git endpoint isn't available. + */ +export async function initLocalMemoryRepo(agentId: string): Promise { + const dir = getMemoryRepoDir(agentId); + + debugLog("memfs-git", `Initializing local repo at ${dir}`); + + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }); + } + + // Initialize git repo + await runGit(dir, ["init"], undefined); + + // Configure user (required for commits) + await runGit(dir, ["config", "user.email", "ani@localhost"], undefined); + await runGit(dir, ["config", "user.name", "Ani"], undefined); + + // Create initial structure + const systemDir = join(dir, "system"); + if (!existsSync(systemDir)) { + mkdirSync(systemDir, { recursive: true }); + } + + // Install pre-commit hook + installPreCommitHook(dir); + + debugLog("memfs-git", "Local memory repo initialized"); +} + /** * Clone the agent's state repo into the memory directory. * --- a/src/agent/memoryFilesystem.ts +++ b/src/agent/memoryFilesystem.ts @@ -250,13 +250,40 @@ export async function applyMemfsFlags( // 4. Add git tag + clone/pull repo. let pullSummary: string | undefined; if (isEnabled) { - const { addGitMemoryTag, isGitRepo, cloneMemoryRepo, pullMemory } = + const { addGitMemoryTag, isGitRepo, cloneMemoryRepo, pullMemory, initLocalMemoryRepo } = await import("./memoryGit"); await addGitMemoryTag( agentId, options?.agentTags ? { tags: options.agentTags } : undefined, ); - if (!isGitRepo(agentId) && (await isLettaCloud())) { - await cloneMemoryRepo(agentId); + + // E: Local-first initialization + // F: Configurable backend support + const memfsBackend = process.env.LETTABOT_MEMFS_BACKEND || "auto"; + const isCloud = await isLettaCloud(); + + if (!isGitRepo(agentId)) { + if (memfsBackend === "none" || memfsBackend === "local") { + // Local-only mode (self-hosted, Gitea, Codeberg) + await initLocalMemoryRepo(agentId); + } else if (memfsBackend === "cloud" || (memfsBackend === "auto" && isCloud)) { + // Cloud mode (Letta Cloud) + await cloneMemoryRepo(agentId); + } else { + // Auto mode, self-hosted detected - use local init + await initLocalMemoryRepo(agentId); + } } else if (options?.pullOnExistingRepo) { - const result = await pullMemory(agentId); - pullSummary = result.summary; + // Try to pull, but don't fail if remote unavailable + try { + const result = await pullMemory(agentId); + pullSummary = result.summary; + } catch (error) { + if (error.message?.includes("501") || error.message?.includes("404")) { + // Remote not available - continue with local + console.log("[memfs] Remote unavailable, using local state"); + } else { + throw error; + } + } } }