---
title: Letta Agent SDK quickstart | Letta Docs
description: Learn the basics of the SDK
---

Already familiar with the Claude Agent SDK? Jump to our [migration guide](/letta-agent-sdk/migration/index.md).

## 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.

- [Cloud (Constellation)](#tab-panel-192)
- [Local](#tab-panel-193)
- [Remote server](#tab-panel-194)

1. **Get an API key**

   Create an API key from [app.letta.com/api-keys](https://app.letta.com/api-keys) and set it as an environment variable:

   ```
   export LETTA_API_KEY='your-api-key-here'
   ```

2. **Install the SDK**

   ```
   npm install @letta-ai/letta-code-sdk tsx
   ```

3. **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);
   ```

4. **Run your code**

   ```
   npx tsx quickstart.ts
   ```

5. **Example output**

   ```
   Agent ID: agent-abc123
   Conversation ID: conv-abc123
   ## SDK release brief
   - Current state: ...
   - Risks: ...
   - Follow-ups: ...
   ```

1) **Install Node.js**

   The SDK runs Letta Code locally as a subprocess. Install [Node.js](https://nodejs.org/en/download) version 22.19+.

2) **Install the SDK**

   ```
   npm install @letta-ai/letta-code-sdk tsx
   ```

3) **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);
   ```

4) **Run your code**

   ```
   npx tsx quickstart.ts
   ```

5) **Example output**

   ```
   Agent ID: agent-abc123
   Conversation ID: conv-abc123
   ## Engineering onboarding memo
   - Architecture: ...
   - Workflows: ...
   - Gotchas: ...
   ```

1. **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](/letta-code/app-server/index.md) for server setup and auth.

2. **Install the SDK**

   ```
   npm install @letta-ai/letta-code-sdk tsx
   ```

3. **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);
   ```

4. **Run your code**

   ```
   LETTA_APP_SERVER_URL='http://127.0.0.1:4500' npx tsx quickstart.ts
   ```

5. **Example output**

   ```
   Agent ID: agent-abc123
   Conversation ID: conv-abc123
   ## Morning handoff
   - Recent changes: ...
   - Blockers: ...
   - Recommended actions: ...
   ```

## 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

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

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

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

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

### 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

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

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

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

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

### 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

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`        |

For Cloud and Remote sessions, `cwd` must be a path inside the selected runtime environment, not a local path on the SDK caller’s machine.

`model` updates passed to `createSession()` or `resumeSession()` persist on the agent.

### 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

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](/letta-agent-sdk/reference/index.md).
