学习笔记 · Obsidian

Managed Deep Agents TypeScript:渠道、调度与评估

LangChainTypeScript

结论先行

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:

选项默认值含义
autoReplytruerun 完成后自动发最终回复
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 流程:

  1. 一处要求先部署,再执行 mda channel add slack .,生成 template 与 final manifest,并称没有 bootstrap manifest。
  2. 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/ 只是可选的单向起点。

两种方式:

  1. 直接在 evals/tasks/<task>/ 写完整 Harbor task、environment 和 verifier。
  2. 用 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_harbor adapter、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 compilescaffold 整目录替换、Node test wrapper、未选 task 保留
Harbor runDocker、环境变量、reward、失败 artifact 与新 job 隔离
副作用同一 Slack event 多次运行仍只产生一次业务结果

关联