Self hosted remote API (BYOR)
Self-host the remote API by connecting a custom client directly to a Letta Code runtime without Letta Cloud.
You can use the Remote Client API without Letta Cloud. In a self-hosted remote, your app owns the transport between a Letta Code runtime and your custom client.
Quickstart
Section titled “Quickstart”1. Start your client WebSocket server
Section titled “1. Start your client WebSocket server”Your custom client can be a WebSocket server. It accepts one Letta Code connection, renders events, and sends commands back over the same socket.
import { WebSocketServer, WebSocket } from "ws";
const token = process.env.BYOR_REMOTE_TOKEN ?? "dev-token";const server = new WebSocketServer({ host: "127.0.0.1", port: 8284 });
let lettaCodeSocket: WebSocket | null = null;
server.on("connection", (socket, request) => { const auth = request.headers.authorization; if (auth !== `Bearer ${token}`) { socket.close(1008, "unauthorized"); return; }
lettaCodeSocket = socket;
socket.on("message", (raw) => { const event = JSON.parse(raw.toString()); handleRuntimeEvent(event); });
socket.on("close", () => { if (lettaCodeSocket === socket) lettaCodeSocket = null; });});
function sendCommand(command: unknown) { if (!lettaCodeSocket || lettaCodeSocket.readyState !== WebSocket.OPEN) { throw new Error("Letta Code runtime is not connected"); } lettaCodeSocket.send(JSON.stringify(command));}
function handleRuntimeEvent(event: any) { if (event.type === "stream_delta") { console.log(event.delta); } if (event.type === "control_request") { console.log("approval required", event); }}2. Start letta remote with your endpoint
Section titled “2. Start letta remote with your endpoint”Start the stock Letta Code remote listener with LETTA_BASE_URL pointing at your self-hosted server:
LETTA_BASE_URL=http://localhost:8284 \IGNORE_SELF_HOSTED_LISTENER_ERROR=1 \letta remote --env-name local-dev --backend localLETTA_BASE_URL is the custom server endpoint.
For deeper command and event details, see the API reference.
3. Send a message
Section titled “3. Send a message”After the runtime connects, send an input command over the socket:
sendCommand({ type: "input", runtime: { agent_id: "agent-...", conversation_id: "conv-...", }, payload: { kind: "create_message", messages: [ { role: "user", content: "What files should I inspect first?", client_message_id: "cm_01J_demo", }, ], supports_control_response: true, },});4. Render streamed events
Section titled “4. Render streamed events”Handle runtime events from the same socket:
function handleRuntimeEvent(event: any) { switch (event.type) { case "update_device_status": updateDeviceStatus(event.device_status); break; case "update_loop_status": updateRunStatus(event.loop_status); break; case "stream_delta": appendStreamDelta(event.delta); break; case "control_request": showApprovalPrompt(event); break; default: handleResponseOrOtherEvent(event); }}Heartbeats and reconnects
Section titled “Heartbeats and reconnects”For direct mode, keep heartbeats simple:
- Client sends
{ "type": "ping" }every 30 seconds. - Runtime replies with
{ "type": "pong", "timestamp": Date.now() }. - If the socket closes, mark the runtime offline.
- When the runtime reconnects, send
syncto replay current state.
You can skip transport seq and ack in a single-socket direct transport. Add them only if you need replay, gap detection, or compatibility with clients that expect hosted remote semantics.
When to add a broker
Section titled “When to add a broker”Add a broker/router only when direct runtime-to-client is not enough.
Use a broker if you need:
- Multiple clients watching the same runtime.
- Multiple Letta Code runtimes behind one endpoint.
- Runtime selection or discovery.
- Per-subscriber
seqstamping and ACK tracking. - Reconnect parking while the runtime restarts.
- Internet routing when the client and runtime cannot reach each other directly.
- Optional proxying to Letta Cloud for some connections.
A broker has the same shape as Letta Code Desktop’s local environment server:
Letta Code runtime <── runtime WS ──> broker <── client WS ──> custom clientBut it is not required for BYOR.
Security checklist
Section titled “Security checklist”For local BYOR:
- Bind to
127.0.0.1unless you intentionally need LAN access. - Require a per-session token on the WebSocket.
- Prefer
Authorization: Bearer <token>when the runtime/client can set headers. - Validate browser
Originheaders if a browser can connect. - Do not expose file, terminal, memory, or secret commands to untrusted clients.
For LAN or internet BYOR:
- Use TLS (
wss://). - Authenticate both sides.
- Restrict which origins, IPs, or users can connect.
- Treat file, terminal, memory, and secret commands as privileged.
When to use Letta Cloud instead
Section titled “When to use Letta Cloud instead”Use Letta Cloud remote environments when you need:
- Remote access over the internet without custom networking.
- Hosted authentication and remote environment selection.
- Multiple clients from different networks.
- Durable remote environment records.
- Less custom infrastructure to maintain.
Use BYOR when you want direct local control, embedded desktop routing, LAN-only remotes, or a private command center inside your own application.