Blog

Luis Majano

September 04, 2026

Spread the word


Share your thoughts

Every AI agent that touches something real eventually needs a human in the loop. Someone has to approve the delete, confirm the wire transfer, or just say "go ahead" before a tool call runs. The question BoxLang AI 3.4.0 answers is: approve it how? A terminal prompt? A webhook? A Slack button?

The answer is the new Gateway SPI, IGateway, a single interface that normalizes every one of those into the same shape.

What a gateway actually is

A gateway is a bidirectional adapter. In one direction, it turns a platform event, a keystroke, an HTTP POST, a button click, into normalized agent input. In the other, it turns agent events, including a suspended approval request, back into something native to that platform.

Every gateway implements IGateway. Every capability method has a safe default, so a gateway only needs to override what it actually supports:

// Capabilities a gateway can declare
inboundMessages, outboundMessages, streaming, threads,
attachments, messageEditing, interactiveActions,
humanApproval, argumentEditing, authentication

A gateway that only handles approvals doesn't need to implement streaming. One that only streams doesn't need to know what argument editing is. Nobody pays for capabilities they don't use.

Resolving a gateway

The aiGateway() BIF resolves a gateway by name, core or externally registered:

cli  = aiGateway( "cli" )
http = aiGateway( "http", { secret: "shared-hmac-secret" } )

Three gateways ship in the box:

GatewayWhat it does
CliGatewayBlocking stdin/stdout approval prompt, now with approve_always/approve_session, not just approve/reject/quit
HttpGatewayHMAC-SHA256 signed requests, timestamp tolerance + nonce dedup, TTL-bounded pending interactions, atomic decision claims
MockGatewayIn-memory gateway for tests and examples

That last point on HttpGateway matters more than it sounds. A duplicate decision POST for an interaction that's already resolved gets rejected outright, not silently overwritten. Two people can't race to approve or reject the same request and have the second write clobber the first.

We also have tons of new gateways that are coming on our new agent framework to be released soon: BxAgents from WhatsApp, Signal, Slack, Discourse, Email, Telegram, etc.

Attaching a gateway to an agent

Gateways plug straight into HumanInTheLoopMiddleware:

aiAgent(
    middleware  : new HumanInTheLoopMiddleware( gateway: aiGateway( "http" ) ),
    checkpointer: aiMemory( "cache" )
)

Swap aiGateway( "http" ) for aiGateway( "cli" ) in a local dev script and nothing else in your agent code changes. The approval flow, the middleware, the checkpointer, none of it knows or cares which gateway is behind it.

Building your own

External platforms, Slack, Discord, Teams, whatever your team runs on, ship as their own modules and register a gateway instance under their own name:

aiGatewayRegistry().register( new MyPlatformGateway(), "my-platform" )
myGateway = aiGateway( "my-platform" )

aiGateway() can also auto-register the instance it constructs, so you don't have to call the registry yourself:

myGateway = aiGateway( name: "http", register: true, module: "my-module" )

To implement IGateway yourself, you get lifecycle hooks for free by extending BaseGateway, which tracks isRunning() and fires onGatewayConnect/onGatewayDisconnect automatically. Override onStart()/onStop(), never start()/stop() directly.

Why this matters

Before 3.4.0, "how does a human approve this" was a decision baked into your middleware code, tied to whatever transport you happened to build first. Now it's a resolvable object. Write your HITL logic once, then decide at deploy time whether approvals come in over a terminal, a signed webhook, or a chat platform your team hasn't built yet.

Next in the series: what happens on the other side of that gateway, the fully rebuilt Human-in-the-Loop subsystem, and the durable grants that mean nobody gets asked the same question twice.

Docs: Middleware, Human-in-the-Loop · Gateways

Add Your Comment

Recent Entries

BoxLang 1.17 Series Part1 : Module Inception

BoxLang 1.17 Series Part1 : Module Inception

BoxLang has always been an extensible language. As of 1.17.0, it is a hierarchically extensible one. Every module ecosystem you have worked in is flat. You declare a list of dependencies and something outside the language, a package manager or a build tool, resolves and downloads and orders them. The language itself has no opinion about the shape of the graph. It sees a list, not a tree.

Luis Majano
Luis Majano
September 04, 2026
Why Migrate from ColdFusion to BoxLang? 10 Reasons to Modernize Your CFML Applications

Why Migrate from ColdFusion to BoxLang? 10 Reasons to Modernize Your CFML Applications

For organizations running applications built with ColdFusion or CFML, modernization often comes with an uncomfortable question:

“Do we keep investing in the platform we already have, or do we rewrite everything in another language?”

That choice is not as binary as it may seem.

At Into the Box 2026, Ortus Solutions Senior Architect and BoxLang core developer Jon Clausen presented a session titled “Top Ten Reasons to Migrate to Bo...

Cristobal Escobar
Cristobal Escobar
September 04, 2026
Be a rebel, but trust us on this: use ColdBox. You'll thank us later!

Be a rebel, but trust us on this: use ColdBox. You'll thank us later!

CFML developers have always had a bit of a rebellious streak.

That's part of what makes CFML great. It gives you enormous freedom to build applications quickly and structure them almost any way you want.

So yes, you can build a successful ColdFusion application without a framework. Many developers have, and some of those applications have been running for decades.

But trust us on this one:

Use a framework.

And if you're building or maintaini...

Cristobal Escobar
Cristobal Escobar
September 03, 2026