Skip to content
Letta Code Letta Code Letta Docs
Sign up
App Server

App Server

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.

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 when you want a higher-level TypeScript library that owns the runtime lifecycle for you. Use the Letta API client SDKs when you want to build server-side agents directly on the Letta API without the Letta Code harness.

App Server is organized around a small set of concepts:

ConceptMeaning
App Server processA local letta app-server process that hosts the websocket transport and a Letta Code listener runtime
Control channelWebsocket channel for commands, request responses, and controller-owned tool callbacks
Stream channelWebsocket channel for streamed agent output and runtime updates
RuntimeA resolved { agent_id, conversation_id } scope
TurnOne input command that sends user messages or approval responses to the runtime
External toolA 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.

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": {}
}

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.

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.

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