feat: expose MEMORY_DIR and AGENT_ID in runtime context (#986)

This commit is contained in:
Charles Packer
2026-02-16 18:24:59 -08:00
committed by GitHub
parent 72c8a0ab23
commit 66ced52c81
7 changed files with 246 additions and 9 deletions

View File

@@ -4,11 +4,14 @@
You have an advanced memory system that enables you to remember past interactions and continuously improve your own capabilities.
## Memory Filesystem
Your memory is stored in a git repository at `~/.letta/agents/<agent-id>/memory/`. This provides full version control, sync with the server, and create worktrees for parallel edits.
Your memory is stored in a git repository at `$MEMORY_DIR` (usually `~/.letta/agents/$AGENT_ID/memory/`). This provides full version control, sync with the server, and create worktrees for parallel edits.
Each file contains metadata frontmatter include a `description` (the description of the file's contents), `limit` (the character limit), and optional `metadata`.
The filesystem tree is always available in your system prompt, along with the contents of files in the `system/` folder.
You also have additional external memory (e.g. your message history) that is accessible and that you can bring into context with tools when needed.
When running in Letta Code, shell tools provide:
- `MEMORY_DIR`: absolute path to this agent's memory repository
## How It Works
1. Each `.md` file in `memory/system/` is pinned to your system prompt with tags <system/context/{name}.md></system/context/{name}.md>
2. The `memory_filesystem` block renders the current tree view of all available memory files
@@ -18,7 +21,7 @@ You also have additional external memory (e.g. your message history) that is acc
## Syncing
```bash
cd ~/.letta/agents/<agent-id>/memory
cd "$MEMORY_DIR"
# See what changed
git status
@@ -35,5 +38,5 @@ The system will remind you when your memory has uncommitted changes. Sync when c
## History
```bash
git -C ~/.letta/agents/<agent-id>/memory log --oneline
git -C "$MEMORY_DIR" log --oneline
```

View File

@@ -6,6 +6,8 @@ Your memory consists of core memory (composed of memory blocks) and external mem
- Memory blocks: Each memory block contains a label (title), description (explaining how this block should influence your behavior), and value (the actual content). Memory blocks have size limits. Memory blocks are embedded within your system instructions and are pinned in-context (so they are always visible).
- External memory: Additional memory storage that is accessible and that you can bring into context with tools when needed.
When running in Letta Code, shell tools provide `AGENT_ID`: your current agent ID.
Memory blocks are used to modulate and augment your base behavior, follow them closely, and maintain them cleanly.
Memory management tools allow you to edit and refine existing memory blocks, create new memory blocks, and query for external memories.
Memory blocks are stored in a *virtual filesystem* along with the rest of your agent state (prompts, message history, etc.), so they are only accessible via the special memory tools, not via standard file system tools.

View File

@@ -7843,9 +7843,13 @@ ${recentCommits}
## Memory Filesystem Location
Your memory blocks are synchronized with the filesystem at:
\`~/.letta/agents/${agentId}/memory/\`
\`${getMemoryFilesystemRoot(agentId)}\`
Use this path when working with memory files during initialization.
Environment variables available in Letta Code:
- \`AGENT_ID=${agentId}\`
- \`MEMORY_DIR=${getMemoryFilesystemRoot(agentId)}\`
Use \`$MEMORY_DIR\` when working with memory files during initialization.
`
: "";

View File

@@ -3,6 +3,7 @@
import { execSync } from "node:child_process";
import { platform } from "node:os";
import { getMemoryFilesystemRoot } from "../../agent/memoryFilesystem";
import { LETTA_CLOUD_API_URL } from "../../auth/oauth";
import { SYSTEM_REMINDER_CLOSE, SYSTEM_REMINDER_OPEN } from "../../constants";
import { settingsManager } from "../../settings-manager";
@@ -194,6 +195,17 @@ export function buildSessionContext(options: SessionContextOptions): string {
}
}
const showMemoryDir = (() => {
try {
return settingsManager.isMemfsEnabled(agentInfo.id);
} catch {
return false;
}
})();
const memoryDirLine = showMemoryDir
? `\n- **Memory directory (also stored in \`MEMORY_DIR\` env var)**: \`${getMemoryFilesystemRoot(agentInfo.id)}\``
: "";
// Build the context
let context = `${SYSTEM_REMINDER_OPEN}
This is an automated message providing context about the user's environment.
@@ -238,7 +250,7 @@ ${gitInfo.status}
// Add agent info
context += `
## Agent Information (i.e. information about you)
- **Agent ID**: ${agentInfo.id}
- **Agent ID (also stored in \`AGENT_ID\` env var)**: ${agentInfo.id}${memoryDirLine}
- **Agent name**: ${agentInfo.name || "(unnamed)"} (the user can change this with /rename)
- **Agent description**: ${agentInfo.description || "(no description)"} (the user can change this with /description)
- **Last message**: ${lastRunInfo}

View File

@@ -0,0 +1,88 @@
import { describe, expect, test } from "bun:test";
import { getMemoryFilesystemRoot } from "../agent/memoryFilesystem";
import { buildSessionContext } from "../cli/helpers/sessionContext";
import { settingsManager } from "../settings-manager";
describe("session context reminder", () => {
test("always includes AGENT_ID env var", () => {
const agentId = "agent-test-session-context";
const context = buildSessionContext({
agentInfo: {
id: agentId,
name: "Test Agent",
description: "Test description",
lastRunAt: null,
},
serverUrl: "https://api.letta.com",
});
expect(context).toContain(
`- **Agent ID (also stored in \`AGENT_ID\` env var)**: ${agentId}`,
);
});
test("does not include MEMORY_DIR env var when memfs is disabled", () => {
const agentId = "agent-test-session-context-disabled";
const original = settingsManager.isMemfsEnabled.bind(settingsManager);
(
settingsManager as unknown as {
isMemfsEnabled: (id: string) => boolean;
}
).isMemfsEnabled = () => false;
try {
const context = buildSessionContext({
agentInfo: {
id: agentId,
name: "Test Agent",
description: "Test description",
lastRunAt: null,
},
serverUrl: "https://api.letta.com",
});
expect(context).not.toContain(
"Memory directory (also stored in `MEMORY_DIR` env var)",
);
expect(context).not.toContain(getMemoryFilesystemRoot(agentId));
} finally {
(
settingsManager as unknown as {
isMemfsEnabled: (id: string) => boolean;
}
).isMemfsEnabled = original;
}
});
test("includes MEMORY_DIR env var when memfs is enabled", () => {
const agentId = "agent-test-session-context-enabled";
const original = settingsManager.isMemfsEnabled.bind(settingsManager);
(
settingsManager as unknown as {
isMemfsEnabled: (id: string) => boolean;
}
).isMemfsEnabled = () => true;
try {
const context = buildSessionContext({
agentInfo: {
id: agentId,
name: "Test Agent",
description: "Test description",
lastRunAt: null,
},
serverUrl: "https://api.letta.com",
});
expect(context).toContain(
`- **Memory directory (also stored in \`MEMORY_DIR\` env var)**: \`${getMemoryFilesystemRoot(agentId)}\``,
);
} finally {
(
settingsManager as unknown as {
isMemfsEnabled: (id: string) => boolean;
}
).isMemfsEnabled = original;
}
});
});

