AI Harness Engineering · OpenClaw 1 of 7 · Anatomy of an Agent Gateway

The Gateway — One Daemon, Many Surfaces

You want one AI agent that you can talk to from WhatsApp, Telegram, Slack, your terminal, and your phone — all at once, all sharing memory. The architectural answer is a single long-lived process: the gateway. This lesson builds the mental model that the next six lessons stand on.

Prerequisites: you have used a chat app and you know roughly what an API and a server process are. A little Python for the labs. No networking or systems background assumed.
8
Chapters
3
Simulations
3
Code Labs

Chapter 0: Why — One Brain, Many Mouths

The whole series in one sentence. You will learn how to build the piece of software that sits between your chat apps and an AI agent — receiving messages from WhatsApp, Telegram, Slack, Discord, Signal, iMessage and a web chat, running an agent that can use tools and remember context, and streaming replies back — all from one process you run yourself. That process is the gateway, and OpenClaw is our worked example of one.

Picture what you actually want. You have an assistant. You'd like to message it from your phone's WhatsApp on the bus, from Slack at work, and from your laptop terminal at home — and you want it to be the same assistant each time, with the same memory, the same tools, the same ongoing conversation. Not seven disconnected bots. One brain, many mouths.

Now try to build that the obvious way, with no central piece. Each chat app would need its own little program holding its own connection and its own copy of the conversation. Seven apps × three of your devices is a tangle of independent, stateful connections that have no idea about each other. Your terminal can't see the conversation your phone is having. Worse: some of these connections are jealous — WhatsApp, for instance, lets exactly one client hold a session at a time. Two programs fighting over it get you logged out.

The core tension. A messaging integration is a long-lived, stateful connection (a logged-in session, a socket the provider keeps open). You cannot freely duplicate it across devices and apps. But you want to reach your agent from many devices and apps. Something has to own the connections in one place and multiplex access to them. That something is the gateway.

The fix is the oldest idea in distributed systems: stop wiring everything to everything. Put a hub in the middle. One process owns every provider connection. Everything else — your terminal, your phone app, the web UI — becomes a thin client that connects to the hub and asks it to do things. The slider below shows why this matters as you scale.

The wiring explosion: mesh vs. hub

Left, the no-hub world: every message surface (a chat app) wired directly to every client (a device/UI) — that's a full mesh, and the connection count grows like the product N×M. Right, the gateway: every surface and every client connects only to the hub — the count grows like the sum N+M. Drag the slider and watch the left side choke.

devices each side4

That single decision — one process owns the connections, everything else is a client of it — cascades into every other lesson in this series: the protocol those clients speak (Lesson 2), how the hub decides who is allowed to connect (Lesson 3), how it runs the agent (Lessons 4–5), and how it keeps overlapping messages from colliding (Lessons 6–7). Get the gateway picture and the rest has a place to live.

What is the fundamental reason you can't just run a separate copy of each chat-app integration on every device?

Chapter 1: One Daemon, One Session

The gateway is a daemon — a program that starts once and keeps running in the background, indefinitely, with no terminal attached. (You may have met daemons before: sshd serves SSH, dockerd runs containers. The "d" is for daemon.) You start it with a command like openclaw gateway, and from then on it lives in the background, holding all the connections.

Why must it be long-lived rather than spun up per request? Because the things it holds are themselves long-lived. A logged-in WhatsApp session, an open WebSocket to Telegram, the in-memory state of an ongoing agent run — none of these survive being torn down and recreated for every message. The daemon is the container that keeps them alive between messages.

The defining rule: one gateway per host. On a given machine, exactly one gateway process runs, and it is the sole owner of each provider connection. OpenClaw states this as a hard invariant for WhatsApp specifically: exactly one gateway controls a single Baileys session per host (Baileys is the open-source WhatsApp Web library it uses). Two gateways on one host both trying to drive WhatsApp is not a degraded experience — it's a broken one.

Here is the concrete failure, because "single-owner session" sounds abstract until it bites. WhatsApp Web binds your account to one active client. If a second program logs in with the same credentials, the first is kicked, reconnects, kicks the second, and so on — a flapping loop that WhatsApp's anti-abuse systems read as suspicious and can answer by banning the number. So "one gateway owns the session" is not a stylistic choice; it is the price of not getting locked out. We'll make this invariant concrete — and enforce it in code — in Chapter 6.

Because the daemon is the single point that everything depends on, you don't usually babysit it by hand. You hand it to a supervisorlaunchd on macOS, systemd on Linux — whose entire job is "keep this one process alive: start it at boot, restart it if it crashes." One process, supervised, owning everything. That's the spine.

Where the gateway listens. The daemon opens a server on a network address — by default 127.0.0.1:18789. The 127.0.0.1 part (loopback) means "this machine only": by default the gateway is reachable from programs on the same computer and nobody else. That default is a security decision we'll unpack in Lesson 3; for now just note that the gateway is a server, and clients reach it at an address.
OpenClaw enforces "exactly one gateway controls a single Baileys (WhatsApp) session per host." Why is this a hard invariant rather than a nice-to-have?

Chapter 2: Control Plane — Operators and Nodes

