Gateway
The Gateway is the protocol edge — the boundary between email and the engine. Everything about mail lives here, and nothing else does: routing an inbound message to the right agent, guarding against loops and unwanted senders, and turning a reply back into correctly-threaded outbound mail. The inference core never learns what a mailbox is, and the Gateway never learns what a model is.
Inbound
Every received email passes through a fixed sequence of gates before it can reach inference. Each gate is a hard stop: the message is dropped, bounced, or accepted, and only an accepted message becomes a run.
Inbound email (customer → support@thescoop.com)
│
▼
recursion limit reached? ── yes ─► silently drop
no agent owns the address? ── yes ─► bounce (unrouted)
sender not allowed in? ── yes ─► bounce (access denied)
attachments over the limit? ── yes ─► bounce (attachment rejected)
│
└─ accepted ─► enqueue an inference run- Recursion guard. Every email an agent sends is stamped with a hop count —
1on fresh mail, the inbound message's count plus one on a reply. Human mail clients never echo the header back, so a person replying anywhere in the conversation resets the chain; only an unbroken agent-to-agent reply chain accumulates. An inbound email whose count has reached the limit (config.recursion_limit, default 50) is silently dropped — this is what stops two agents from replying to each other forever. - Routing. Recipients are gathered from both
To:andCc:— an agent copied on a reply-all is still reached — and each address is matched to an agent, honouring plus-tag subaddressing, sosupport+order42@thescoop.comstill routes to Sundae atsupport@thescoop.com. Every addressed agent gets its own independent run: an email sent to both Sundae and Scout is handled by each on its own. When no address matches, the email bounces as unrouted. - Access guardrail. The sender is checked against each addressed agent's inbound access rules before the message reaches inference. This is how Scout stays reachable only from inside
@thescoop.com. The check is per-agent, so one email can reach some of its addressed agents and be refused for others. See Security for how the layers compose. - Attachment limits. Oversized or too-numerous attachments are rejected at the edge, before anything is stored.
The Gateway only claims mail for domains Protege actually receives for, so mounting the engine never hijacks the host app's other inbound mail.
Outbound
Agents never talk to a mail transport. An agent's plain assistant text is never delivered on its own; the only way a message leaves the building is the send_email tool, which hands the composed mail to the Gateway. That single exit point is where the outbound guarantees live:
- Identity is enforced. Every send is re-stamped with the sending agent's own address — an agent cannot impersonate anyone else. Sundae's replies always come from
support@thescoop.com. - Threading is preserved. A reply inherits the original subject and threading, so it lands back in the same conversation rather than starting a new one.
- Transport is chosen automatically. Console conversations stay in-app; real mail goes out over SMTP. The inference core doesn't know or care which.
- History is recorded. The outbound message is persisted on its thread, with attachments stored by reference so a file is uploaded once and thereafter only pointed at.
Threading
A conversation is a thread, identified by the mail's own message-id and reference chain. An inbound message and the reply it triggers resolve to the same thread, which is what gives an agent conversational memory for free — the next turn can replay everything said so far (see Resolvers and the Harness).
Related
- Security — the access guardrail and what the engine enforces vs. what you own.
- Mail transport — provider vs. self-hosted SMTP, DNS, and DKIM.
- Invoking from your app — handing an agent work from host code.