View File

@@ -1,12 +1,38 @@
import { describe, expect, test } from "bun:test";
import { spawnSync } from "node:child_process";
import * as path from "node:path";
import { getMemoryFilesystemRoot } from "../../agent/memoryFilesystem";
import { settingsManager } from "../../settings-manager";
import {
ensureLettaShimDir,
getShellEnv,
resolveLettaInvocation,
} from "../../tools/impl/shellEnv";
function withTemporaryAgentEnv<T>(agentId: string, fn: () => T): T {
const originalAgentId = process.env.AGENT_ID;
const originalLettaAgentId = process.env.LETTA_AGENT_ID;
process.env.AGENT_ID = agentId;
process.env.LETTA_AGENT_ID = agentId;
try {
return fn();
} finally {
if (originalAgentId === undefined) {
delete process.env.AGENT_ID;
} else {
process.env.AGENT_ID = originalAgentId;
}
if (originalLettaAgentId === undefined) {
delete process.env.LETTA_AGENT_ID;
} else {
process.env.LETTA_AGENT_ID = originalLettaAgentId;
}
}
}
describe("shellEnv letta shim", () => {
test("resolveLettaInvocation prefers explicit launcher env", () => {
const invocation = resolveLettaInvocation(
@@ -137,3 +163,74 @@ describe("shellEnv letta shim", () => {
}
});
});
test("getShellEnv injects AGENT_ID aliases", () => {
withTemporaryAgentEnv(`agent-test-${Date.now()}`, () => {
const env = getShellEnv();
expect(env.AGENT_ID).toBeTruthy();
expect(env.LETTA_AGENT_ID).toBe(env.AGENT_ID);
});
});
test("getShellEnv does not inject MEMORY_DIR aliases when memfs is disabled", () => {
withTemporaryAgentEnv(`agent-test-${Date.now()}`, () => {
const originalIsMemfsEnabled =
settingsManager.isMemfsEnabled.bind(settingsManager);
const originalMemoryDir = process.env.MEMORY_DIR;
const originalLettaMemoryDir = process.env.LETTA_MEMORY_DIR;
(
settingsManager as unknown as { isMemfsEnabled: (id: string) => boolean }
).isMemfsEnabled = () => false;
process.env.MEMORY_DIR = "/tmp/stale-memory-dir";
process.env.LETTA_MEMORY_DIR = "/tmp/stale-memory-dir";
try {
const env = getShellEnv();
expect(env.LETTA_MEMORY_DIR).toBeUndefined();
expect(env.MEMORY_DIR).toBeUndefined();
} finally {
(
settingsManager as unknown as {
isMemfsEnabled: (id: string) => boolean;
}
).isMemfsEnabled = originalIsMemfsEnabled;
if (originalMemoryDir === undefined) {
delete process.env.MEMORY_DIR;
} else {
process.env.MEMORY_DIR = originalMemoryDir;
}
if (originalLettaMemoryDir === undefined) {
delete process.env.LETTA_MEMORY_DIR;
} else {
process.env.LETTA_MEMORY_DIR = originalLettaMemoryDir;
}
}
});
});
test("getShellEnv injects MEMORY_DIR aliases when memfs is enabled", () => {
withTemporaryAgentEnv(`agent-test-${Date.now()}`, () => {
const original = settingsManager.isMemfsEnabled.bind(settingsManager);
(
settingsManager as unknown as { isMemfsEnabled: (id: string) => boolean }
).isMemfsEnabled = () => true;
try {
const env = getShellEnv();
expect(env.AGENT_ID).toBeTruthy();
const resolvedAgentId = env.AGENT_ID as string;
const expectedMemoryDir = getMemoryFilesystemRoot(resolvedAgentId);
expect(env.LETTA_MEMORY_DIR).toBe(expectedMemoryDir);
expect(env.MEMORY_DIR).toBe(expectedMemoryDir);
} finally {
(
settingsManager as unknown as {
isMemfsEnabled: (id: string) => boolean;
}
).isMemfsEnabled = original;
}
});
});

View File

@@ -11,6 +11,7 @@ import * as path from "node:path";
import { fileURLToPath } from "node:url";
import { getServerUrl } from "../../agent/client";
import { getCurrentAgentId } from "../../agent/context";
import { getMemoryFilesystemRoot } from "../../agent/memoryFilesystem";
import { settingsManager } from "../../settings-manager";
/**
@@ -180,13 +181,43 @@ export function getShellEnv(): NodeJS.ProcessEnv {
: pathPrefixes.join(path.delimiter);
}
// Add Letta context for skill scripts
// Add Letta context for skill scripts.
// Prefer explicit agent context, but fall back to inherited env values.
let agentId: string | undefined;
try {
env.LETTA_AGENT_ID = getCurrentAgentId();
const resolvedAgentId = getCurrentAgentId();
if (typeof resolvedAgentId === "string" && resolvedAgentId.trim()) {
agentId = resolvedAgentId.trim();
}
} catch {
// Context not set yet (e.g., during startup), skip
// Context not set yet (e.g., during startup), try env fallback below.
}
if (!agentId) {
const fallbackAgentId = env.AGENT_ID || env.LETTA_AGENT_ID;
if (typeof fallbackAgentId === "string" && fallbackAgentId.trim()) {
agentId = fallbackAgentId.trim();
}
}
if (agentId) {
env.LETTA_AGENT_ID = agentId;
env.AGENT_ID = agentId;
try {
if (settingsManager.isMemfsEnabled(agentId)) {
const memoryDir = getMemoryFilesystemRoot(agentId);
env.LETTA_MEMORY_DIR = memoryDir;
env.MEMORY_DIR = memoryDir;
} else {
// Clear inherited/stale memory-dir vars for non-memfs agents.
delete env.LETTA_MEMORY_DIR;
delete env.MEMORY_DIR;
}
} catch {
// Settings may not be initialized in tests/startup; preserve inherited values.
}
}
// Inject API key and base URL from settings if not already in env
if (!env.LETTA_API_KEY || !env.LETTA_BASE_URL) {
try {