If the gateway owns everything, then everything else is a client of it. This is the second big idea: the macOS app, the command-line tool, the web UI, your automations — none of them are "the system." They are all just clients that connect to the one gateway and ask it to act. Swap the app for the CLI and you are talking to the very same brain.

Borrowing language from networking, we split the world into two planes. The control plane is where intent flows: "run the agent on this message," "show me the chat history," "change this setting." The data plane is where the heavy, stateful work actually happens: the provider connections, the running agent, the session transcripts. Clients live in the control plane; the gateway is the data plane. Thin clients, fat hub.

Two kinds of client, by role. When a client connects, it declares a role. There are two:
  • Operator — a control-plane client that drives the gateway: the CLI, the desktop/web UI, an automation script. Operators issue commands and read results. "Run the agent." "List sessions." "Patch the config."
  • Node — a capability host: a device that offers abilities to the gateway. A paired phone might offer its camera; a Mac might offer screen capture or the ability to run a shell command. Nodes don't drive the agent; they lend it hands and eyes.

This operator/node split is worth pausing on, because it inverts the usual direction. An operator asks the gateway to do things. A node lets the gateway do things on it. The desktop app is an operator. Your phone, paired so the agent can snap a photo, is a node. The same physical laptop can even connect as both, in two separate connections with two different roles. We'll give nodes their own chapter (Chapter 4), because the abilities they expose are exactly where the security weight sits.

