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

App Server is the local websocket control plane for Letta Code runtimes. Use it when you want to build your own controller, UI, daemon, or multi-agent orchestrator on top of Letta Code without shelling out to the CLI or using the higher-level Letta Code 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 local clients. It currently listens on loopback websocket URLs and is designed for clients running on the same machine or inside the same trusted environment.

## 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 Code runtime
- Building a daemon that coordinates multiple Letta Code 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 Code SDK](/letta-code-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 Code harness.

## Core model

App Server is organized around a small set of concepts:

| Concept            | Meaning                                                                                                 |
| ------------------ | ------------------------------------------------------------------------------------------------------- |
| App Server process | A local `letta app-server` process that hosts the websocket transport and a Letta Code 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 Code process.

## Current constraints

- App Server currently supports `ws://` loopback listen URLs.
- The default listen URL is `ws://127.0.0.1:0`, which chooses an available port.
- 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-code/app-server/quickstart/index.md) - Start App Server and send the first turn.
- [Protocol lifecycle](/letta-code/app-server/protocol-lifecycle/index.md) - Understand runtime startup, turns, sync, and abort.
- [External tools](/letta-code/app-server/external-tools/index.md) - Register tools that execute in your controller.
- [Integration patterns](/letta-code/app-server/integration-patterns/index.md) - Design robust controllers and team orchestrators.
