Add MemFS self-hosted fix documentation
- reference/memfs-selfhosted-fix.patch: Implementation patch - reference/PR-memfs-selfhosted.md: PR description - E: Local-first initialization - F: Configurable backend support - Addresses subagent spawn failures on self-hosted
This commit is contained in:
86
reference/PR-memfs-selfhosted.md
Normal file
86
reference/PR-memfs-selfhosted.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# PR: Fix MemFS for Self-Hosted and Configurable Backends
|
||||
|
||||
## Problem
|
||||
|
||||
Subagents fail to spawn on self-hosted Letta servers because `applyMemfsFlags` tries to `git clone` from `http://localhost:8283/v1/git/{agent_id}/state.git`, which returns HTTP 501 (Not Implemented) on self-hosted.
|
||||
|
||||
**Error:**
|
||||
```
|
||||
fatal: unable to access 'http://10.10.20.19:8283/v1/git/.../state.git/':
|
||||
The requested URL returned error: 501
|
||||
```
|
||||
|
||||
## Root Cause
|
||||
|
||||
The current code only checks `isLettaCloud()` but doesn't handle:
|
||||
1. Self-hosted servers without git endpoint
|
||||
2. Future Gitea/Codeberg backends
|
||||
3. Graceful fallback when remote is unavailable
|
||||
|
||||
## Solution
|
||||
|
||||
### E: Local-First Initialization
|
||||
- Always initialize local git repo first
|
||||
- Try to pull/clone remote only if available
|
||||
- Gracefully continue with local state if remote fails
|
||||
|
||||
### F: Configurable Backend
|
||||
- Add `LETTABOT_MEMFS_BACKEND` environment variable:
|
||||
- `"cloud"` - Force Letta Cloud mode
|
||||
- `"local"` - Force local-only (for Gitea/Codeberg)
|
||||
- `"auto"` - Auto-detect (default)
|
||||
- `"none"` - Disable remote sync
|
||||
|
||||
## Changes
|
||||
|
||||
### 1. `src/agent/memoryGit.ts`
|
||||
- Add `initLocalMemoryRepo(agentId)` function
|
||||
- Initializes local git repo with proper config
|
||||
- Creates initial directory structure
|
||||
- Installs pre-commit hooks
|
||||
|
||||
### 2. `src/agent/memoryFilesystem.ts`
|
||||
- Modify `applyMemfsFlags` to use local-first approach
|
||||
- Check `LETTABOT_MEMFS_BACKEND` env var
|
||||
- Handle 501/404 errors gracefully
|
||||
- Continue with local state if remote unavailable
|
||||
|
||||
## Usage
|
||||
|
||||
### For Self-Hosted (current fix):
|
||||
```bash
|
||||
# No changes needed - auto-detects and uses local mode
|
||||
export LETTABOT_MEMFS=true
|
||||
lettabot start
|
||||
```
|
||||
|
||||
### For Gitea Backend (future):
|
||||
```bash
|
||||
export LETTABOT_MEMFS=true
|
||||
export LETTABOT_MEMFS_BACKEND=local
|
||||
export LETTABOT_MEMFS_REMOTE=https://gitea.example.com/ani/memory.git
|
||||
lettabot start
|
||||
```
|
||||
|
||||
### For Letta Cloud (unchanged):
|
||||
```bash
|
||||
export LETTABOT_MEMFS=true
|
||||
# Works as before with cloud git endpoint
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
1. **Self-hosted:** Subagents now spawn successfully
|
||||
2. **Local repo created:** `~/.letta/agents/{id}/memory/.git/`
|
||||
3. **Graceful degradation:** Works without remote sync
|
||||
|
||||
## Backwards Compatibility
|
||||
|
||||
- Letta Cloud users: No change
|
||||
- Self-hosted users: Now works (was broken)
|
||||
- Future Gitea/Codeberg: Supported via `LETTABOT_MEMFS_BACKEND`
|
||||
|
||||
## Related
|
||||
|
||||
Fixes subagent spawn failures on self-hosted servers.
|
||||
Enables future Gitea/Codeberg integration.
|
||||
91
reference/memfs-selfhosted-fix.patch
Normal file
91
reference/memfs-selfhosted-fix.patch
Normal file
@@ -0,0 +1,91 @@
|
||||
--- 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<void> {
|
||||
+ 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;
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user