Letta Agent SDK quickstart
Learn the basics of the SDK
Quickstart
Section titled “Quickstart”Use the Agent SDK to create stateful digital people and employees with Letta Code’s computer-use harness: shell access, file editing, skills, subagents, and living memory that improves over time.
-
Get an API key
Create an API key from app.letta.com/api-keys and set it as an environment variable:
export LETTA_API_KEY='your-api-key-here' -
Install the SDK
npm install @letta-ai/letta-code-sdk tsx -
Create your code
Save this as
quickstart.ts:import { LettaCodeClient } from "@letta-ai/letta-code-sdk";async function main() {const client = new LettaCodeClient({backend: "cloud",apiKey: process.env.LETTA_API_KEY,});const agentId = await client.createAgent({persona:"You are Nora, a proactive digital chief of staff. You research, write crisp briefs, remember preferences, and turn ambiguous requests into concrete next steps.",human:"The user wants concise executive summaries, explicit risks, and suggested follow-up actions.",});await using session = client.createSession(agentId);console.log("Agent ID:", agentId);console.log("Conversation ID:", session.conversationId);await session.send("Research the launch plan for a small SDK release. Draft a status brief, list the top risks, and propose the first three follow-up tasks.",);for await (const message of session.stream()) {if (message.type === "assistant") {console.log(message.content);}}}main().catch(console.error); -
Run your code
npx tsx quickstart.ts -
Example output
Agent ID: agent-abc123Conversation ID: conv-abc123## SDK release brief- Current state: ...- Risks: ...- Follow-ups: ...
-
Install Node.js
The SDK runs Letta Code locally as a subprocess. Install Node.js version 22.19+.
-
Install the SDK
npm install @letta-ai/letta-code-sdk tsx -
Create your code
Save this as
quickstart.ts:import { LettaCodeClient } from "@letta-ai/letta-code-sdk";async function main() {const client = new LettaCodeClient({ backend: "local" });const agentId = await client.createAgent({persona:"You are Patch, a resident engineering teammate for this repository. You inspect files before changing them, learn local conventions, and keep a memory of durable codebase patterns.",human:"The user prefers practical handoffs: architecture notes, commands to run, and gotchas worth remembering.",});await using session = client.createSession(agentId, {cwd: process.cwd(),});console.log("Agent ID:", agentId);console.log("Conversation ID:", session.conversationId);await session.send("Inspect this repository and write an onboarding memo for a new engineer: architecture, important workflows, and gotchas.",);for await (const message of session.stream()) {if (message.type === "assistant") {console.log(message.content);}}}main().catch(console.error); -
Run your code
npx tsx quickstart.ts -
Example output
Agent ID: agent-abc123Conversation ID: conv-abc123## Engineering onboarding memo- Architecture: ...- Workflows: ...- Gotchas: ...
-
Start an app server
Run a Letta Code app server on the machine where you want agent state and tools to run. See App Server for server setup and auth.
-
Install the SDK
npm install @letta-ai/letta-code-sdk tsx -
Create your code
Save this as
quickstart.ts:import { LettaCodeClient } from "@letta-ai/letta-code-sdk";async function main() {const client = new LettaCodeClient({backend: "remote",url: process.env.LETTA_APP_SERVER_URL ?? "http://127.0.0.1:4500",authToken: process.env.LETTA_APP_SERVER_TOKEN,});const agentId = await client.createAgent({persona:"You are Ops, a digital employee running in a shared workspace. You use shell and file tools to inspect systems, prepare handoff reports, and coordinate follow-up work.",human:"The user values morning briefs with evidence, blockers, owners, and suggested next actions.",});await using session = client.createSession(agentId, {cwd: "/workspace/project",});console.log("Agent ID:", agentId);console.log("Conversation ID:", session.conversationId);await session.send("Prepare a morning handoff report: inspect the workspace, summarize recent changes, identify blockers, and list the first actions you recommend.",);for await (const message of session.stream()) {if (message.type === "assistant") {console.log(message.content);}}}main().catch(console.error); -
Run your code
LETTA_APP_SERVER_URL='http://127.0.0.1:4500' npx tsx quickstart.ts -
Example output
Agent ID: agent-abc123Conversation ID: conv-abc123## Morning handoff- Recent changes: ...- Blockers: ...- Recommended actions: ...
Sessions
Section titled “Sessions”An agent is the persistent entity with memory. A conversation is a thread on that agent. A session is the active connection you use to send messages and stream events.
Start a new conversation
Section titled “Start a new conversation”Use createSession(agentId) when you want a new conversation on an existing agent.
const agentId = await client.createAgent({ persona: "You are Avery, a persistent product operations partner who remembers context, writes plans, and follows through across conversations.",});
await using session = client.createSession(agentId);
console.log("Save this conversation ID:", session.conversationId);
await session.send( "Plan next week's beta launch: milestones, owner checklist, open questions, and a concise Slack update.",);for await (const message of session.stream()) { if (message.type === "assistant") console.log(message.content);}Resume a conversation
Section titled “Resume a conversation”Save session.conversationId and pass it to resumeSession() later.
const conversationId = "conv-abc123";
await using session = client.resumeSession(conversationId);
await session.send("Continue from where we left off.");for await (const message of session.stream()) { if (message.type === "assistant") console.log(message.content);}Resume the main conversation
Section titled “Resume the main conversation”Each agent also has a main conversation. Pass an agentId to resumeSession() to resume that default thread.
await using session = client.resumeSession(agentId);One-shot prompts
Section titled “One-shot prompts”For simple calls, use prompt(). Passing an agentId creates a short-lived new conversation on that agent.
const result = await client.prompt( "Draft a five-bullet prep note for my 1:1 with the design lead.", agentId,);console.log(result.result);If you omit agentId on a local client or top-level helper, the SDK follows Letta Code’s local default-agent resolution: use the default or most recently used agent and start a new conversation, similar to letta -p. For Cloud and Remote clients, pass an agent ID.
Interaction
Section titled “Interaction”Stream a turn
Section titled “Stream a turn”Use send() to start a turn and stream() to read events until the result message.
await session.send("Run the tests and summarize the failures.");
for await (const message of session.stream()) { if (message.type === "reasoning") { console.log("thinking:", message.content); }
if (message.type === "tool_call") { console.log("tool:", message.toolName, message.toolInput); }
if (message.type === "assistant") { console.log(message.content); }
if (message.type === "queue_update") { console.log("queued turns:", message.queue.length); }
if (message.type === "result" && !message.success) { console.error(message.errorDetail ?? message.error); }}Cloud, Remote, and Local agent sessions can accept another send() while a turn is streaming. The listener owns queueing and stream() may emit queue_update events before the current turn’s result.
Handle permission requests
Section titled “Handle permission requests”Use canUseTool to approve, deny, or edit tool calls. The callback can be fully interactive: return a promise that resolves after your UI or server receives a user’s decision. The SDK keeps the tool approval pending until the callback returns.
async function askUserForApproval(request: { toolName: string; toolInput: Record<string, unknown>;}): Promise<{ allow: boolean; updatedInput?: Record<string, unknown>; reason?: string;}> { // Show a modal, send a websocket event, wait for an admin action, etc. return { allow: false, reason: "Approval UI not implemented" };}
await using session = client.createSession(agentId, { permissionMode: "default", canUseTool: async (toolName, toolInput) => { const decision = await askUserForApproval({ toolName, toolInput });
if (decision.allow) { return { behavior: "allow", updatedInput: decision.updatedInput ?? toolInput, }; }
return { behavior: "deny", message: decision.reason ?? "Denied by user", }; },});permissionMode: "bypassPermissions" auto-allows tool calls that do not require user input. Tools that ask the user for input still flow through canUseTool, so your app can render the prompt and return the user’s response.
Interrupt a turn
Section titled “Interrupt a turn”Call abort() to stop the active turn without closing the session.
await session.send("Run the full test suite and fix failures.");
setTimeout(() => { void session.abort();}, 5_000);
for await (const message of session.stream()) { if (message.type === "result") break;}Use close() when you are done with the SDK session and want to release its resources.
Change model and reasoning effort
Section titled “Change model and reasoning effort”Inspect the model catalog and update the model between turns when you want to trade speed, cost, and reasoning depth.
const catalog = await session.listModels();const target = catalog.entries.find( (entry) => entry.handle === "anthropic/claude-opus-4",);
if (!target) throw new Error("Model is not available");
await session.updateModel({ modelHandle: target.handle, reasoningEffort: "high",});
await session.send("Use the stronger model to pressure-test the launch risks.");reasoningEffort is resolved against the model catalog. Use a model ID returned by listModels() or a full modelHandle; avoid shorthand names unless they are real catalog IDs. You can also pass model and reasoningEffort when creating or resuming a session.
Send advanced protocol commands
Section titled “Send advanced protocol commands”Most applications should use the typed SDK methods. For protocol-level integrations, use sendCommand() with raw Letta Code websocket protocol commands.
if (!session.agentId || !session.conversationId) { throw new Error("Session is not initialized");}
const runtime = { agent_id: session.agentId, conversation_id: session.conversationId,};
await session.sendCommand({ type: "change_device_state", runtime, payload: { cwd: "/workspace/project" },});
const sync = await session.sendCommand<{ type: "sync_response"; success: boolean;}>({ type: "sync", runtime }, { responseType: "sync_response" });
console.log("Synced:", sync.success);Omit responseType for fire-and-forget commands. Provide responseType or a custom predicate when you want to wait for a protocol response.
Configuration
Section titled “Configuration”Agent options
Section titled “Agent options”Configure persistent agent state when you call createAgent().
const agentId = await client.createAgent({ model: "anthropic/claude-sonnet-4", persona: "You are Quinn, a digital research analyst who can inspect files, run commands, synthesize sources, and maintain living memory about the user's organization.", human: "The user prefers concise memos with evidence, caveats, and recommended next actions.",});You can also pass memory blocks directly:
const agentId = await client.createAgent({ memory: [ { label: "persona", value: "You are Quinn, a digital research analyst who can inspect files, run commands, synthesize sources, and maintain living memory about the user's organization.", }, { label: "human", value: "The user prefers concise memos with evidence, caveats, and recommended next actions.", }, ],});Session options
Section titled “Session options”Configure runtime behavior when you call createSession() or resumeSession(): model, reasoning effort, current working directory, permission mode, and dreaming settings.
await using session = client.createSession(agentId, { cwd: "/path/to/project", model: "anthropic/claude-sonnet-4", reasoningEffort: "medium", permissionMode: "default", dreaming: { trigger: "step-count", stepCount: 8, },});| Setting | Option |
|---|---|
| Model | model |
| Reasoning effort | reasoningEffort |
| Working directory | cwd |
| Permission mode | permissionMode |
| Interactive approvals and questions | canUseTool |
| Dreaming settings | dreaming |
System prompts
Section titled “System prompts”Set a custom system prompt when creating an agent:
const agentId = await client.createAgent({ systemPrompt: "You are a highly autonomous digital employee. Use tools to gather evidence, write concise reports, and remember durable preferences.",});Available presets include default, letta-claude, letta-codex, letta-gemini, claude, codex, and gemini.
Cleanup
Section titled “Cleanup”Sessions can be closed automatically with await using or manually with session.close().
const session = client.createSession(agentId);try { // ... use session ...} finally { session.close();}For client methods, session types, and option interfaces, see the SDK reference.