Escalate an AI email agent's thread to a human

Escalate an AI email agent’s thread to a human

将 AI 邮件代理的会话升级给人工处理

Most “AI email agent” demos quietly assume the agent answers everything. Point a model at the inbox, generate a reply, send it, repeat. That’s a fine loop right up until the model hits a message it shouldn’t touch — an angry customer, a legal question, a refund the agent has no authority to approve — and confidently fires off a reply anyway. 大多数“AI 邮件代理”演示都默认代理能处理所有事情。将模型指向收件箱,生成回复,发送,循环往复。这个循环运行良好,直到模型遇到它不该触碰的消息——比如愤怒的客户、法律问题、代理无权批准的退款——然后依然自信地发出回复。

The expensive failures in agent email aren’t the threads the agent gets wrong. They’re the threads the agent answers at all when it should have stepped back. So let’s build the part that steps back. Not the classifier that decides a message is risky — that’s triage, a separate problem. This is the handoff: once something flags a thread as “needs a human,” how do you actually pull the whole conversation out of the agent’s reach, park it where a person can find it, and make sure the agent keeps its hands off until that person clears it? 邮件代理中代价高昂的失败并非源于代理回复错误,而是源于代理本该退避时却进行了回复。因此,让我们构建这个“退避”环节。这并非指判断消息是否有风险的分类器(那是分类问题,属于另一范畴)。我们要解决的是交接问题:一旦某事物将线程标记为“需要人工介入”,你该如何将整个对话从代理的控制中剥离,将其放置在人工可见的地方,并确保在人工处理完毕前代理不再插手?

I work on the Nylas CLI, so the terminal commands below are the exact ones I reach for when I wire up an escalation path. Every operation gets the two-angle tour: the raw curl call and the nylas command that does the same thing. 我负责 Nylas CLI 的开发,因此下文中的终端命令是我在构建升级路径时实际使用的。每个操作都将通过两个视角展示:原始的 curl 调用以及实现相同功能的 nylas 命令。

What the handoff actually needs

交接的实际需求

An Agent Account is, underneath, just a Nylas grant with a grant_id. That’s the spine of everything here, and it’s worth sitting with: there is nothing new to learn on the data plane. The same grant-scoped endpoints you already use — Messages, Threads, Folders, Drafts — work against this grant exactly the way they work against any Gmail or Microsoft grant you got through OAuth. 从底层来看,代理账户(Agent Account)本质上就是一个带有 grant_id 的 Nylas 授权。这是本文所有内容的核心,值得深思:在数据层面没有任何新东西需要学习。你已经在使用的那些基于授权的端点——消息(Messages)、线程(Threads)、文件夹(Folders)、草稿(Drafts)——对于此授权的运作方式,与你通过 OAuth 获取的任何 Gmail 或 Microsoft 授权完全一致。

So the escalation path isn’t some special agent feature. It’s three plain operations you already half-know: 因此,升级路径并非什么特殊的代理功能,它只是你已经略知一二的三个简单操作:

  1. A place to put escalated threads. A custom folder — call it “Needs human” — that lives alongside the six system folders every Agent Account ships with (inbox, sent, drafts, trash, junk, archive).

  2. 放置升级线程的地方。 一个自定义文件夹(命名为“Needs human”),它与每个代理账户自带的六个系统文件夹(收件箱、已发送、草稿、垃圾箱、垃圾邮件、归档)并列存在。

  3. A way to move the whole thread there. Not one message — the thread. A reply is just the latest message in a conversation; a reviewer needs the full chain.

  4. 将整个线程移动到该处的方法。 不是单条消息,而是整个线程。回复只是对话中的最新消息;审核人员需要完整的上下文链条。

  5. A way to stop the agent from replying to anything in that folder until a human signs off.

  6. 在人工确认前,阻止代理回复该文件夹中任何内容的方法。

The honest part up front, because it shapes the whole design: Nylas doesn’t store a “paused” flag for you on an Agent Account. Custom metadata isn’t supported on these grants, so there’s no server-side field you can set to say “hands off this thread.” The pause is your state — a row in your database keyed by thread_id. The folder move is the visible, durable signal a human sees in their mail client; the pause flag is the invisible one your agent checks before it ever drafts a reply. You need both, and they do different jobs. 开诚布公地说,因为这决定了整个设计:Nylas 不会为你存储代理账户的“暂停”标志。这些授权不支持自定义元数据,因此没有服务器端字段可以让你设置“禁止触碰此线程”。暂停状态由你掌控——即你数据库中以 thread_id 为键的一行记录。移动文件夹是人工在邮件客户端中可见的持久信号;而暂停标志则是你的代理在起草回复前检查的隐形信号。你需要两者,它们各司其职。

Before you begin

开始之前

You need an Agent Account and its grant_id. If you don’t have one yet, it’s a single call: 你需要一个代理账户及其 grant_id。如果你还没有,只需一次调用即可:

curl --request POST \
  --url "https://api.us.nylas.com/v3/connect/custom" \
  --header "Authorization: Bearer <NYLAS_API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{
    "provider": "nylas",
    "name": "Acme Support",
    "settings": { "email": "support@yourcompany.com" }
  }'

The response hands back data.id — that’s your grant_id. From the CLI it’s one line: 响应会返回 data.id,这就是你的 grant_id。使用 CLI 只需一行:

nylas agent account create support@yourcompany.com --name "Acme Support"

Create the review folder

创建审核文件夹

The destination has to exist before you can route anything to it. Create a custom folder once, at setup time, and reuse its ID forever. 在路由任何内容之前,目标必须存在。在设置时创建一次自定义文件夹,并永久复用其 ID。

curl --request POST \
  --url "https://api.us.nylas.com/v3/grants/<GRANT_ID>/folders" \
  --header "Authorization: Bearer <NYLAS_API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{ "name": "Needs human" }'

From the CLI it’s one line: 使用 CLI 只需一行:

nylas email folders create "Needs human"

Move the whole thread, not just the message

移动整个线程,而非单条消息

Here’s the part people get wrong. When something flags a conversation for human review, the instinct is to move the message that triggered the flag. But a reviewer opening “Needs human” needs the whole exchange — what the agent said, what the customer said back, the original request three messages up. Move one message and you’ve handed your colleague a single confused reply with no context. So you move the whole thread. 这是人们容易出错的地方。当某事物标记对话需要人工审核时,直觉是移动触发标记的那条消息。但审核人员打开“Needs human”文件夹时,需要的是整个交流过程——代理说了什么、客户如何回复、三条消息之前的原始请求。只移动一条消息,你给同事留下的只是一条毫无上下文的困惑回复。因此,你需要移动整个线程。