Letta Agent SDK
Memory frameworkOfficial Letta project
Created by Letta
Published in letta-ai/letta-agent-sdk. Publisher page
116 GitHub stars
What it is
SDK for programmatic control of Letta agents
AutoGineer overview, from the brain’s own files
The Letta Agent SDK provides a programmatic interface for creating and managing stateful AI agents. It enables agents to maintain their identity and long-term memory across different conversations, models, and host environments. The SDK supports three backends—cloud, local, and remote—allowing developers to choose between managed hosting or local execution. It includes features for tool registration, session management, and streaming message handling, ensuring that agents can resume their state and continue tasks independently of the client connection.
How it works
Memory model: Not stated
AutoGineer overview, from the brain’s own files
The SDK uses a client-based architecture to manage agent sessions. Developers initialize a LettaAgentClient to handle communication with the agent backend. Agents are created with specific system prompts and memory configurations. The SDK provides a session interface to send messages and stream responses. It supports tool execution by allowing developers to register tools that are routed to specific conversations, enabling agents to perform actions based on their persistent state.
| Folder | Role | Files |
|---|---|---|
| .github | agent-config | 3 |
| examples | examples | 52 |
| scripts | scripts | 1 |
| src | source-code | 92 |
Requirements
Runtimes: Node.js >=22.19.0
API keys (by name): LETTA_API_KEY
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Google Antigravity, Kiro, OpenCode, Amp, Roo Code, Cline, JetBrains Junie, Devin (ex-Windsurf)
Set it up
- Not scanned by AutoGineerIts dependencies are installed by npm when you run it and were not scanned by AutoGineer (3 declared). https://www.npmjs.com/package/@letta-ai/letta-agent-sdk
Created by Letta. Published in letta-ai/letta-agent-sdk. Licence: Apache License 2.0.
Usage
AutoGineer overview, from the brain’s own files
- Creating and managing stateful agents with persistent memory.
- Resuming agent sessions across different environments or restarts.
- Streaming agent responses and handling background task notifications.
- Registering tools to be shared across multiple agent conversations.
- Executing one-off queries without creating persistent agents.
- Configuring agent toolsets and access policies programmatically.
- “Create a new research analyst agent named Nora.”
- The SDK returns an agentId that can be used to resume the agent's session and memory later.
- “Resume a previous conversation with an existing agent.”
- The SDK restores the session context, allowing the agent to continue based on its stored history.
- “Register a customer lookup tool for multiple conversations.”
- The tool becomes available to all registered conversations, with state keyed by conversationId.
- “Perform a one-off query without creating a persistent agent.”
- The SDK runs a stateless conversation and closes the runtime automatically after the response.
Documentation by Letta
Letta Agent SDK
The SDK for stateful agents: create an agent once, then resume it from anywhere. Each agent has its own identity and long-term memory, and keeps both across conversations, models, and the computers it runs on.
Read the documentation for guides and the full API reference.
Quick start
npm install @letta-ai/letta-agent-sdkimport { LettaAgentClient } from "@letta-ai/letta-agent-sdk";
const client = new LettaAgentClient({ backend: "cloud" });
// Create the agent once...
const agentId = await client.createAgent({
systemPrompt: "You are Nora, a research analyst who tracks our competitors.",
memfs: true,
});
// ...then resume it, from anywhere, for as long as it lives.
await using session = client.resumeSession(agentId);
await session.send("What changed since last week?");
for await (const message of session.stream()) {
if (message.type === "assistant") process.stdout.write(message.content);
}Local and remote management clients can use await using client = new LettaAgentClient(...), or call await client.close() explicitly. Client
disposal closes its pooled management connection and shared local App Server.
Local calls share one lazily started server per client; closing a session does
not stop its siblings. Keep the client open while using these sessions.
Sessions with environment overrides or memory confinement retain dedicated
processes and must be closed separately. Externally hosted servers are never
stopped by the client.
The top-level convenience functions own short-lived clients; use a
LettaAgentClient instance to share a server across repeated or concurrent calls.
Latency-sensitive applications can initialize the runtime and transport before the first user action without fetching transcript history or invoking the model:
const session = client.resumeSession(conversationId);
await session.ready();
await session.send(message);ready() is idempotent and safe to call concurrently. SDKResultMessage.durationMs
measures the tracked turn and excludes session initialization; measure ready()
separately when startup latency matters.
Use the final result.success to determine whether a turn succeeded. A turn can
recover from a failed API run: run-scoped errors and explicitly nonterminal
error notices remain stream_event diagnostics while the harness continues.
They do not close the session or produce a failed result on their own.
For a simple question that should not create or use an agent, call query().
It creates an ordinary conversation from the supplied model and
system prompt, streams the turn, and closes the runtime when iteration ends:
for await (const message of client.query({
prompt: "What is the capital of France?",
options: {
model: "openai/gpt-5.6-luna",
system: "Answer directly and concisely.",
},
})) {
if (message.type === "assistant") process.stdout.write(message.content);
}query() supports API-backed App Servers and local App Servers that advertise
local conversation support. Older local App Servers return an upgrade error.
Pass the returned query.conversationId as options.conversationId on a later
query() to continue that conversation with its history. On resume, model,
system, and other creation settings are ignored; the conversation keeps its
stored configuration.
Cloud queries require an explicit connected computer. Local and remote clients run the conversation through their App Server.
Turns the agent starts on its own
A conversation can also run turns that no send() started: a background task
finishing, a cron prompt, or another client's message. stream() yields those
turns as they run, and their result carries an origin such as
"task_notification" or "cron". Only the result of your own turn ends the
stream() pass, so existing send() and stream() loops keep working.
for await (const message of session.stream()) {
if (message.type === "result" && message.origin) {
console.log(`${message.origin} turn finished: ${message.result}`);
}
}A session's allowedTools and toolset apply to those turns too, for as long
as the session is open.
Tools for many conversations
To serve client tools to many conversations at once, register them on the client instead of opening a session per conversation. One connection carries every registration, calls are routed to the tools registered for the calling conversation, and registrations are restored after a reconnect:
const client = new LettaAgentClient({ backend: "local" });
await using registration = await client.registerTools({
conversations: [plannerConversationId, reviewerConversationId],
tools: [lookupCustomer],
allowedTools: ["lookup_customer", "Read", "Grep"],
});
// Sessions on a registered conversation inherit its tools and allowedTools.
await using session = client.resumeSession(plannerConversationId);A tool's execute receives a context as its last argument, { toolCallId, agentId, conversationId }, so one tool instance can serve every conversation
and keep per-conversation state keyed on conversationId:
const lookupCustomer: AnyAgentTool = {
// name, label, description, parameters ...
execute: async (toolCallId, args, signal, onUpdate, context) => {
const notes = notesFor(context?.conversationId);
// ...
},
};Call registerTools() once per tool set; each call can bind its own tool
implementations to its own conversations. It requires the local or remote
backend, and allowedTools or toolset require a Letta Code release that
advertises runtime tool policies.
A session opened with resumeSession() on a registered conversation uses the
registration's tools, allowedTools and toolset, and picks up
registration.update() while it is open, so you set them in one place. Options
you pass to the session can add and narrow but not widen:
toolsare added on top of the registered tools; reusing a registered name is an error.allowedToolsnarrows the registered allowlist. Names outside it are dropped unless they are the session's own tools.toolsetis only accepted when the registration has none or the same one.
Set LETTA_API_KEY for the cloud backend. See the quickstart for the local and self-hosted paths.
Where your agents run
One interface, three backends:
| Backend | Agent state | Tools execute |
|---|---|---|
"cloud" | Hosted by Letta | A managed sandbox, or a computer you connect |
"local" | On this machine* | On this machine* |
"remote" | Your App Server | On your App Server machine |
* "this machine" refers to the machine that the SDK code itself is running on
Browser, Expo, and React Native applications import from @letta-ai/letta-agent-sdk/client, which does not require Node and supports the cloud and remote backends. See Deployment.
Examples
Runnable applications live in examples/. Start with the examples guide, which orders the demos by concept and lists their setup and side effects. See the React chat template for a more complete custom UI.
Contributing
Development conventions for this repository are in AGENTS.md.
Made with 💜 in San Francisco
<img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=29de91a5-e18c-4366-b192-33a909e184bc&page=README.md" alt="" aria-hidden="true" />
AGENTS.md
Letta Agent SDK contributor map
Canonical source
Work in src/. dist/ is generated by bun run build, ignored by Git, and
published as a bundle. A repeated class or method in dist/index.js or
dist/client-entry.js can represent separate source-layer responsibilities
that Bun placed in one file; trace it back to src/ before changing it.
Session architecture
client-base.tsselects the backend and constructs a session.Remote, Cloud, and the default local app-server path share
remote-client-session-core.ts.remote-session-protocol.tsowns portable protocol types, validation, and wire-to-domain normalization.remote-turn-coordinator.tsowns active/queued turn correlation and theSDKMessagestream queue.app-server-session.tsadapts an app-server connection to the runtime controller used by the portable session core.cloud-session.tsresolves Cloud runtimes and managed sandboxes, then uses the same runtime controller and portable session core.types.tsdefines the stable public SDK surface. Internal protocol types belong beside their implementation rather than in the public type barrel.
For session.listMessages() on an app-server or Cloud session, the call path
is:
RemoteClientSessionCore.listMessages →
RemoteClientRuntimeController.listMessages →
the selected transport adapter.
Those methods operate at different layers and should not be combined merely because they share a name.
Stream identifiers
Streamed SDKMessage values carry several identifiers, and each one answers a
different question. Most duplicate or missing message bugs in consumers come
from using one to answer another's question.
seqIdanswers "have I already processed this stream position?" It is a per-run replay cursor: use it to suppress replayed events after a resume or reconnect, compare it only within the samerunId, and reset the threshold when a new run starts. It is not row identity.otidanswers "which typed message slice is this?" Use it to reassemble the fragments of one slice. It is not a universal key across message types.uuidanswers "which message object is this?" Use it for message identity and for history cursoring and backfill. It is the top-level message ID when the stream provides one and an SDK-generated identifier otherwise, so it is neither a guaranteed server ID nor a replay cursor.toolCallIdanswers "which tool call is this about?" Use it to join atool_callwith itstool_result. It is not identity for assistant or reasoning messages.
Only assistant and reasoning carry otid and seqId; tool_call and
tool_result carry toolCallId and uuid. Identity therefore has to be
decided per message family. A single generic seqId → otid → uuid fallback
chain applied to every message will merge unrelated rows.
Key
assistantandreasoningaccumulators on the message type together withotid, not onotidalone. A provider can reuse anotidacross kinds, which collapses assistant text into reasoning. Fall back touuidonly within the same family —src/tests/stream-message-identity.test.tspins the case where two slices share auuidand differ only by type andotid.Merge tool arguments and results by
toolCallId, but do not flatten every tool-family message onto it. Envelope identity and tool-payload identity are distinct.Do not merge persisted history into a transcript while a turn is streaming. When backfill after a reconnect is unavoidable, rebase the in-progress accumulators onto the fetched rows; otherwise later fragments append to a stale baseline and the row renders twice.
Mid-stream,
tool_call.toolInputis{ raw: "<chunk>" }until the argument fragments parse as JSON. AccumulaterawArgumentsand parse once at the end rather than overwriting parsed arguments with a partial wrapper.
Cloud API ownership
@letta-ai/letta-codeowns the agent harness, app-server protocol, runtime lifecycle, and tool execution boundary.@letta-ai/letta-clientowns Letta API HTTP contracts, authentication, headers, retries, pagination, and API errors. Declare it as a direct dependency when importing it; do not rely on Letta Code's transitive copy.Before adding a Cloud REST call, inspect the installed generated client. Use the generated resource method when it covers the endpoint.
When an endpoint is not generated yet, use the shared Letta client's typed
get/post/patch/deletemethod and validate the ungenerated response at that boundary. Do not add rawfetch, URL/auth/header helpers, response parsers, retry loops, or parallel HTTP error types.LettaAgentClientBaseowns one lazy Cloud client and injects it into management, repository, environment, and session code. New Cloud REST surfaces should reuse that instance. WebSocket transport remains separate.Keep the public management types portable across Cloud and app-server backends. Generated Cloud types belong at the Cloud transport seam; do not narrow the shared facade to shapes that the app-server protocol cannot promise.
Reuse generated Letta entity, request, and response types whenever the SDK contract has the same semantics. Camel-case SDK facades may rename fields, but their value types should derive from the generated params. Do not mirror generated entities as partial
Record<string, unknown>shapes or rewrite app-server envelopes already exported byapp-server-protocol.Verify generated route paths against the live API before introducing or preserving a generic-request exception. Server routing evolves: a mismatch proven in an older PR may no longer exist. Prefer the generated resource as soon as its live route works, and delete the workaround rather than fossilizing it in new architecture.
After changing this boundary, run bun run check, bun test, and
bun run build. If path behavior changed, run the narrow live route test too;
do not infer POST/DELETE slash behavior from a successful GET.
Change discipline
Keep transport parsing out of the session facade.
Keep turn correlation and stream buffering in
RemoteTurnCoordinator.Preserve exports from the package root and
/clientunless a change is explicitly intended to be breaking.Treat source-size exceptions in
scripts/check-source-size.tsas debt ceilings: reduce them when a file shrinks and do not raise them casually.Run
bun run check,bun test, andbun run buildbefore opening a PR.
158 files · 154 not served here
- .github/workflows/ci.yml (not served here)
- .github/workflows/letta.yml (not served here)
- .github/workflows/release.yml (not served here)
- .gitignore (not served here)
- AGENTS.md
- ATTRIBUTION.md
- LICENSE
- README.md
- build.ts (not served here)
- examples/.gitignore (not served here)
- examples/README.md (not served here)
- examples/bug-fixer/cli.ts (not served here)
- examples/bug-fixer/fixer.ts (not served here)
- examples/bug-fixer/types.ts (not served here)
- examples/create-agent-session.ts (not served here)
- examples/custom-tools/README.md (not served here)
- examples/custom-tools/main.ts (not served here)
- examples/custom-tools/shared-tools.ts (not served here)
- examples/dungeon-master/README.md (not served here)
- examples/dungeon-master/cli.ts (not served here)
- examples/dungeon-master/dm.ts (not served here)
- examples/dungeon-master/types.ts (not served here)
- examples/economics-seminar/README.md (not served here)
- examples/economics-seminar/cli.ts (not served here)
- examples/economics-seminar/faculty.ts (not served here)
- examples/economics-seminar/presenter.ts (not served here)
- examples/economics-seminar/seminar.ts (not served here)
- examples/economics-seminar/types.ts (not served here)
- examples/file-organizer/cli.ts (not served here)
- examples/file-organizer/organizer.ts (not served here)
- examples/file-organizer/types.ts (not served here)
- examples/focus-group/README.md (not served here)
- examples/focus-group/agents.ts (not served here)
- examples/focus-group/cli.ts (not served here)
- examples/focus-group/focus-group.ts (not served here)
- examples/focus-group/types.ts (not served here)
- examples/release-notes/cli.ts (not served here)
- examples/release-notes/generator.ts (not served here)
- examples/release-notes/types.ts (not served here)
- examples/research-team/README.md (not served here)
- examples/research-team/agents/analyst.ts (not served here)
- examples/research-team/agents/researcher.ts (not served here)
- examples/research-team/agents/workflow.ts (not served here)
- examples/research-team/agents/writer.ts (not served here)
- examples/research-team/cli.ts (not served here)
- examples/research-team/teleport-example.ts (not served here)
- examples/research-team/tools/file-store.ts (not served here)
- examples/research-team/types.ts (not served here)
- examples/sdk-tour.ts (not served here)
- examples/stateless-conversations/README.md (not served here)
- examples/stateless-conversations/main.ts (not served here)
- examples/web-chat/README.md (not served here)
- examples/web-chat/index.html (not served here)
- examples/web-chat/server.ts (not served here)
- examples/workflows/README.md (not served here)
- examples/workflows/audit-files.ts (not served here)
- examples/workflows/fix-until-pass.ts (not served here)
- examples/workflows/migrate-files.ts (not served here)
- examples/workflows/plan-panel.ts (not served here)
- examples/workflows/research-sources.ts (not served here)
- examples/workflows/runtime.ts (not served here)
- package-lock.json (not served here)
- package.json (not served here)
- scripts/check-source-size.ts (not served here)
- src/agent-creation.ts (not served here)
- src/agent-repositories.ts (not served here)
- src/app-server-management.ts (not served here)
- src/app-server-models.ts (not served here)
- src/app-server-session.ts (not served here)
- src/cli-resolver.ts (not served here)
- src/client-base.ts (not served here)
- src/client-entry.ts (not served here)
- src/client.ts (not served here)
- src/cloud-client.ts (not served here)
- src/cloud-management.ts (not served here)
- src/cloud-sandbox.ts (not served here)
- src/cloud-session.ts (not served here)
- src/cloud-status-transport.ts (not served here)
- src/computers.ts (not served here)
- src/external-tools.ts (not served here)
- src/index.ts (not served here)
- src/interactiveToolPolicy.ts (not served here)
- src/local-app-server-session.ts (not served here)
- src/local-app-server.ts (not served here)
- src/management-errors.ts (not served here)
- src/management-types.ts (not served here)
- src/management.ts (not served here)
- src/mcp-runtime.ts (not served here)
- src/mcp.ts (not served here)
- src/model-update-resolution.ts (not served here)
- src/query-types.ts (not served here)
- src/query.ts (not served here)
- src/remote-client-session-core.ts (not served here)
- src/remote-session-protocol.ts (not served here)
- src/remote-turn-coordinator.ts (not served here)
- src/remote.ts (not served here)
- src/repositories.ts (not served here)
- src/repository-types.ts (not served here)
- src/request-ids.ts (not served here)
- src/runtime-tool-policy.ts (not served here)
- src/sandbox-files.ts (not served here)
- src/shared-local-app-server.ts (not served here)
- src/skill-loading.ts (not served here)
- src/skill-node.ts (not served here)
- src/stream-events.ts (not served here)
- src/structured-output-session.ts (not served here)
- src/structured-output.ts (not served here)
- src/tests/advanced-session.ts (not served here)
- src/tests/agent-repositories.test.ts (not served here)
- src/tests/app-server-session.test.ts (not served here)
- src/tests/bootstrap-sdk.test.ts (not served here)
- src/tests/client-lifecycle-process.test.ts (not served here)
- src/tests/client.test.ts (not served here)
- src/tests/cloud-session.test.ts (not served here)
- src/tests/cloud-transport.live.integration.test.ts (not served here)
- src/tests/computers.test.ts (not served here)
- src/tests/fixtures/agentfiles/stuck-approval.af (not served here)
- src/tests/fixtures/client-management-exit.ts (not served here)
- src/tests/fixtures/live/init_contract.json (not served here)
- src/tests/fixtures/live/list_messages_during_stream.json (not served here)
- src/tests/fixtures/live/list_messages_pagination.json (not served here)
- src/tests/fixtures/live/resume_conversation_init.json (not served here)
- src/tests/fixtures/live/send_stream_basic.json (not served here)
- src/tests/fixtures/live/stream_event_partials.json (not served here)
- src/tests/fixtures/live/tool_lifecycle.json (not served here)
- src/tests/fixtures/shared-app-server-client.ts (not served here)
- src/tests/headless-skills-reminder-contract.test.ts (not served here)
- src/tests/image.test.ts (not served here)
- src/tests/interactiveToolPolicy.test.ts (not served here)
- src/tests/list-messages.test.ts (not served here)
- src/tests/live.integration.test.ts (not served here)
- src/tests/local-app-server.test.ts (not served here)
- src/tests/management.test.ts (not served here)
- src/tests/mcp.test.ts (not served here)
- src/tests/permission-mode.test.ts (not served here)
- src/tests/portable-client.test.ts (not served here)
- src/tests/public-session-types.test.ts (not served here)
- src/tests/query.test.ts (not served here)
- src/tests/remote-turn-recovery.test.ts (not served here)
- src/tests/remote.test.ts (not served here)
- src/tests/repositories.test.ts (not served here)
- src/tests/runtime-tool-policy.test.ts (not served here)
- src/tests/shared-local-app-server.live.test.ts (not served here)
- src/tests/shared-local-app-server.test.ts (not served here)
- src/tests/skill-seeding.test.ts (not served here)
- src/tests/stream-events.test.ts (not served here)
- src/tests/stream-message-identity.test.ts (not served here)
- src/tests/structured-output.test.ts (not served here)
- src/tests/transcript-accumulator.test.ts (not served here)
- src/tests/validation.test.ts (not served here)
- src/tool-helpers.ts (not served here)
- src/tool-registration.ts (not served here)
- src/transcript-accumulator.ts (not served here)
- src/types.ts (not served here)
- src/validation.ts (not served here)
- src/websocket.ts (not served here)
- tsconfig.build.json (not served here)
- tsconfig.json (not served here)
Advantages
- Agents maintain identity and long-term memory across conversations and host machines. evidence: README.md
- Supports multiple backends including cloud, local, and remote for flexible deployment. evidence: README.md
Limitations
- Requires specific backend compatibility for certain features like local conversation support. evidence: README.md
- Tool registration and policy enforcement require specific Letta Code release versions. evidence: README.md
Security
Risk level: No risk found
This brain runs
Agent instructions that run or fetch something (5)
AGENTS.md:`dist/` is generated by `bun run build`, ignored by Git, and published as a bundle.AGENTS.md:`seqId` answers "have I already processed this stream position?" It is a per-run replay cursor: use it to suppress replayed events after a resume or reconnect, compare it only within the same `runId`, and reset the threshold when a new run starts.AGENTS.md:After changing this boundary, run `bun run check`, `bun test`, and `bun run build`.AGENTS.md:If path behavior changed, run the narrow live route test too;AGENTS.md:Run `bun run check`, `bun test`, and `bun run build` before opening a PR.
Attribution and licence
- Created by
- Letta
- Publisher
- Letta (Official Letta project)
- Source
- letta-ai/letta-agent-sdk · commit eb7236b
- Licence
- Apache License 2.0 (files served here)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Letta.
Install from source
154 of its files are not served here, so there is no ZIP. letta-ai/letta-agent-sdk at eb7236b