Skip to main content
Letta builds stateful agents that keep memory across conversations. The Letta Agent SDK runs those agents in TypeScript and lets you give them your own tools. Back the tools with sandbox.commands.run and sandbox.files so every command the agent runs, and every file it writes, stays in an isolated VM instead of on your server. The model API key stays with the agent runtime; the sandbox never sees it.

Setup

The Letta Agent SDK needs Node.js 22.19 or later. The examples use top-level await, so run them as ES modules: set "type": "module" in package.json and run each file with npx tsx. The examples use Anthropic. To use OpenAI, set OPENAI_API_KEY and pass model: "openai/gpt-5.5" to createAgent. See API keys to create a Superserve API key.

Wire the sandbox into the agent

The highlighted block defines a bash tool backed by the sandbox. Letta runs the tool’s execute function in your process, so the command goes to the sandbox through the Superserve SDK.
Three options matter:
  • allowedTools is the exact list of client-side tools the agent can call. Letta agents also have built-in client-side tools, such as Bash, Read, and Write, that run on the machine that hosts the agent runtime. List only your sandbox tools so the agent cannot use them.
  • baseTools: [] attaches no server-side tools to the agent. By default, createAgent asks for the web_search and fetch_webpage tools, which run outside the sandbox and its network rules.
  • Letta asks for approval before each tool call by default. permissionMode: "unrestricted" approves every call. To decide per call, see Approve tool calls.
The tool returns stdout, stderr, and exitCode, so the agent sees failed commands as well as their output.

Run code with file tools

Add file tools next to bash to let the agent write a script, run it, and read the result. Put the tools in a function that takes a sandbox, so you can reuse them:
sandbox-tools.ts
Pass the tools and their names to the session:
Output from a run with claude-sonnet-5-5 (the model’s wording varies):
sandbox.files paths must be absolute. See Read & write files for binary content and directory downloads.

Stream tool calls

client.prompt returns only the final result. Open a session and read session.stream() to show each tool call and its output while the agent works.
Output from a run (trimmed):
See Streaming to stream a single command’s output.

One sandbox per agent

A Letta agent keeps its memory across conversations, so give it a sandbox that lasts as long. Tag the sandbox with the agent ID, find it on each turn, and pause it between turns so files and installed packages stay available without paying for an idle VM.
Output from a run (trimmed; the model’s wording varies):
client.prompt starts a new conversation on each call. resumeSession continues the agent’s default conversation, so the second turn knows about the file from the first. Sandbox.connect() resumes a paused sandbox before it returns. It waits up to 30 seconds for a pause that is still in progress, so pause({ wait: true }) makes sure the pause is done before the next turn. See Lifecycle and Metadata.

Use a self-hosted App Server

The local backend starts a Letta App Server as a child process of your app. To run one App Server for many app processes, start it yourself with the model API key in its environment:
Then connect with the remote backend. The tools still run in your app process, so commands still go to the sandbox:
The agent reports the sandbox hostname, not the hostname of the App Server machine. To listen on an address other than loopback, set up authentication first. See App Server in the Letta docs.

Approve tool calls

In the file tools example, replace permissionMode with a canUseTool callback to check each call before it reaches the sandbox. The agent reads the deny message and can change its plan.
Without permissionMode: "unrestricted" or canUseTool, the turn stops and waits for an approval. See Permissions in the Letta docs.

Use the Superserve MCP server

To let the agent create and manage sandboxes itself, connect the Superserve MCP server instead of defining tools. MCP tools are named mcp__<server>__<tool>. List the ones the agent can call in allowedTools.
Output from a run (trimmed):
With MCP, the agent owns the sandbox lifecycle, so do not rely on the model to delete what it creates. The canUseTool callback changes each sandbox_create call before it runs: it tags the sandbox with the agent ID, pauses it after one hour of active time, and deletes it 10 minutes after it pauses. The finally block deletes every tagged sandbox that is left, even if the turn fails. See Auto-delete.

Good to know

  • The agent can call only the client-side tools in allowedTools and the server-side tools in baseTools. Keep kill(), pause(), and update() in your application code, not in a tool.
  • To limit what agent-written code can reach, create the sandbox with network rules.
  • Pass timeoutMs to commands.run. Without it, the SDK request times out after 30 seconds, so the tool call fails on longer commands such as package installs.
  • The default sandbox has python3 but no python. Tell the agent in the prompt, or use a template with the tools you need.
  • As a backstop for crashes, set timeoutSeconds and autoDeleteSeconds when you create the sandbox. See Auto-delete.

Resources

Letta Agent SDK docs

Agents, sessions, tools, and permissions in the Letta Agent SDK.

Run commands

Options for commands.run, including environment variables and working directory.