Skip to content
Letta Code Letta Code Letta Docs
Sign up

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.

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);
}
}

Start the stock Letta Code remote listener with LETTA_BASE_URL pointing at your self-hosted server:

Terminal window
LETTA_BASE_URL=http://localhost:8284 \
IGNORE_SELF_HOSTED_LISTENER_ERROR=1 \
letta remote --env-name local-dev --backend local

LETTA_BASE_URL is the custom server endpoint.

For deeper command and event details, see the API reference.

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,
},
});

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);
}
}

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 sync to 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.

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 seq stamping 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 client

But it is not required for BYOR.

For local BYOR:

  • Bind to 127.0.0.1 unless 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 Origin headers 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.

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.