@letta-ai/letta-agent-sdk 0.7.0 → 0.7.2
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.
- package/README.md +27 -347
- package/dist/client-entry.js +136 -379
- package/dist/client-entry.js.map +6 -6
- package/dist/index.d.ts +4 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +158 -383
- package/dist/index.js.map +8 -8
- package/dist/remote-session-protocol.d.ts +7 -1
- package/dist/remote-session-protocol.d.ts.map +1 -1
- package/dist/remote-turn-coordinator.d.ts +3 -0
- package/dist/remote-turn-coordinator.d.ts.map +1 -1
- package/dist/types.d.ts +7 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +4 -5
- package/src/remote-session-protocol.ts +24 -7
- package/src/remote-turn-coordinator.ts +88 -14
- package/src/types.ts +7 -5
package/README.md
CHANGED
|
@@ -2,379 +2,59 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@letta-ai/letta-agent-sdk) [](https://discord.gg/letta)
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
25
|
-
|
|
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
|
+
systemPrompt: "You are Nora, a research analyst who tracks our competitors.",
|
|
23
|
+
memfs: true,
|
|
93
24
|
});
|
|
94
25
|
|
|
95
|
-
|
|
96
|
-
|
|
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`:
|
|
26
|
+
// ...then resume it, from anywhere, for as long as it lives.
|
|
27
|
+
await using session = client.resumeSession(agentId);
|
|
138
28
|
|
|
139
|
-
|
|
140
|
-
import { createTranscriptAccumulator } from "@letta-ai/letta-agent-sdk";
|
|
141
|
-
|
|
142
|
-
const transcript = createTranscriptAccumulator();
|
|
143
|
-
|
|
144
|
-
await session.send("Summarize the repo");
|
|
29
|
+
await session.send("What changed since last week?");
|
|
145
30
|
for await (const message of session.stream()) {
|
|
146
|
-
|
|
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);
|
|
31
|
+
if (message.type === "assistant") process.stdout.write(message.content);
|
|
183
32
|
}
|
|
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
33
|
```
|
|
308
34
|
|
|
309
|
-
See the [
|
|
35
|
+
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
36
|
|
|
311
|
-
##
|
|
37
|
+
## Where your agents run
|
|
312
38
|
|
|
313
|
-
|
|
39
|
+
One interface, three backends:
|
|
314
40
|
|
|
315
|
-
|
|
316
|
-
|
|
41
|
+
| Backend | Agent state | Tools execute |
|
|
42
|
+
| ---------- | ------------------- | -------------------------------------- |
|
|
43
|
+
| `"cloud"` | Hosted by Letta | A managed sandbox, or a computer you connect |
|
|
44
|
+
| `"local"` | On this machine* | On this machine* |
|
|
45
|
+
| `"remote"` | Your App Server | On your App Server machine |
|
|
317
46
|
|
|
318
|
-
|
|
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
|
-
});
|
|
47
|
+
\* *"this machine" refers to the machine that the SDK code itself is running on*
|
|
347
48
|
|
|
348
|
-
|
|
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
|
-
});
|
|
49
|
+
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
50
|
|
|
355
|
-
|
|
356
|
-
await client.agents.repositories.detach(agents[0].id, repository.id);
|
|
357
|
-
await client.agents.delete(agents[0].id);
|
|
358
|
-
```
|
|
51
|
+
## Examples
|
|
359
52
|
|
|
360
|
-
`
|
|
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.
|
|
53
|
+
Runnable applications live in [`examples/`](./examples). Start with the [examples guide](./examples/README.md), which orders the demos by concept and lists their setup and side effects. See the [React chat template](https://github.com/letta-ai/letta-agent-sdk-react-chat) for a more complete custom UI.
|
|
369
54
|
|
|
370
|
-
##
|
|
55
|
+
## Contributing
|
|
371
56
|
|
|
372
|
-
|
|
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)
|
|
57
|
+
Development conventions for this repository are in [AGENTS.md](./AGENTS.md).
|
|
378
58
|
|
|
379
59
|
---
|
|
380
60
|
|