---
title: App Server | Letta Docs
description: Build custom controllers and UIs on top of Letta Agent's app-server websocket protocol
---

App Server is the websocket control plane for Letta Agent runtimes. Use it when you want to build your own controller, UI, daemon, or multi-agent orchestrator on top of Letta Agent without shelling out to the CLI or using the higher-level Letta Agent SDK.

App Server exposes native v2 websocket frames. A client connects to the local server, starts or resumes a runtime for an agent and conversation, sends turns, streams events, and handles controller-owned tools.

App Server is an integration surface for trusted clients. Loopback listeners work without websocket auth. Non-loopback listeners require websocket auth and reject browser-origin requests, so use server-side or native clients that can send `Authorization: Bearer ...` headers.

## When to use App Server

Use App Server when you need a protocol-level control plane:

- Building a desktop, web, or mobile controller for a local Letta Agent runtime
- Building a daemon that coordinates multiple Letta agents
- Registering application-owned tools that execute in your controller process
- Streaming detailed runtime events into your own UI
- Managing agent runtimes without invoking `letta` through `Bash`

Use the [Letta Agent SDK](/letta-agent-sdk/quickstart/index.md) when you want a higher-level TypeScript library that owns the runtime lifecycle for you. Use the [Letta API client SDKs](/api-overview/client-sdks/index.md) when you want to build server-side agents directly on the Letta API without the Letta Agent harness.

## Core model

App Server is organized around a small set of concepts:

| Concept            | Meaning                                                                                            |
| ------------------ | -------------------------------------------------------------------------------------------------- |
| App Server process | A `letta app-server` process that hosts the websocket transport and a Letta Agent listener runtime |
| Control channel    | Websocket channel for commands, request responses, and controller-owned tool callbacks             |
| Stream channel     | Websocket channel for streamed agent output and runtime updates                                    |
| Runtime            | A resolved `{ agent_id, conversation_id }` scope                                                   |
| Turn               | One `input` command that sends user messages or approval responses to the runtime                  |
| External tool      | A tool registered by the controller during `runtime_start` and executed through callbacks          |

A client should treat both websocket channels as a single event stream. Request responses use `request_id` for correlation, but runtime state updates and turn events may be emitted independently of the command that caused them.

## Protocol shape

App Server does not use JSON-RPC. Each websocket frame is a JSON object with a `type` field.

```
{
  "type": "runtime_start",
  "request_id": "runtime-1",
  "agent_id": "agent-123",
  "conversation_id": "conv-123"
}
```

Responses and events use the same discriminated-union style:

```
{
  "type": "runtime_start_response",
  "request_id": "runtime-1",
  "success": true,
  "runtime": {
    "agent_id": "agent-123",
    "conversation_id": "conv-123"
  },
  "created": {
    "agent": false,
    "conversation": false
  },
  "agent": {},
  "conversation": {}
}
```

## Main command flow

Most clients follow this sequence:

1. Start App Server with `letta app-server`.
2. Connect both websocket channels.
3. Send `runtime_start` to resolve or create an agent and conversation.
4. Send `input` to start turns.
5. Read `stream_delta`, `update_loop_status`, `update_device_status`, `update_queue`, and other events from both channels.
6. Send `sync` after reconnects or when a UI needs an authoritative replay.
7. Send `abort_message` to cancel active work.

## What App Server owns

App Server owns runtime execution:

- Tool preparation and local tool execution
- CWD and permission mode for the runtime
- Agent and conversation resolution
- Turn queueing and streaming
- State replay through `sync` and `runtime_start`
- External tool callback routing

Your application should own product state:

- User accounts and UI state
- Team/task registries
- Dashboard state
- Durable job results
- Reconnect and retry policy
- Any domain-specific database

This separation keeps App Server as the execution substrate and keeps application state recoverable outside the Letta Agent process.

## Current constraints

- App Server currently supports `ws://` listen URLs.
- The default listen URL is `ws://127.0.0.1:0`, which chooses an available port.
- Loopback listeners (`localhost`, `127.x.x.x`, and `::1`) do not require websocket auth.
- Non-loopback listeners require `--ws-auth capability-token` or `--ws-auth signed-bearer-token`.
- Requests and websocket upgrades with an `Origin` header are rejected.
- The default websocket path is `/ws`.
- Use `?channel=control` and `?channel=stream` to open the two channels.
- A process accepts one active control session at a time. A new control session replaces the existing listener runtime.
- The protocol is v2-only. Do not send legacy listener commands like `request_state`, `change_cwd`, or `cancel_run`.

## Subpages

- [Quickstart](/letta-agent/app-server/quickstart/index.md) - Start App Server and send the first turn.
- [Protocol lifecycle](/letta-agent/app-server/protocol-lifecycle/index.md) - Understand runtime startup, turns, sync, and abort.
- [External tools](/letta-agent/app-server/external-tools/index.md) - Register tools that execute in your controller.
- [Integration patterns](/letta-agent/app-server/integration-patterns/index.md) - Design robust controllers and team orchestrators.
