Architecture

How the SDK, the runtime on Cloudflare and the dashboard fit together, and what happens on each message.

Components

┌──────────────┐   WebSocket / SSE / HTTPS    ┌──────────────────────────────┐
│  Your app    │ ───────────────────────────▶ │  api.kletso.ai (Worker)       │
│  kletso_core │ ◀─────────────────────────── │  · sessions, keys, CORS       │
│  kletso_flutter                            │  · realtime proxy             │
└──────────────┘                              │  · agent loop + tools         │
                                              │  · triggers + push (FCM)      │
┌──────────────┐   HTTPS (cookie session)     │  ┌────────────────────────┐   │
│  Dashboard   │ ───────────────────────────▶ │  │ Durable Objects        │   │
│ app.kletso.ai│   via kletso-admin Worker    │  │ · ConversationDO       │   │
└──────────────┘                              │  │ · EndUserDO            │   │
                                              │  │ · TenantDO (limits)    │   │
                                              │  └────────────────────────┘   │
                                              │  D1 (control plane) · KV      │
                                              └──────────────┬───────────────┘
                                                             │ HTTPS, as the user
                                                    ┌────────▼────────┐
                                                    │ Your APIs (tools)│
                                                    │ Model provider   │
                                                    └──────────────────┘
  • Worker (kletso-api). Stateless HTTP and WebSocket entry point. Validates keys and session tokens, enforces CORS for /v1/*, proxies realtime sockets to the conversation’s Durable Object, and hosts the control-plane API the dashboard uses.
  • ConversationDO. One per conversation. Holds the ordered event log and messages in SQLite, accepts hibernating WebSockets, replays events after a given sequence number on reconnect, runs agent turns, executes tool calls, evaluates confirmations and emits app.notify.
  • EndUserDO. One per end user. Fans out user-level notifications to every open conversation socket of that user and tracks push tokens.
  • TenantDO. Per-project rate limits (for example 30 user messages per minute per user).
  • D1 kletso-control. Organisations, projects, environments, agents and versions, tools, components, triggers, secrets, API keys, members, end users and conversation summaries.
  • KV CONFIG. Cached published configuration for fast reads on the hot path.

What happens on a message

  1. The SDK sends a message frame (or POST /v1/conversations/:id/messages) with a clientId for idempotency.
  2. The DO appends message.created for the user, checks the per-user rate limit, then starts a turn and emits agent.typing.
  3. The agent loop builds the prompt from the published agent version: system prompt with {{ context.* }} variables (public keys only), the tone, the allowed component catalogue, the last N messages, and the tool specs (your HTTP tools plus the built-ins render_ui, notify_app, app_command, handoff).
  4. The provider streams tokens. Text deltas become message.delta events. Tool calls become tool.started; HTTP tools run with the SSRF guard, templated URL and headers, and the user’s identity when actAsUser is set.
  5. Tools that require confirmation pause the turn and render a confirmation surface. The turn resumes when the user approves or declines.
  6. render_ui results are validated against the component allowlist and emitted as ui.render. The loop continues until the model stops or maxToolRounds is reached.
  7. message.completed closes the turn with usage, cost, latency and finish reason. Every event carries a sequence number; clients that reconnect send the last one they saw and receive the rest.

Environments and versions

A project has a development and a production environment, each with its own API keys. Agents are edited as a draft and published to an environment as an immutable version. The SDK’s publishable key selects the environment, so a build pointed at the development key sees development versions and nothing else changes in your app.

Where data lives

  • Conversation events and messages: in the conversation’s Durable Object (SQLite), on Cloudflare’s network.
  • Control-plane data: D1.
  • Secrets (model keys, tool credentials, push credentials): D1, encrypted with a per-organisation data key that is itself wrapped by a Worker secret. See Security model.
  • Nothing is sent to a model provider except what the agent needs for the turn: the prompt, recent messages, tool specs and tool results.

Last updated 2026-09-28 · Report an issue with this page