One more surface belongs here: the gateway can also serve a little web UI of its own. OpenClaw calls this the canvas host — static pages it serves under internal paths (you'll see URLs like /__openclaw__/canvas/). The point isn't the path; it's that "a browser tab showing the agent" is, again, just another operator client of the same gateway. Everything is a client. There is only ever one hub.

A paired phone that lets the agent take a photo, and the desktop app you type commands into, connect with two different roles. Which is which?

Chapter 3: Surfaces as Adapters

Now look at the other side of the hub: the chat apps. OpenClaw calls each one a surface — WhatsApp, Telegram, Slack, Discord, Signal, iMessage, and a built-in WebChat. Each is a place a human message can surface into the system. And each speaks a completely different dialect.

WhatsApp is driven through Baileys; Telegram through its Bot API (OpenClaw uses a library called grammY); Slack and Discord have their own SDKs and event shapes; Signal and iMessage are different again. A "new message" event from Telegram looks nothing like one from Slack. If the rest of the gateway had to know about all seven dialects, it would be an unmaintainable mess.

The adapter pattern. Each surface is wrapped in an adapter whose one job is translation: turn that provider's idiosyncratic events into one internal, normalized shape, and turn the gateway's internal "send this reply" into that provider's specific API call. The gateway core then speaks a single vocabulary — it never sees a raw Telegram update or a raw Slack payload. Add a new chat app later? Write one adapter; the core doesn't change.

What is that single internal vocabulary? The gateway emits a small set of event kinds that everything downstream understands — among them chat (a message moved), agent (the agent produced output), presence (who's online), health (is the system OK), heartbeat and cron (timed beats and scheduled jobs). Seven dialects in; one clean event stream out.

Here is the move that makes a hub a hub, though: when a message arrives on one surface, the gateway doesn't just hand it to the agent — it can fan it out to every interested client. Your phone app and your desktop app both subscribe; both light up. This is the multiplexing we promised in Chapter 0, made real: one inbound message, normalized once, delivered to many. Let's build exactly that wiring.

Why is each chat app wrapped in an "adapter" instead of letting the gateway core handle each provider directly?

Chapter 4: Capability Nodes — Lending the Agent Hands

Operators tell the gateway what to do. Nodes do the opposite: they let the gateway reach out and act in the physical or local world through a device. A node is a phone, a Mac, an Android tablet, or a headless box that connects to the gateway and says, in effect, "here are the things I can do for you."

What it can do is declared precisely at connect time, in three nested lists — and the nesting is the whole security story:

caps → commands → permissions.
  • caps — broad categories of ability the node offers: camera, screen, location, voice, canvas, talk. "I am the kind of device that has a camera."
  • commands — the specific invocations the node will accept: an allowlist like camera.capture or system.run. If a command isn't on this list, the node refuses it outright.
  • permissions — granular toggles that must be granted for a command to actually fire: screen.record, camera.capture. A command can be allowlisted yet still blocked because its permission was never turned on.

When the agent wants the node to do something, the gateway issues a node.invoke. The node checks the request against its own declaration: is this command on my allowlist and is the required permission granted? Both must be true. Either alone is not enough. This is capability-based security — a node can only ever do what it explicitly advertised it would do, nothing more.

Why this matters so much: a paired Mac node is remote code execution. If a macOS node advertises the system.run command, then "the agent" can run shell commands on that Mac. That is RCE — remote code execution — on a real machine you own. The capability declaration is the fence around it. This is also why, in Lesson 3, we'll treat pairing a node as one of the highest-trust actions in the whole system: you are handing the agent a pair of hands.

Let's implement the gate itself — the check that runs on every node.invoke. It's small, but getting it exactly right (both conditions, no shortcuts) is the difference between a fence and a hole.

A node allowlists the command camera.capture but the camera.capture permission was never granted. What happens on node.invoke?

Chapter 5: Staying in Sync — Events, Presence, Heartbeats

A client that only ever asks the gateway questions would be blind between questions. But things happen on their own: a message arrives, a node goes offline, the agent finishes thinking. So the gateway doesn't just answer requests — it pushes events to its connected clients, unprompted, the moment something changes. That's how your desktop app updates the instant a WhatsApp message lands without you refreshing.

We met the event vocabulary in Chapter 3; now watch it flow. The gateway continuously emits a stream: chat when a message moves, agent when the agent speaks, presence for who's online, health for system status, and a steady heartbeat/tick that says "I'm still alive." Each event carries a sequence number — a simple counter, 1, 2, 3, … — so a client can tell what order things happened in.

The crucial design choice: events are not replayed. The gateway streams events live, but it does not keep a durable log you can rewind. If a client misses an event — its connection blipped, a packet dropped — the gateway will not resend the old one. Instead the client notices the gap (the sequence number jumped from 7 straight to 9) and does the only safe thing: it throws away its possibly-stale view and asks the gateway for a fresh snapshot. Refresh, don't replay.

Why design it this way? Keeping a perfect, replayable, per-client event log is expensive and fiddly — you'd need durable storage, retention policies, and acknowledgements. "Detect a gap, then refetch the current truth" is dramatically simpler and is always correct, because the snapshot you refetch is the real current state, not a reconstruction. You trade a little extra work on the rare gap for a system that can never quietly drift out of sync. Play with it:

The live event stream — and what a gap does

The gateway (left) emits numbered events that flow to a client (right), which tracks the last sequence number it saw. Press Drop an event to make one go missing in transit. When the next event arrives with a skipped number, the client detects the gap and refreshes — it doesn't ask for the lost event back.

Streaming live events…

This "refresh on gap" rule looks like a small networking detail, but it shapes how every client in the system is written: assume your view can go stale, watch the sequence numbers, and be ready to resync from scratch. We'll see the exact wire-level machinery — the sequence fields, the keepalive tick, the reconnect logic — in Lesson 2.

A client sees event sequence numbers jump from 7 to 9 — it missed number 8. What does it do?

Chapter 6: The Three Invariants — and How They Fail

An invariant is a rule the system promises is always true, no matter what. The gateway's whole design rests on three of them. Naming them gives you a checklist for reasoning about any agent gateway — and each one, broken, is a specific real failure.

Invariant 1 — one gateway owns each provider session (per host). Exactly one process controls the single Baileys/WhatsApp session. Broken: two owners flap and you risk a ban (Chapter 1).

Invariant 2 — the handshake is mandatory. The very first thing a client sends must be a valid connect message. Anything else — non-JSON, or a different message first — gets the connection hard-closed immediately. Broken (if it weren't enforced): unauthenticated or malformed clients could poke at the system before proving who they are.

Invariant 3 — events are never replayed. Clients refresh on gaps; the gateway keeps no rewindable log (Chapter 5). Broken (if violated): clients could silently act on a stale, half-missing view of the world.

Invariant 2 is your first taste of the protocol mindset we'll live in next lesson: the server is strict on purpose. A connection that doesn't immediately speak the expected first word is not coddled or corrected — it's dropped. Strictness at the door is cheaper and safer than guessing at intentions later.

Invariant 1 is the one we can make concrete right now. "Exactly one owner" is a classic mutual-exclusion problem: a shared resource (the session) and competitors (gateway processes) where letting two in at once corrupts everything. Let's watch it break, then fix it with the one rule that makes it hold.

A client's very first frame to the gateway is a random non-connect message. By Invariant 2, what happens?

Chapter 7: Showcase & Connections

Here is the whole picture in motion. Surfaces on the left, the gateway in the middle, operator clients and capability nodes on the right. Press Inbound message to watch a message surface on a random app, flow into the gateway, fan out to every operator client, and come back as an agent reply. Toggle the Mac node to see a capability host appear. This single diagram is the map for the next six lessons.

The live gateway — one hub, many surfaces and clients

Teal = message surfaces (chat apps). Orange = the gateway daemon. Blue = operator clients. Green = capability nodes. Watch an inbound message normalize at the hub and fan out; the small ticks on the gateway are presence/health heartbeats.

Idle. Press “Inbound message”.
The spine of the series, in one breath. One daemon owns the stateful provider connections (Ch 1). Everything else is a client — operators that drive it, nodes that lend it capabilities (Ch 2, 4). Each chat app is a surface behind an adapter that normalizes seven dialects into one event stream (Ch 3). The gateway pushes events with sequence numbers; clients refresh on gaps (Ch 5). Three invariants hold it together: one owner per session, mandatory handshake, no replay (Ch 6).

Where each thread continues

Related lessons on this site

The Feynman test. Close the lesson and explain to a friend: why one process owns the connections; the difference between an operator and a node; what an adapter normalizes; why events are pushed but never replayed; and the three invariants — including which real failure each one prevents. If you can, you own the architecture, and the rest of the series is detail hung on this frame.
In one sentence, what IS the gateway?