学习笔记 · Obsidian
Managed Deep Agents TypeScript:渠道、调度与评估
结论先行
Channels、schedules 和 evals 分别解决外部消息入口、无用户触发的定时运行、可重复行为验证。它们不是同一个运行路径:channel run 有 runtime.channel,普通 HTTP 和 scheduled run 没有;schedule 的结果投递必须显式指定 channel;MDA evals 只生成 Harbor handoff,不替 Harbor 运行 trial。
TypeScript 版的主要工程差异是 camelCase 配置、静态可提取的 schedule 声明、Node 原生测试 scaffold,以及 Slack 配置文档当前存在相互矛盾的 CLI 流程。自动化前必须以已安装版本的 mda --help 和实际 dry run 为准。
Channels 模型
Channel 把外部消息系统连接到已部署 agent:验证并规范化事件,解析 caller identity 与 thread,启动 run,再把响应投递回原会话。
每个 channels/<name>.ts 导出命名对象 channel。文件名同时是运行时 channel 名和 inbound 路由的一部分:
channels/support.ts -> POST /channels/support/events
约束:
- 一个文件一个 channel,项目内名称必须唯一。
- 不要把文件命名为
channels/channel.ts。 - channel-originated run 的 tools/middleware 可以读取
runtime.channel。 - 普通 HTTP 和 schedule run 的
runtime.channel不存在,必须做空值分支。 - MCP connector 给 agent 增加远端工具;channel 接收消息并触发运行,两者不能混用。
默认 runner 会把最终回复发回 originating conversation。中间消息、显式最终消息和 schedule 定向投递应分别处理,避免重复发送。
Slack TypeScript 配置
Slack 是 bring-your-own-app。项目维护 channels/slack.ts 和可编辑的 slack-app-manifest.json;.mda/slack/** 是生成产物,不应手工编辑或提交。
运行选项使用 camelCase:
| 选项 | 默认值 | 含义 |
|---|---|---|
autoReply | true | run 完成后自动发最终回复 |
mentionBehavior | "strip" | 是否移除 mention token |
filters.includeConversations | 全部 | conversation allowlist |
filters.excludeConversations | 空 | conversation denylist |
filters.includeUsers | 全部 | 完整 Slack identity allowlist |
filters.excludeUsers | 空 | identity denylist |
conversation.appMention | "thread" | mention 的 thread 映射 |
conversation.directMessage | "conversation" | DM 的 thread 映射 |
映射值可以是 thread、conversation 或 message。默认支持 app mention、DM,以及 agent 已参与 thread 的非 mention 后续回复;顶层未 mention、bot、自身消息、不支持 subtype 和 filter 拒绝的事件不会启动 run。
回复语义
runtime.channel.post({ text }, { final: true })会把消息标记为最终回复,并抑制autoReply的重复发送。- 不带
final的 post 是中间消息,不会抑制自动最终回复。 - channel-originated run 只能向 originating Slack thread 发送,不能任意指定目的地。
- schedule 通过
deliverTo指定 Slack conversation,不依赖 originating channel。
Slack caller identity 形如 slack:<team>:<user>,与 HTTP caller identity 分离,当前不支持 account linking。
安全与可靠性
- 每个请求基于 raw body 验签,超过五分钟窗口的签名被拒绝。
runtime.channel不暴露 bot token 或 provider credential。- Slack Connect shared conversation 当前不支持,
allowSharedConversations: true也不能启用它。 - 事件去重仅在进程内;Slack retry 或多副本仍可能为同一逻辑事件启动多次 run。
- 所有会产生外部副作用的工具都必须幂等,不能依赖“channel 已去重”。
当前文档冲突
Slack 页面同时描述了两套不完全一致的 manifest 流程:
- 一处要求先部署,再执行
mda channel add slack .,生成 template 与 final manifest,并称没有 bootstrap manifest。 - CLI reference 与同页后文要求
mda deploy --configure-slack;缺少凭据时生成 bootstrap manifest 并在修改远端状态前退出,补齐凭据后再 waited deploy 生成 final manifest。
同页还分别建议用 mda channel add slack . 或 mda deploy . --configure-slack 重新生成配置。这是当前官方文档内部的不一致,不能静默选择一条当成稳定契约。
上线前应:固定 managed-deepagents 版本,查看该版本 mda --help,在 development deployment 验证生成文件和远端状态,再把确认过的命令写入 CI/runbook。无论采用哪条流程,都以 slack-app-manifest.json 为可编辑真源,生成文件不提交,credential 只进 secret channel。
Schedules
每个 schedules/<name>.ts 导出命名 schedule,文件名成为托管 schedule 名。声明通过 defineSchedule 创建。
核心规则:
- 必须且只能设置
prompt或结构化input之一。 cron使用标准五字段,不支持秒字段;未指定timezone时是 UTC。- 默认每次运行创建 ephemeral thread,并在结束后请求删除。
- 只有确实需要跨次累积 thread state 时才用
thread: { mode: "persistent", id: ... }。 - Slack 投递用
deliverTo;该能力要求managed-deepagents >= 0.4.0,且 bot 必须能访问目标 conversation。
静态提取约束
Schedule 在编译阶段提取,配置必须静态可序列化:
- 可以使用字面量、数组、对象和顶层字面量常量。
- 不能读取环境变量、调用函数、spread 对象或动态计算值。
- 动态业务行为应放到 agent、tool、middleware 或 runtime context。
这比普通 TypeScript 类型检查更严格:代码能通过 tsc 不代表 schedule 能被 MDA 编译器静态提取。需要用真实 mda build 验证。
部署一致性
Waited deploy 在 revision 达到 DEPLOYED 后,删除现有 MDA-owned cron 并按本地声明重建。删除本地 schedule 文件再部署,也会删除对应远端 cron。
mda deploy --no-wait 会在远端 build 完成前退出,因此该次不做 schedule reconciliation。只要 schedule 有新增、修改或删除,就必须执行 waited deploy 并核对远端 cron。
Harbor Evals
MDA evals 就是 Harbor evals:evals/tasks/ 是 canonical dataset,evals/scaffold/ 只是可选的单向起点。
两种方式:
- 直接在
evals/tasks/<task>/写完整 Harbor task、environment 和 verifier。 - 用
mda evals init <name>创建 scaffold,再用mda evals compile生成同名 canonical task。
TypeScript scaffold 的语言原生测试使用 node:test、node:assert/strict 和 .test.ts。编译器会在缺少 tests/test.sh 时生成 wrapper,运行语言测试并写 1 或 0 reward。
关键覆盖边界:
- 编译选中的 scaffold 会整体替换
evals/tasks/<同名>/,只改 canonical copy 的内容会丢失。 - 未选中的 canonical task 会保留。
--task可重复选择;不传时选择所有 task 并刷新全部 scaffold。--model可重复记录多个模型,但生成 job config 使用第一个。- MDA 生成 agent artifact、
mda_harboradapter、tasks 和harbor-job.json,但不运行 trials。 evals/不进入 agent deployment;evals/harbor-jobs/是本地 trial 输出,应排除版本控制。
TypeScript 与 Harbor 的运行边界
即使 agent 是 TypeScript,官方 handoff 仍通过 PYTHONPATH=evals/harbor-adapter 和 Harbor Python 运行入口执行;本机需要 Harbor,或需要 uv run --with harbor。默认 Docker environment 还要求 Docker 正常运行。
Harbor 不读取项目 .env。生成 job config 只包含 ${VAR} placeholder,实际 secret 必须由启动 Harbor 的进程环境提供。重复运行同一命令会恢复 job config 指向的 jobs directory;要开始全新试验,应重新编译或指定新的 Harbor job name。
Verifier 必须向 /logs/verifier/reward.txt 写数值 reward,或向 /logs/verifier/reward.json 写数值 metrics。测试脚本成功退出但没有有效 reward,不应被当作有效评估。
验证矩阵
| 能力 | 必测场景 |
|---|---|
| Slack ingress | 签名失败、过期 replay、重复 event、mention/DM/thread 映射、filter |
| Slack reply | 中间消息、final: true、autoReply 抑制、bot scope 与 retry |
| Slack setup | 安装版本对应的 channel add / --configure-slack 实际行为 |
| Schedule | 五字段 cron、timezone、静态提取、ephemeral/persistent thread |
| Reconciliation | 增删改 schedule、waited deploy、--no-wait 后的未对账状态 |
| Eval compile | scaffold 整目录替换、Node test wrapper、未选 task 保留 |
| Harbor run | Docker、环境变量、reward、失败 artifact 与新 job 隔离 |
| 副作用 | 同一 Slack event 多次运行仍只产生一次业务结果 |