把 Managed Agents 接入生产:MCP 工具集接入、配置注入(会话环境变量与 Vault 密钥库)、Deployment 定时触发、人工审核(HITL)与资源生命周期管理,示例统一用 curl。
概述
本篇讲 Managed Agents 的生产化改造:MCP 工具集接入(市场注册制)、配置注入(会话环境变量 + Vault 密钥库)、触发平台化(Deployment:手动 / cron 定时 / initial_events)、人审(HITL)、资源生命周期管理,以及数据驻留的当前行为。示例统一用 curl。
凭证注入的深度内容(Vault 占位符安全模型、出网网关、allowed_hosts)拆到进阶:密钥库安全注入与出网网关——本篇只用最短路径把凭证接起来。
场景:云岭茶饮的晨报机器人
「云岭茶饮」是一家虚构的连锁茶饮品牌,运营团队每天早上要做的第一件事,是翻一遍行业动态:竞品上了什么新品、供应链有什么风吹草动、有没有涉及茶饮的负面舆情。这件事重复、耗时、容易漏——典型该交给自动化的任务。要搭一个晨报机器人,把生产化的几块基础设施全部串起来:
| 需求 | 用什么能力 |
|---|---|
| 每天早上 7:30 自动跑,不用人守着 | Deployment(cron 定时触发) |
| 触发时自动带上当天的任务说明 | Deployment(initial_events 触发时下发) |
| 搜索行业动态 | MCP 工具集(市场里的 WebSearch 服务) |
| 把晨报推送到公司 GitHub 仓库 | Vault 密钥库(注入 GITHUB_TOKEN,占位符 + 出网网关替换) |
| 遇到重大负面舆情,先停下等人确认 | HITL(requires_action + 轮询) |
| 换季改 prompt、季度末停用、清理资源 | 版本管理 + 资源生命周期 |
为什么定时链路用 Vault 而不是会话环境变量?因为 Deployment 不支持
environment_variables(传了被静默忽略,触发的 Session 里无此变量)——无人值守链路的凭证注入只有 vault_ids 一条路。应用侧自建 Session 的场景优先用环境变量(见 步骤 4)。前置
- 一个阿里云百炼工作空间,及其工作空间 ID;
- 一把 DashScope API Key(一把 Key 覆盖工作空间内全部资源);
- 一枚 GitHub PAT(演示用假值
GITHUB_TOKEN表示——本篇只需要验证「注入」这个动作,用假 token 更安全)。
Managed Agents 当前仅
cn-beijing 单区域。所有请求带 Authorization: Bearer $DASHSCOPE_API_KEY,响应带 x-request-id 便于排查(请求体里也回显 request_id 字段)。原理:两条扩展线 + 一条触发线
给 Agent 扩展能力,生产上真正常用的是两条互相独立的线:
- MCP 工具集:Agent 声明一个(或多个)阿里云百炼 MCP 市场里的服务,运行时平台代替 Agent 去调那个服务的工具。适合「服务在公网、由阿里云百炼市场托管」的场景——不用管连接细节,也(通常)不用管 key。
- 配置/凭证注入(两条路,按敏感级分层):
- 会话环境变量(
environment_variables,主推):建会话时传键值对,明文直通沙箱进程,Agent 在 bash / python 里os.environ拿到真实值。适合非敏感配置与交互式会话——最简单,不用预建任何资源。限制:Deployment 不支持。 - Vault 密钥库:密钥预先存进 vault,会话只带
vault_ids。沙箱内拿到的是占位符(BMA_SECRET_PLACEHOLDER_*),真实值仅在出网网关命中该密钥的allowed_hosts白名单时替换Authorization头。适合高敏密钥与无人值守链路。完整安全模型见进阶:密钥库安全注入与出网网关。
- 会话环境变量(
function_call 事件把调用抛回应用,应用算完再用 function_call_output 回填。服务只在内部网络可达时用这条。
本篇再补上触发线:入门篇的用法里,Session 由应用创建、消息由应用发——「谁在早上 7:30 把任务发出去」只能靠自写的定时脚本。Deployment 把这个角色交给平台:声明式地配好「哪个 Agent、锁哪个版本、触发时下发哪些消息、挂哪些凭证、什么时间表」,之后定时触发、Session 创建、initial_events 下发全部由平台完成,应用只需要来看结果。
安全边界:token 存进 vault 后不再进入沙箱——
printenv 只能看到占位符;出网时由网关按 allowed_hosts 白名单替换。占位符保证覆盖环境变量与出站请求(替换前);被调用服务的响应体(例如回显型的测试端点会把 Authorization 原样返回)会作为工具输出进入事件流,这属于外部服务自身行为,不在占位符保证范围内。但注意两条仍然成立的边界:网关只替换 Authorization 头;沙箱出站 HTTPS 走网关 MITM,但沙箱已预置网关 CA(SSL_CERT_FILE / CURL_CA_BUNDLE / REQUESTS_CA_BUNDLE 均指向它),curl 与 requests 默认校验可直接通过,仅在个别环境证书异常时才需要排查 CA 配置,细节与排坑见进阶:密钥库安全注入与出网网关。步骤 1 · 建 Vault
Vault 有两个关键字段:display_name(控制台展示用)和 metadata(存内部用户 ID 等)。
id(形如 vlt_01M1G0W54ARXJ4W61B0N4281TK)。记下它:
步骤 2 · 存 GitHub token 密钥
凭证挂在 vault 之下,端点是嵌套的 /vaults/{vault_id}/credentials(没有顶级 /credentials 端点,调用返回 404)。
每条凭证 = 变量名 + 值 + 出口白名单。auth.type 当前仅支持 environment_variable(传 static_bearer / mcp_oauth 返回 CREDENTIAL_AUTH_TYPE_ERROR)。allowed_hosts 必填且必须嵌在 auth.networking 里(漏传 → 409 CREDENTIAL_AUTH_NETWORKING_ERROR「出口网络地址不能为空」):
secret_name:变量名。沙箱里os.environ["GITHUB_TOKEN"]拿到的是占位符BMA_SECRET_PLACEHOLDER_GITHUB_TOKEN——真实值只在出网请求命中白名单 host 时由网关替换进Authorization头。secret_value:真实密钥。只写不回显——GET 回来时auth里只有secret_name/type/networking,没有值。networking.allowed_hosts:密钥允许发往的域名白名单,支持通配(*.github.com)。token 只会被替换到发往这些 host 的请求里——发往其他域名的请求保持占位符,防外泄。- 响应
id形如vcrd_01M1G0W5BYXJDRD32NMB16ER1Z(前缀是vcrd_,不是cred_)。
步骤 3 · 建 Agent:挂 MCP 工具集
这一步把三样东西各自备好:Agent 定义里声明 MCP server 与工具集、vault 里的凭证(步骤 2 已完成)、Session / Deployment 创建时用 vault_ids 接线(步骤 4 / 步骤 5)。
先建 Agent。mcp_servers 里声明市场服务,type 为 official、name 为市场服务的 code;tools 里用 mcp_toolkit 启用:
configs 必须逐工具启用。mcp_toolkit 与 builtin_toolkit 同构:光有 default_config.enabled: true 不够,要在 configs[ ] 里把每个要用的工具(这里是 bailian_web_search)显式 enabled: true,否则 Agent 运行时拿不到这个工具,也不会有任何报错。https://{workspace_id}.cn-beijing.maas.aliyuncs.com/api/v1/mcps/{code}/sse,一般不需要提供凭证;需要自己 key 的服务,把 key 存进 vault、在 bash 里带 Authorization 头直连即可(bash 里拿到的是占位符,真实值由出网网关替换)。
tools 再加一个入门篇讲过的 builtin_toolkit(bash/read/write 等),两者并存没有问题。纯 MCP 的 Agent 甚至可以不挂 Environment——Session 的 environment_id 留空也能跑。
步骤 4 · 手动试点:建 Session 验证工具与密钥链路
上定时任务之前,先用最朴素的姿势跑一回合,验证两件事:工具调用真的发生、密钥链路真的通。
首选:会话环境变量(非敏感配置 / 交互式会话)
应用侧自建 Session 时,配置注入首选 environment_variables——明文键值对直接进沙箱,不用预建 vault,Agent 的 bash / python 里 os.environ 直接拿到真实值:
printenv BRIEF_MAX_ITEMS → 输出 5(真实值)。创建响应与 GET 均回显该字段,注意只放非敏感内容。
交互式会话 + 非敏感配置 → 用环境变量就够了。需要密钥级安全(沙箱内不见明文)或走 Deployment 无人值守链路 → 用下面的 vault 接线。
定时链路必需:vault_ids 接线
Deployment 不支持 environment_variables,定时链路(步骤 5)的凭证只能靠 vault。创建 Session 时用 vault_ids 把密钥接入运行时(agent 字段传 Agent ID 字符串,Session 会锁定该 Agent 当前版本的完整快照):
sessions.create 不接受初始输入字段,标准做法是「先创建 Session,再单独 POST 一条 message 事件」两步驱动。(若要「触发即带首条消息」,用后文的 Deployment,它支持 initial_events。)printenv GITHUB_TOKEN:
event: 行恒为 message,注释行有 :connected / :HTTP_STATUS/200 / :keepalive):
Authorization 头调用 api.github.com,网关替换成真 PAT,GitHub 鉴权通过。
再发一条消息触发 MCP 工具(「用工具搜索茶饮行业最新动态」),MCP 调用产生的是专属事件类型,与 tool_call 平行:
tool_call / tool_call_output,MCP 工具走 mcp_call / mcp_call_output。收到 session_status = idle 且 stop_reason.type == end_turn 时本回合结束。
试点通过,两块地基都没问题。接下来把「每天 7:30 谁来发消息」这件事交给平台。
步骤 5 · Deployment:把定时任务交给平台
Deployment 是与 Agent / Environment / Session 平级的资源:声明「锁哪个版本的 Agent、触发时下发什么、挂哪些凭证、什么时间表」,之后触发、建 Session、下发 initial_events 都由平台完成。控制台操作见部署。
创建:锁版本的 agent + initial_events + vault_ids
agent必须传对象{id, version},不接受 ID 字符串(传字符串返回400 PARAMS_ILLEGAL)。这与sessions.create不同(Session 传字符串 = 锁最新)。Deployment 是长期资源,创建时就显式锁死 Agent 版本——响应里会把该版本的完整快照(system / tools / model)展开回显。版本语义见下文「版本语义:锁死与漂移陷阱」。initial_events是触发时下发的输入数组,形状与给 Session 发消息的input完全一致。它补上了sessions.create不接受初始输入的缺口:触发生成的 Session,第一条 user 消息就是它——「触发即跑」,不需要应用再补发一条消息。vault_ids在 deployment 级接线:触发生成的每个 Session 都自动带上这些凭证(注入行为与手动建 Session 完全相同)。
schedule 就是手动模式(只能通过 POST /run 触发);填了就是定时模式(见下文 cron 小节)。创建成功响应(200)回显全部字段,status 为 "active",id 形如 depl_xxx(前缀 depl_)。
触发:run 与 deployment_run
deployment_run(前缀 drun_),session_id 在异步生成前为 null:
session_id(也是生产里判断「跑完了没」的方式——status 变 succeeded、finished_at 有值):
{data: [...], next_page, request_id},按时间倒序。trigger_source 区分 "manual" 与 "schedule"(cron)。
触发即跑:一次触发的完整采证
对触发生成的 Session 拉事件(GET /sessions/{id}/events?order=asc,或 SSE),一次典型触发约 26 个事件,关键帧:
initial_events 作为首条 user 消息进入 Session、MCP 工具被调用、vault 密钥注入 deployment 生成的 Session(沙箱内为占位符,出网时网关替换)。应用全程没有发过一条消息,触发与执行全部由平台完成。
版本语义:锁死与漂移陷阱
Deployment 创建时锁 {id, version},之后 Agent 升级不影响它。把 morning-briefing 的 prompt 从 v1 升到 v2(POST /agents/{id},name + 当前 version 必填,见进阶:提示词版本管理与回滚),再触发同一个 deployment:
POST /deployments/{id} 更新(PATCH 语义,不传的字段保留)。但这里有个生产陷阱:
对比记忆:Session 锁的是「创建那一刻的最新或指定版本」快照;Deployment 锁的是「创建时显式指定的版本」,但 update 时不传 agent 会漂移到最新。
cron:定时触发
给 deployment 加 schedule 就是定时模式。验证调度可用每分钟表达式(正式场景换成业务时间表):
* * * * *,每分钟):
- 整分钟准时自动触发,runs 列表里出现
trigger_source: "schedule"的新 run,与手动 run 完全同构(同样创建 Session、下发initial_events)。 - 响应的
schedule对象带next_run_at/last_run_at——判断「定时任务活着、下次几点跑」不用自己算 cron,查 deployment 对象即可。 - 正式的晨报场景:
"expression": "30 7 * * *"(每天 07:30)、"timezone": "Asia/Shanghai"。
运维动词:pause / unpause / archive
| 动词 | 端点 | 行为 |
|---|---|---|
| 暂停调度 | POST /deployments/{id}/pause | status: "paused"、paused_reason: {type: "manual"};cron 不再触发,next_run_at 冻结 |
| 恢复 | POST /deployments/{id}/unpause | status: "active",从下一个调度点恢复;暂停期间错过的触发不补跑 |
| 归档 | POST /deployments/{id}/archive | archived_at 有值、next_run_at 清空(解除调度);GET 仍可查,列表默认过滤归档项 |
| 删除 | DELETE /deployments/{id} | 405 不支持——与 Agent 同款「只能归档不能删」 |
- pause 只挡调度,不挡手动触发:对 paused 的 deployment
POST /run仍可正常触发执行。需要停掉定时任务但保留应急手动触发时,使用 pause。 - archive 才是彻底停用:对 archived 的 deployment
POST /run返回409 DEPLOYMENT_STATE_CONFLICT "deployment archived"——手动也触发不了。 - 并发触发不受限制:前一次运行未结束时再次
POST /run,会各自创建 run 并行执行。需要控制并发时,在触发端自行限流,或通过 update 调整 schedule。
观察面小结
| 想知道什么 | 查哪里 |
|---|---|
| 定时任务还活着吗、下次几点跑 | GET /deployments/{id} 的 status + schedule.next_run_at |
| 某次触发跑完了吗 | GET /deployments/{id}/runs 的 status / finished_at |
| 某次触发干了什么 | run 的 session_id → GET /sessions/{id}/events(或 SSE) |
| 跑挂了吗 | run 的 error 字段 + session 事件流的 session_status |
session.status_idled 等事件,让平台主动 POST 接收端。
步骤 6 · 生产里的人审(HITL)
生产环境里的人审(HITL)不应该依赖长连接。人工审核的等待窗口可能是几分钟到几小时,如果这段时间一直靠 SSE 长连接挂着,既不好横向扩展(连接与实例绑定,无法自由调度),也扛不住服务重启(一次发布就丢掉全部在途审批)。正确的形态是:让 Session 停在等待状态,由服务在需要时再去取状态、做决定、回填结果。
Managed Agents 的 Session 天然支持这种「停下来等」的语义——当 Agent 需要人工确认某次工具调用时,Session 会转为 idle 并给出 requires_action,然后一直保持这个状态,直到回填确认结果。
方案 A:轮询历史事件 + requires_action
由服务定时拉取历史事件,判断 Session 是否停在「需要人工/工具介入」的状态。处于该状态时,最后一条事件的 session_status 为 idle、stop_reason 为 requires_action,并带上 pending_batch_id 与待处理的 pending_call_ids。
tool_approval_response 事件批准或拒绝该工具调用(batch_id 取自 stop_reason.pending_batch_id,call_id 取自 pending_call_ids 中对应的一项):
result 改为 deny。同一批次有多条待审批调用时,逐条回填(每条一个 call_id),全部收齐后工具才会执行。若是自托管函数结果的回填,则用 function_call_output(带 call_id + output)。
放到晨报场景:晨报 Agent 发现疑似重大负面舆情、要往公司 repo 写「舆情预警」之前,让它调用一个需要确认的动作(自定义工具或写入前确认),Session 停在 requires_action,服务轮询发现后推给运营值班群,人点「允许/拒绝」,回填 tool_approval_response,Agent 继续。工具审批的配置方式见内置工具。
关于成本上限:Session 当前不提供成本上限(budget)字段,也没有触达上限时的通知事件。成本控制建议放到 Deployment 层做触发频率与并发限流,再配合事后用量核对来兜底。
方案 B:Deployment 触发 + 事后查询
如果触发场景是「定时」或「由外部系统手动触发一次任务」,直接用 步骤 5 的 Deployment:外部系统只需要 POST /deployments/{id}/run(或干脆交给 cron),事后用 GET /deployments/{id}/runs 收结果——status 到 succeeded 只代表执行完成,还要用程序核验产物内容(例如逐条校验晨报资讯的发布日期是否落在时间窗内、来源 URL 是否可解析),不能以 run 状态代替内容验收。session_id 拿去看产物。全程无长连接。
步骤 7 · 数据驻留与区域
当前行为说明:Managed Agents 为单区域部署(cn-beijing),Agent 推理与沙箱运行都发生在该区域。因此不提供推理区域选择参数,也不提供在 sessions.create 时对单个 Session 做区域覆盖的配置。
这一节无需任何额外配置:数据驻留由「单区域部署」这一事实保证。如果合规要求是「数据不出某个区域」,当前形态已经满足;如需多区域选择,以官方后续发布的能力为准。
步骤 8 · 资源生命周期:list / retrieve / update / archive / delete
每种资源有一套动词,但不是每种资源都支持全套:
| 资源 | 列表/单查 | 更新 | 归档 | 删除 |
|---|---|---|---|---|
| agents | GET /agents、/agents/{id} | POST /agents/{id}(自动升 version) | POST /agents/{id}/archive | 不支持(DELETE 返回 405) |
| environments | GET /environments、/{id} | POST /environments/{id} | POST /{id}/archive | DELETE 可用 |
| sessions | GET /sessions、/{id} | — | POST /{id}/archive | DELETE 可用 |
| deployments | GET /deployments、/{id}(列表默认过滤归档) | POST /deployments/{id}(注意 agent 漂移) | POST /{id}/archive | 不支持(DELETE 405) |
| vaults | GET /vaults、/{id} | POST /vaults/{id} | POST /{id}/archive | DELETE 可用 |
| credentials | GET /vaults/{id}/credentials | — | — | DELETE /vaults/{vid}/credentials/{cid} |
POST /agents/{id},不是 PATCH(405);body 里必须带当前 version 做乐观并发控制;必须带 name(否则报 AGENT_005 智能体名称不能为空)。更新成功 version 自动 +1:
archive 保留记录供审计,拆掉容器、停止计量;delete 是彻底删除。生产中优先 archive。Agent 和 Deployment 只能归档不能删,归档后版本历史与触发记录仍保留可查。
步骤 9 · 清理
清理有依赖顺序,且有一个坑:删除 vault 不会级联删除它下面的 credentials。先删 vault 再去删 credential,credential 端点仍然可用(孤儿数据)。正确顺序是子资源先于父资源:
GET /deployments/{id}/runs 收集全部 session_id,逐个 DELETE(注意 running 状态的 session 不能删,须等 idle)。
小结
这一篇把生产运维的骨架搭了起来:
- 用 MCP 工具集(
mcp_serversofficial +mcp_toolkitconfigs 逐工具启用)让 Agent 调用市场外部服务,平台代理调用,事件流里看mcp_call/mcp_call_output。 - 配置注入分两层:非敏感配置走会话环境变量(
environment_variables明文直通沙箱,交互式会话首选);高敏密钥走 Vault(占位符注入 + 出网网关按allowed_hosts白名单替换,token 不经过应用侧、不进 Agent 配置、不进沙箱)——定时链路(Deployment)只支持后者。 - 用 Deployment 把触发交给平台托管:
agent {id, version}显式锁版本,initial_events触发即下发首条消息,cron 定时自动跑,pause(挡调度不挡手动)/unpause(错过不补跑)/archive(彻底停用)管生命周期,GET runs收结果。 - 生产 HITL 的两条路:轮询
requires_action+ 回填tool_approval_response,或 Deployment 触发 + 事后查询 runs。 - 生命周期动词按资源区别对待:Agent 和 Deployment 只归档不删,Session / Environment / Vault 可删,credential 先于 vault 删(无级联)。
{id, version})、并发触发无冲突限制(限流要自己做)、Session 无成本上限字段(Deployment 层限流兜底)、单区域 cn-beijing、mcp_servers.name 不做存在性校验(写错名字静默失败)、沙箱出网走网关 MITM(已预置网关 CA,默认 TLS 校验可过;个别环境异常时排查 CA 配置)。
下一步
- 进阶:密钥库安全注入与出网网关:占位符模型、网关替换行为与排坑。
- 进阶:Webhook 事件通知:让平台在任务跑完时主动通知。
- 进阶:提示词版本管理与回滚:晨报 prompt 的灰度与回滚。