Skip to main content
The gateway is the entry point for every conversation. It connects one or more messaging channels, hands each incoming message to the AI backend, and delivers the reply back through the same channel. It also runs a proactive system that can reach out to you without waiting for you to send a message first.

How it flows

You (Telegram / WhatsApp / Discord)
  |
  v
Gateway -> router -> backend session
  |                    |
  |                    v
  |              Nuggets memory stack
  |              - FHRR facts
  |              - graph notes
  |              - rewrite pass
  |
  +-> heartbeat
  +-> cron
  +-> maintenance events
  +-> sticky skill state per conversation

Normal flow

1

You send a message

A message arrives over Telegram, WhatsApp, or Discord.
2

The gateway checks the allowlist

Before processing anything, the gateway verifies that the sender is on the allowlist for that channel. Messages from unknown senders are silently dropped.
3

The router enqueues the message

The router places the message in a per-conversation queue. This serializes concurrent messages so the agent always handles one message at a time per conversation.
4

The backend responds

The agent session processes the message, queries memory as needed, and returns a reply.
5

The gateway delivers the reply

The reply is sent back through the same channel using the registered sender for that conversation.

Proactive flow

The gateway also fires proactive events without waiting for a message from you.
1

A heartbeat or cron event fires

The heartbeat timer or cron scheduler pushes a ProactiveEvent onto the event queue.
2

The router handles the event

The event is enqueued for the target conversation and dispatched to the backend.
3

The agent decides whether to send

For heartbeat and maintenance events, the agent only sends a message if it has something meaningful to say. If the response is empty or contains NOTHING, the gateway stays silent.

Event queue

All proactive triggers — heartbeats, cron jobs, timers, and maintenance runs — flow through a single EventQueue. The queue emits each event synchronously as it arrives, and the router serializes processing per conversation using a per-JID promise chain. Event types:
TypeSourceDescription
heartbeatHeartbeatManagerFires when the user has been inactive for the heartbeat interval
cronCronSchedulerFires when a cron expression matches the current time
timerCronSchedulerOne-shot variant of a cron job
maintenanceHeartbeatManager or CronSchedulerSilent daily memory reflection pass
webhookReserved for future use

Channels

Telegram

Uses the grammy library for polling. Requires TELEGRAM_BOT_TOKEN and supports an optional chat ID allowlist.

WhatsApp

Uses @whiskeysockets/baileys. Connects via a QR code scan and persists the session to disk so you only pair once.

Discord

Zero-dependency implementation using Node.js 22+ built-in WebSocket and fetch. Supports DMs and server channels with optional mention gating.

Proactive system

The gateway runs three proactive processes automatically after startup.

Heartbeat

The HeartbeatManager registers a timer for every conversation the bot has had. When the timer fires, the agent checks memory for anything worth surfacing — pending tasks, reminders, or useful information discovered since the last interaction. If it finds nothing, it stays silent.
SettingDefaultDescription
HEARTBEAT_INTERVAL_MS1800000 (30 min)How often the heartbeat fires per conversation
QUIET_HOURS_START22 (10 PM)Hour at which the heartbeat stops firing
QUIET_HOURS_END8 (8 AM)Hour at which the heartbeat resumes
The heartbeat resets every time you send a message, so it only fires during genuine periods of inactivity.

Cron scheduler

The CronScheduler evaluates all cron jobs once per minute (configurable with CRON_EVAL_INTERVAL_MS). Jobs are stored as JSON in .gateway/cron/jobs.json and survive restarts. The agent can add and remove jobs at runtime by writing to .gateway/cron/requests.jsonl.
SettingDefaultDescription
CRON_EVAL_INTERVAL_MS60000 (1 min)How often the scheduler evaluates cron expressions

Daily memory reflection

When the first conversation is registered, the cron scheduler installs a hidden daily job that runs reflectAndCleanMemory() at 9:00 AM. This pass inspects up to 10 notes, merges near-duplicates, improves tags and links, and archives low-value entries.
SettingDefaultDescription
MEMORY_REFLECTION_HOUR9Hour at which the reflection job runs
MEMORY_REFLECTION_MINUTE0Minute at which the reflection job runs
MEMORY_REFLECTION_MAX_NOTES10Maximum notes inspected per reflection pass
The daily reflection job is hidden — it does not appear in the cron job list. It only sends you a message if the cleanup surfaces something you genuinely need to know.

Allowlist and security

Each channel has its own allowlist:
ChannelVariableBehavior when empty
WhatsAppGATEWAY_ALLOWLISTOpen — accepts messages from any JID
TelegramTELEGRAM_ALLOWLISTOpen — accepts messages from any chat ID
DiscordDISCORD_ALLOWED_USER_IDSClosed — denies all messages
WhatsApp and Telegram allowlists default to open when the variable is empty. Set them to your own JID or chat ID before exposing the gateway to external networks.
The gateway exits at startup if no channel is configured. At least one of GATEWAY_ALLOWLIST, TELEGRAM_BOT_TOKEN, or DISCORD_BOT_TOKEN must be set.

Build docs developers (and LLMs) love