self-host the edge
create the KV namespace, set the secrets, deploy the Worker, register the Telegram webhook.
self-host the edge Worker
the edge Worker is the whole backend: the /api/* routes and the SessionDO Durable
Object, one deploy, one wrangler.toml. deploy it from services/edge.
1. KV namespace
the Worker uses one KV namespace (KRISPY_KV) for tenant config, handoff discovery, the topicβsession map,
and usage counters. create it and paste the id into services/edge/wrangler.toml
(replacing REPLACE_WITH_KV_ID):
bunx wrangler kv namespace create KRISPY_KV2. secrets
the Worker never reads a .env file at runtime. set each secret in Cloudflare with
wrangler secret put (from services/edge):
bunx wrangler secret put TELEGRAM_BOT_TOKEN # BotFather token
bunx wrangler secret put TELEGRAM_CHAT_ID # supergroup id, e.g. -1001234567890
bunx wrangler secret put TELEGRAM_WEBHOOK_SECRET # any random string
bunx wrangler secret put TENANT_SYNC_SECRET # optional: gates /api/tenant/config
bunx wrangler secret put KNOWLEDGE_GATEWAY_SECRET # optional: private retrieval gatewayfor local iteration without deploying, wrangler dev reads these from a git-ignored
.dev.vars file in services/edge.
Telegram creds, TENANT_SYNC_SECRET, and BILLING_SYNC_SECRET are secrets β they belong in
Cloudflare, never in the repo. KV and Durable Object bindings are not secrets; they live in
wrangler.toml. see security.
3. optional vars
set in wrangler.toml under [vars], or as secrets. all optional β code defaults apply:
| var | default | what it does |
|---|---|---|
ALLOWED_ORIGIN | * | CORS allow-origin. lock to your site origin in production. |
AI_MODEL | @cf/meta/llama-3.3-70b-instruct-fp8-fast | override the Workers AI model. |
SYSTEM_PROMPT | β | override the system prompt from env (KV wins if unset). |
MAX_HISTORY_MSGS | 8 | sliding window β prior messages the AI sees. |
MAX_OUTPUT_TOKENS | 256 | hard cap on reply length. |
MAX_AI_TURNS | 10 | AI turns before a forced human handoff. |
RESEND_API_KEY | β | enables lead-email delivery (no key β email no-ops). |
LEAD_EMAIL_FROM | β | verified-domain from-address for lead email. |
KNOWLEDGE_GATEWAY_URL | β | private retrieval gateway URL (HTTPS; loopback HTTP only locally). |
KNOWLEDGE_TENANT_ID | β | exact tenant binding required before retrieval runs. |
KNOWLEDGE_SITE_ID | empty | exact site binding; empty means the default site. |
KNOWLEDGE_TIMEOUT_MS | 250 | bounded gateway deadline, clamped to 2000 ms. |
MAX_HISTORY_MSGS / MAX_OUTPUT_TOKENS / MAX_AI_TURNS are the "turn tax" cost knobs β the
client re-sends the whole history each turn, so these three bound per-turn cost without
changing behavior on normal short chats.
The canonical handoff marker is [!HANDOFF]; a standalone terminal !HANDOFF is accepted
for provider compatibility. Either marker produces a visitor acknowledgement if it is the
entire model reply, so the handoff never leaves an empty bubble.
The optional knowledge gateway is called only from the Worker. It must enforce the same exact tenant/site binding and return bounded cited reference data. A timeout, scope mismatch, malformed response, or gateway error leaves the normal chat path unchanged; no provider setting is exposed through the widget.
4. deploy
cd services/edge
bunx wrangler deploy5. register the Telegram webhook
point Telegram at your deployed Worker so owner replies reach it. the secret_token must
match the TELEGRAM_WEBHOOK_SECRET you set β the Worker rejects any webhook call whose
x-telegram-bot-api-secret-token header doesn't match.
curl "https://api.telegram.org/bot<TOKEN>/setWebhook" \
-d "url=https://krispy-edge.YOU.workers.dev/api/telegram/webhook" \
-d "secret_token=<TELEGRAM_WEBHOOK_SECRET>"6. verify
curl https://krispy-edge.YOU.workers.dev/health # β {"status":"ok","service":"edge"}then embed the widget (embed + theme the widget) with
data-api pointed at your Worker, and drive the loop end to end.