@letta-ai/letta-agent-sdk 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +26 -347
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -2,379 +2,58 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@letta-ai/letta-agent-sdk.svg?style=flat-square)](https://www.npmjs.com/package/@letta-ai/letta-agent-sdk) [![Discord](https://img.shields.io/badge/discord-join-blue?style=flat-square&logo=discord)](https://discord.gg/letta)
4
4
 
5
- Build applications with stateful agents powered by the [Letta agent harness](https://www.letta.com/agent). The Agent SDK provides one TypeScript interface for managed, local, and self-hosted deployments.
5
+ The SDK for [stateful agents](https://docs.letta.com/concepts/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.
6
6
 
7
- ## Installation
7
+ Read the [documentation](https://docs.letta.com/agent-sdk) for guides and the full API reference.
8
+
9
+ ## Quick start
8
10
 
9
11
  ```bash
10
12
  npm install @letta-ai/letta-agent-sdk
11
13
  ```
12
14
 
13
- ## Quick start
14
-
15
- ```ts
15
+ ```typescript
16
16
  import { LettaAgentClient } from "@letta-ai/letta-agent-sdk";
17
17
 
18
- const client = new LettaAgentClient({
19
- backend: "cloud",
20
- apiKey: process.env.LETTA_API_KEY,
21
- });
18
+ const client = new LettaAgentClient({ backend: "cloud" });
22
19
 
20
+ // Create the agent once...
23
21
  const agentId = await client.createAgent({
24
- model: "anthropic/claude-opus-4-8",
25
- memory: [
26
- {
27
- label: "persona",
28
- value: "You are a proactive research assistant.",
29
- },
30
- {
31
- label: "human",
32
- value: "The user prefers concise summaries with sources and next steps.",
33
- },
34
- ],
35
- });
36
-
37
- await using session = client.createSession(agentId);
38
-
39
- await session.send("Research the latest project changes and prepare a brief.");
40
- for await (const message of session.stream()) {
41
- if (message.type === "assistant") {
42
- console.log(message.content);
43
- }
44
- }
45
- ```
46
-
47
- `createAgent()` uses the supplied memory as the agent's identity. Letta Code
48
- personality presets are opt-in: pass `personality: "memo"` (or another preset)
49
- only when you want that preset's name, description, and memory blocks.
50
-
51
- An agent is the persistent entity with memory. A conversation is a thread on that agent. A session is the active connection used to send messages, stream events, execute tools, and handle approvals.
52
-
53
- - `createSession(agentId)` starts a new conversation.
54
- - `resumeSession(conversationId)` resumes a saved conversation.
55
- - `resumeSession(agentId)` resumes the agent's default conversation.
56
-
57
- Pass your own OTID when you render the user message optimistically, so you can
58
- match the row you drew with the persisted one:
59
-
60
- ```ts
61
- const otid = crypto.randomUUID();
62
- await session.send("Summarize today's changes.", { otid });
63
- ```
64
-
65
- The OTID is stored on the persisted user message (visible via `listMessages()`)
66
- and doubles as the turn's `clientMessageId`, so it also identifies the message
67
- in `queue_update` events while it is queued. The SDK generates one when the
68
- option is omitted.
69
-
70
- Pass `stateless: true` when a session should use an existing agent without
71
- loading or changing its MemFS:
72
-
73
- ```ts
74
- await using session = client.createSession(agentId, { stateless: true });
75
- ```
76
-
77
- The agent and conversation still persist. Stateless sessions preserve the
78
- agent's model, prompt, tools, tags, and sampling settings, but skip MemFS sync,
79
- agent-scoped skills and mods, memory transcript writes, and reflection for that
80
- session. Options that mutate persisted configuration (`model`,
81
- `reasoningEffort`, `dreaming`, and `resources`) are rejected, as is
82
- `session.updateModel()`. The option works with local, remote App Server, and
83
- Cloud backends.
84
-
85
- Portable sessions also expose the stateful controls needed by interactive
86
- clients:
87
-
88
- ```ts
89
- const state = await session.bootstrapState();
90
- await session.changeDeviceState({
91
- cwd: "/workspace/project",
92
- permissionMode: "acceptEdits",
22
+ persona: "You are Nora, a research analyst who tracks our competitors.",
93
23
  });
94
24
 
95
- const removed = await session.removeQueuedMessage(queueItemId);
96
- if (!removed.removed) {
97
- // Reconcile the authoritative queue before offering the action again.
98
- }
99
-
100
- await session.recoverPendingApprovals();
101
- ```
102
-
103
- `removeQueuedMessage()` waits for an app-server acknowledgement.
104
- `changeDeviceState()` currently confirms command transport only because the
105
- underlying protocol does not acknowledge that mutation.
106
-
107
- The read side of the device state is exposed as a one-shot getter and a
108
- subscription — enough to restore permission-mode UI and re-surface pending
109
- approvals when a mobile or web client returns to the foreground:
110
-
111
- ```ts
112
- const status = await session.getDeviceStatus();
113
- // status.permissionMode, status.workingDirectory, status.isOnline,
114
- // status.isProcessing, status.memoryDirectory,
115
- // status.pendingControlRequests, status.raw
116
-
117
- const unsubscribe = session.onDeviceStatus((status) => {
118
- // Called for every device-status update pushed by the runtime.
119
- });
120
- unsubscribe();
121
- ```
122
-
123
- `getDeviceStatus()` always sends a lightweight, request-correlated `sync`
124
- (`recover_approvals: false`, `force_device_status: true`) and resolves only
125
- after the runtime acknowledges it and replays a fresh status. This makes the
126
- getter safe for foreground reconciliation instead of returning a snapshot
127
- cached before the app was backgrounded. Pending approval request IDs are for
128
- correlation; decisions continue through `recoverPendingApprovals()` and the
129
- session's `canUseTool` callback.
130
-
131
- ## Rendering a transcript
132
-
133
- `session.stream()` emits message slices, not rows. Reconciling them into a
134
- conversation view means merging text fragments by message family and `otid`,
135
- dropping replayed `seqId` positions per run, and merging tool arguments and
136
- results by `toolCallId`. `createTranscriptAccumulator()` does that for you and
137
- is available from the package root and `/client`:
25
+ // ...then resume it, from anywhere, for as long as it lives.
26
+ await using session = client.resumeSession(agentId);
138
27
 
139
- ```ts
140
- import { createTranscriptAccumulator } from "@letta-ai/letta-agent-sdk";
141
-
142
- const transcript = createTranscriptAccumulator();
143
-
144
- await session.send("Summarize the repo");
28
+ await session.send("What changed since last week?");
145
29
  for await (const message of session.stream()) {
146
- const rows = transcript.apply(message);
147
- render(rows); // TranscriptRow[]: user | assistant | reasoning | tool_call
148
- }
149
-
150
- // Safe mid-run: merge older history without duplicating in-flight rows.
151
- transcript.rebase(await session.listMessages({ limit: 50 }));
152
- ```
153
-
154
- Each row carries a stable `key` for list rendering, plus the identifiers it was
155
- reconciled from (`uuid`, `otid`, `runId`, `seqId`). Tool rows expose the payload
156
- identity (`toolCallId`) separately from the envelopes that produced them, along
157
- with `argumentsComplete` and a `status` of `streaming`, `ready`, or `complete`,
158
- so a partially streamed argument object is never rendered as final input.
159
-
160
- `apply()` returns the same array reference when a message changed nothing (a
161
- replayed position, or a turn-level message such as `result`), so memoized
162
- renderers can skip the update. Turn-level messages (`init`, `result`, `error`,
163
- `retry`, `queue_update`, `loop_status`) are not transcript rows; handle them
164
- directly.
165
-
166
- ## Deployment options
167
-
168
- | Backend | Agent state | Tool execution |
169
- | --- | --- | --- |
170
- | `cloud` | Letta Cloud | Managed cloud sandbox or a selected computer |
171
- | `local` | Current computer | Current computer through an SDK-managed App Server |
172
- | `remote` | Configured by the App Server | A user-managed App Server computer |
173
-
174
- ### Choose a computer
175
-
176
- Cloud clients can list the computers registered with the current Letta account
177
- and select one by name when opening a session:
178
-
179
- ```ts
180
- const { computers } = await client.computers.list({ onlineOnly: true });
181
- for (const computer of computers) {
182
- console.log(computer.name, computer.deviceId, computer.status);
30
+ if (message.type === "assistant") process.stdout.write(message.content);
183
31
  }
184
-
185
- await using session = client.resumeSession(agentId, {
186
- computer: "Work laptop",
187
- });
188
- await session.send("Run the test suite on this computer.");
189
- ```
190
-
191
- Computer names must uniquely identify a registered computer. For persisted
192
- selections, use the stable `deviceId`; a `connectionId` identifies only the
193
- current online lease and can rotate after reconnects:
194
-
195
- ```ts
196
- await using session = client.resumeSession(agentId, {
197
- computer: { deviceId: "device-..." },
198
- });
199
- ```
200
-
201
- `client.computers.get(deviceId)` retrieves one computer and
202
- `client.computers.resolve(selector)` resolves a name or ID to its current online
203
- connection. The previous client-level and session-level `environment` options
204
- remain as deprecated compatibility aliases for `computer`.
205
-
206
- ### Local
207
-
208
- ```ts
209
- const client = new LettaAgentClient({ backend: "local" });
210
-
211
- await using session = client.createSession(agentId, {
212
- cwd: process.cwd(),
213
- });
214
- ```
215
-
216
- Local execution (embedded Letta Code harness / app server) requires Node.js 22.19 or newer.
217
-
218
- ### Built-in client toolsets
219
-
220
- Use `toolset` to select a request-scoped harness preset and add bundled client
221
- tools. `allowedTools` remains the final visibility boundary across bundled and
222
- custom tools.
223
-
224
- Both are scoped to locally executed client tools. Server-side tools (such as
225
- `web_search`) are attached to the agent itself via `baseTools` at creation and
226
- are unaffected by `allowedTools` — listing one there matches nothing.
227
-
228
- ```ts
229
- await using session = client.createSession(agentId, {
230
- toolset: {
231
- base: "none",
232
- include: ["Read", "LS", "Glob", "Grep"],
233
- },
234
- allowedTools: ["Read", "LS", "Glob", "Grep"],
235
- });
236
- ```
237
-
238
- The override applies to this SDK session's turns without changing the agent's
239
- persisted harness toolset preference.
240
-
241
- ### MCP tools
242
-
243
- Pass MCP servers by name in session options. The SDK supports local stdio,
244
- Streamable HTTP, and legacy SSE transports. It namespaces discovered tools as
245
- `mcp__<server>__<tool>` and exposes them through Letta Code's external-tool
246
- protocol.
247
-
248
- ```ts
249
- await using session = client.createSession(agentId, {
250
- cwd: process.cwd(),
251
- mcpServers: {
252
- filesystem: {
253
- command: "npx",
254
- args: ["-y", "@modelcontextprotocol/server-filesystem", process.cwd()],
255
- },
256
- exa: {
257
- type: "http",
258
- url: "https://mcp.exa.ai/mcp",
259
- },
260
- github: {
261
- type: "http",
262
- url: "https://api.githubcopilot.com/mcp/",
263
- headers: {
264
- Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
265
- },
266
- },
267
- },
268
- allowedTools: ["mcp__filesystem__*", "mcp__exa__*", "mcp__github__list_issues"],
269
- });
270
- ```
271
-
272
- Connections start concurrently during session initialization. A failed server
273
- is skipped without dropping healthy servers. OAuth is host-managed, matching
274
- the Claude Agent SDK: complete OAuth in your application and provide the access
275
- token through `headers`. The SDK does not open an interactive browser flow.
276
-
277
- MCP connections run in the Node SDK process and close with the session. This
278
- includes MCP used by remote and Cloud sessions: stdio servers see the SDK host
279
- filesystem, not a managed sandbox. MCP is unavailable from the portable
280
- `@letta-ai/letta-agent-sdk/client` browser and React Native entry point.
281
-
282
- ### Managed Cloud sandbox repositories
283
-
284
- Cloud sessions can clone up to 10 GitHub repositories into the managed
285
- sandbox. Public repositories clone directly; private repositories require
286
- access through the organization's GitHub integration.
287
-
288
- ```ts
289
- await using session = client.createSession(agentId, {
290
- sandbox: {
291
- githubRepositories: [
292
- { owner: "letta-ai", repo: "letta-docs-md" },
293
- { owner: "letta-ai", repo: "letta-code" },
294
- ],
295
- },
296
- });
297
- ```
298
-
299
- ### Remote App Server
300
-
301
- ```ts
302
- const client = new LettaAgentClient({
303
- backend: "remote",
304
- url: "http://127.0.0.1:4500",
305
- authToken: process.env.LETTA_APP_SERVER_TOKEN,
306
- });
307
32
  ```
308
33
 
309
- See the [deployment guide](https://docs.letta.com/letta-agent-sdk/deployment) for managed sandboxes, remote computers, App Server setup, and authentication.
34
+ Set `LETTA_API_KEY` for the cloud backend. See the [quickstart](https://docs.letta.com/agent-sdk/quickstart) for the local and self-hosted paths.
310
35
 
311
- ## Browser and React Native
36
+ ## Where your agents run
312
37
 
313
- Use the portable `/client` entry point in browser, Expo, and React Native applications. It supports the `cloud` and `remote` backends without importing Node process-management modules.
38
+ One interface, three backends:
314
39
 
315
- ```ts
316
- import { LettaAgentClient } from "@letta-ai/letta-agent-sdk/client";
40
+ | Backend | Agent state | Tools execute |
41
+ | ---------- | ------------------- | -------------------------------------- |
42
+ | `"cloud"` | Hosted by Letta | A managed sandbox, or a computer you connect |
43
+ | `"local"` | On this machine* | On this machine* |
44
+ | `"remote"` | Your App Server | On your App Server machine |
317
45
 
318
- const client = new LettaAgentClient({
319
- backend: "cloud",
320
- apiKey: userProvidedApiKey,
321
- webSocketAuth: "query",
322
- });
323
- ```
324
-
325
- For authenticated Remote App Servers in React Native, pass the platform WebSocket through `createReactNativeWebSocketConstructor()` so capability-token headers use React Native's third constructor argument.
326
-
327
- ## Management APIs
328
-
329
- The client exposes agent, conversation, model, computer, and Cloud repository management alongside active sessions:
330
-
331
- ```ts
332
- const agents = await client.agents.list({ tags: ["support"] });
333
- const conversations = await client.conversations.list({
334
- agentId: agents[0].id,
335
- orderBy: "lastMessageAt",
336
- order: "desc",
337
- });
338
-
339
- // No open session required — safe for model pickers and settings screens.
340
- const { entries: models, availableHandles } = await client.models.list();
341
-
342
- const repository = await client.repositories.create({ name: "inputs" });
343
- await client.repositories.files.create(repository.id, {
344
- path: "brief.md",
345
- content: "# Project brief\n",
346
- });
46
+ \* *"this machine" refers to the machine that the SDK code itself is running on*
347
47
 
348
- // Persistent agent knowledge: attach once during provisioning. This
349
- // recompiles the agent's default conversation after the relationship is
350
- // visible, and session.close() will not detach it.
351
- await client.agents.repositories.attach(agents[0].id, repository.id, {
352
- permissions: "read",
353
- });
48
+ 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](https://docs.letta.com/agent-sdk/deployment).
354
49
 
355
- // Remove persistent knowledge explicitly during deprovisioning.
356
- await client.agents.repositories.detach(agents[0].id, repository.id);
357
- await client.agents.delete(agents[0].id);
358
- ```
50
+ ## Examples
359
51
 
360
- `client.repositories` and `client.agents.repositories` are available on the
361
- `cloud` backend. Persistent agent relationships belong under
362
- `client.agents.repositories`; use session `resources` only when the session
363
- should own attachment and cleanup. Attach and detach recompile the agent's
364
- default conversation by default; pass `{ recompile: false }` only when the
365
- caller will handle prompt recompilation separately. Existing explicit
366
- conversations are not silently recompiled. If recompilation fails after a
367
- successful relationship mutation, the method rejects but the attachment state
368
- remains changed; retrying is safe. See [Cloud repositories](https://docs.letta.com/letta-agent-sdk/repositories) for file operations, version history, and session resources.
52
+ Runnable applications live in [`examples/`](./examples) — including a web chat UI, client tools, and multi-agent systems. See the [React chat template](https://github.com/letta-ai/letta-agent-sdk-react-chat) for a more complete example of a custom UI running on the Letta Agent SDK.
369
53
 
370
- ## Documentation
54
+ ## Contributing
371
55
 
372
- - [Overview](https://docs.letta.com/letta-agent-sdk/overview)
373
- - [Quickstart](https://docs.letta.com/letta-agent-sdk/quickstart)
374
- - [Deployment](https://docs.letta.com/letta-agent-sdk/deployment)
375
- - [SDK reference](https://docs.letta.com/letta-agent-sdk/reference)
376
- - [React chat template](https://github.com/letta-ai/letta-agent-sdk-react-chat)
377
- - [Examples](./examples)
56
+ Development conventions for this repository are in [AGENTS.md](./AGENTS.md).
378
57
 
379
58
  ---
380
59
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letta-ai/letta-agent-sdk",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "packageManager": "bun@1.3.0",
5
5
  "description": "SDK for programmatic control of Letta agents",
6
6
  "type": "module",