教程Claude Code系列第 8 / 8 篇

做一个靠谱的定时智能体:官方“每日简报”参考实现拆解

根据 Anthropic 的文章整理:用 Claude Managed Agents 做一个定时读取 Slack 和 GitHub、把要点发到 Slack 的智能体。按来源、去向、代理、定时、记忆、护栏六个部分,讲清每一处常见的失败和官方的六条规则。

发布 · 约 10 分钟读完 · 经人工审核()· 本站的内容怎么来的

本文根据公开分享整理,本站尚未实测。操作前请对照文末的原始来源。

本页目录(8 节)
  1. 怎么拿到代码
  2. 一、来源:读哪里
  3. 二、去向:写到哪里
  4. 三、代理:模型、工具和步骤
  5. 四、定时
  6. 五、记忆
  7. 六、护栏
  8. 对你有什么用

这是“Claude Code 开发系列”的第八篇。

这一篇解决的问题是:定时在后台跑的智能体,很容易悄悄地坏掉。 某个来源读不到了没人发现,同一件事报了两遍,或者你改过的偏好它还在按旧的来。原文用 Claude Managed Agents(测试版)做了一个参考实现:定时读 Slack 频道和 GitHub 的 pull request,记住上次读到哪里,把你需要知道的事发到一个 Slack 频道。

如果只想要结论,是这六条规则:

  1. 每个来源从书签读起,不按固定的时间窗口。
  2. 读取失败要报告为“读不到”,不能当成“今天没事”。
  3. 发出之前,把每一条再核对一遍。
  4. Slack 确认之后才算发出,然后再更新书签和台账。
  5. 每次运行都重新读偏好,偏好放在代理改不了的地方。
  6. 只需要读的地方只给只读权限,并给每次运行设花费上限。

即使你不用 Managed Agents,这六条对任何定时任务都适用。

怎么拿到代码

原文给了参考实现的仓库(链接在原文里),也可以让 Claude Code 带你走一遍。先更新:

claude update

然后在 Claude Code 里运行:

/claude-api managed-agents-onboard https://claude.dev/blog/building-effective-agent-automations/

claude-api skill 会读这篇文章,提出一套配置,把文件写到项目的 agents/ 目录,并用 ant apply 创建资源。原文说把它当作起点,再按自己的来源、去向和偏好去改。

需要准备的:一个 Slack 应用(用仓库里的 manifest 创建)和一个 GitHub token。

文件结构:

daily-brief/
├── agent.md                        模型、工具、指示
├── deployment.md                   定时、时区、预算、每次运行的第一条消息
├── environment.yaml                网络白名单
├── memory_store_preferences.yaml   用户的偏好
├── memory_store_state.yaml         代理自己的书签、台账、笔记和运行记录
├── vault.yaml                      存放凭据的保险库
├── claude-lock.json                资源 ID,由 ant apply 写入
└── slack/manifest.yaml             一个机器人应用

ant apply 是 ant 命令行工具的一个命令:读这些文件,在你的 Claude API 工作区里创建对应的资源,把 ID 记到 claude-lock.json。配置好之后,任务在 Anthropic 的基础设施上按时运行,你自己的电脑不用开着。

下面按原文的顺序讲六个部分。

一、来源:读哪里

默认两个来源:Slack 频道和 GitHub 的 pull request。具体是哪些频道和仓库,写在偏好文件里。

给代理单独的、限定范围的凭据

Managed Agents 把凭据放在保险库(vault)里。代理能引用它们,但真实的值留在保险库里,不进入 Claude 运行代码的沙箱。两种用法:

方式 例子 凭据怎么走
MCP 服务器 GitHub 代理通过沙箱外的代理服务调用 MCP 工具,由它按服务器的 URL 找到对应的凭据
命令行 Slack 代理在沙箱里用 curl 调 Slack API。沙箱里只有一个占位符 $SLACK_BOT_TOKEN,请求离开沙箱时,平台才对你允许的主机换上真实的 token

创建保险库:

ant apply vault.yaml

然后用 TypeScript SDK 把每个凭据加进去。原文里添加 Slack 凭据的例子:

const vaultId = process.env.VAULT_ID!; // 保险库的 ID,来自 claude-lock.json

await client.beta.vaults.credentials.create(vaultId, {
  display_name: "SLACK_BOT_TOKEN",
  auth: {
    type: "environment_variable",
    secret_name: "SLACK_BOT_TOKEN",
    secret_value: process.env.SLACK_BOT_TOKEN!,
    networking: { type: "limited", allowed_hosts: ["slack.com"] },
    injection_location: { header: true },
  },
});

最后把 claude-lock.json 里的保险库 ID 填进 deployment.md 的 vault_ids。

从上次读到的地方接着读

常见的错误:让代理读一个固定的窗口,比如“过去 24 小时”。这次跑晚了就漏一段,跑早了就重复。

做法:每个来源一个书签。每次运行结束时,代理把每个来源里读到的最新一条的时间戳写进 bookmarks.json,例如 "slack": "2026-09-14T13:02:11Z"。下一次从书签开始,窗口自动伸缩,正好覆盖上次之后的全部内容。

书签存在名叫 state 的记忆库里。记忆库就是一个文本文件的文件夹,平台把它挂载到每次运行的沙箱里的 /mnt/memory/ 下,并在两次运行之间保留。代理用普通的文件工具读写。

别把“读取失败”当成“今天没事”

常见的错误:MCP 服务器挂了或者 token 过期了,运行照样开始,只是没有那个服务器的工具。会话日志里有一条错误,但代理从这个来源什么也看不到,于是汇报“没有新内容”。

做法:agent.md 里有三条规则。某个来源失败时,代理要:

  • 让这个来源的书签停在原处;
  • 用其他来源照常写简报;
  • 在简报最后加一行,点名读不到的是什么(例如“本次无法读取 pull request”)。

二、去向:写到哪里

模板只往一个 Slack 频道发,每次运行发一条带日期的帖子。代理在沙箱里用 bash 工具发,用的是读取时的同一个机器人 token。

发帖不需要谁批准:内置的 bash 工具默认不询问就运行,slack.com 也在环境的白名单里。就是一个请求:

curl -s https://slack.com/api/chat.postMessage \
  -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
  -H "Content-Type: application/json; charset=utf-8" \
  -d '{"channel": "C0123456789", "text": "Daily brief, Tue Sep 15 ..."}'

确认发出去了,再记账

常见的错误有两种。记了一条其实没发出去的帖子,书签往前走了,那些内容永远不会被报告。或者拿不准第一次发没发成,又发一次,读者收到两份。

做法:agent.md 里的三条规则。

  1. 发之前,先在频道最近的消息里找今天的标题,已经有了就不发。
  2. 只有 Slack 返回 "ok": true 和消息的 ts,才算发出。
  3. 得到确认之后,才更新台账和书签。结果不明确时,把这次运行标成“可能已发”,其他什么都不动,这样不会丢东西。

代理在记忆库里给每次运行留一份记录(runs/<日期>.md):发之前标“发送中”,之后标“已发”并带上消息 ID,或者标“可能已发”。

三、代理:模型、工具和步骤

在 Managed Agents 里,一个代理是一份带版本的配置:模型、系统提示词和工具。每次运行按步骤做完就停。参考实现的 agent.md:

---
name: Daily brief
model: claude-sonnet-5-5
mcp_servers:
  - type: url
    name: github
    url: https://api.githubcopilot.com/mcp/
tools:
  - type: agent_toolset_20260401
    configs:
      - name: web_search
        enabled: false
      - name: web_fetch
        enabled: false
  - type: mcp_toolset
    mcp_server_name: github
    default_config:
      permission_policy:
        type: always_allow
---

[八个编号的运行步骤,全文见仓库里的 agent.md。]

头部写名字、模型、工具和 MCP 服务器,正文是给代理的指示。注意一点:MCP 工具默认要询问批准,而定时运行时没有人在场,所以 GitHub 工具集设成了 always_allow,同时 GitHub 的 token 是只读的。放开批准和收紧权限是配套的。

八个步骤里,原文展示了两个。

让简报保持简短

第 4 步“取舍”的大意(译自原文):

4. 取舍。一条内容值得占一行,是因为读者今天会据此行动,或者它会改变读者即将做的一个决定。
拿不准就不写。多数日子是几条,有时一条也没有。
一个数字(“12 个待评审”)不算一条内容,把其中被卡住的链接出来。
已经在台账里而且还没解决的,只带一行标注(“仍在等待,第 3 天”),不重新报告;
已经关闭的直接去掉,不用说明。偏好文件里已经停掉的话题,不要再提。

发之前再核对一遍

从读取到发出之间,事情可能已经变了。第 5 步“核实”的大意(译自原文):

5. 核实。你读的时候,世界在变。对每一条要报告的内容,发之前到它的来源再查一次:
读完之后已经解决的,去掉;还没解决但有变化的,改这一行;
确认不了的,去掉,并记到运行记录的“删去”里。
一条过时的“还在等你”,比漏掉十条更伤信任。所以不要对状态含糊其辞:要么肯定地说,要么不说。
每个链接都从来源自己的链接字段复制(pull request 的 html_url、Slack 的 permalink),不要自己拼。

四、定时

代理只是一份配置,要由一个“部署”来运行它。部署指定用哪个代理、哪个环境、每次运行的第一条消息,还带着定时、保险库、记忆库和预算。每次到点,平台开一个全新的代理会话。

模板的 deployment.md,正文就是第一条消息:

---
name: Daily brief
agent: ./agent.md
environment_id: ./environment.yaml
schedule:
  type: cron
  expression: "32 7 * * 1-5"
  timezone: America/New_York
vault_ids: [vlt_...]   # 在“来源”一节创建的保险库
resources:
  - path: ./memory_store_preferences.yaml
    access: read_only
    instructions: The reader's preferences. Re-read them every run. Never write here.
  - path: ./memory_store_state.yaml
    access: read_write
    instructions: Your state. Bookmarks, ledger, notes, proposals, and run records.

---

Write today's brief.
The reader's time zone is America/New_York. Work out every date in that zone.

Follow your run steps in order. Today's edition is titled "Daily brief, <weekday> <month> <day>".

这份配置的意思是工作日早上 7:32(纽约时间)运行。创建部署:

ant apply deployment.md

不想等到点,可以手动跑一次:ant beta:deployments run --deployment-id <id>,ID 在 claude-lock.json 里。

按你的时区算日期

常见的错误:代理按服务器的时区算日期,把今天早上说成“昨天”。

做法:两处都要写。timezone 字段决定什么时候触发,正文第二行告诉代理算日期用哪个时区。国内用户两处都改成 Asia/Shanghai。

五、记忆

每次运行都在一个全新的沙箱里开始,不记得上一次。没有记忆,你的反馈留不住。但过时的记忆也会添乱:一件事已经解决了还报“仍在等待”,或者一件还没解决的事因为“已经报过”被丢掉。

模板用两个记忆库:

记忆库 属于谁 权限 放什么
preferences 你 代理只读 读哪些频道和仓库、哪些不要、长度上限、发到哪里、什么时候停
state 代理 读写 书签、报告过的内容的台账、每次运行的记录、它想对你的偏好提的修改建议、各个来源的特点(比如“只返回最新的 50 条”)

ant apply deployment.md 会创建 preferences 这个库,但不会创建里面的文件。第一次运行之前,要用仓库里的 scripts/seed-preferences.sh 把你的 preferences.md 写进去。

每次运行开始时重新读偏好

常见的错误:把偏好抄了一份写死在提示词里,你已经改了的规则它还在执行。

做法:让代理每次都读文件。读不到就停下来说明,而不是按默认值跑。

记一本台账,只报告变化

代理在 ledger.md 里记下每一条报告过的内容,简报才不会重复。每行记四样:什么时候报的、来自哪里、一个不会变的 ID(Slack 消息的时间戳或 pull request 的编号)、最后知道的状态。

2026-09-09 slack:C0123456789 1788963600.000100 refund thread: customer waiting on a decision
2026-09-11 github 481 review blocked, day 2 (still waiting)
2026-09-11 slack:C0234567891 1789117333.000300 enterprise escalation: owner named, in progress

六、护栏

任务在后台按时运行,没有人盯着,所以要限制它能做什么、能花多少。

限制它能做的事

代理读的是别人写的消息和 issue,这些文字可能被当成指令。办法是限制它即使照做了也能造成多大的后果:

  • GitHub 的 token 是只读的;
  • preferences 记忆库是只读的;
  • 环境只能访问白名单上的主机。

原文对剩下的风险说得很直接:被人埋进去的指令仍然可能改变简报的内容,包括通过代理在两次运行之间保留的笔记。但它写不了 GitHub,也改不了你的规则。

Slack 是例外:同一个 token 既能读也能发。所以只把机器人邀请进它需要读或发的频道。

根据实际运行来设花费上限

  • 起步设成一次正常运行成本的 3 到 5 倍,看到真实数字后再收紧。
  • 达到上限的运行是暂停,不是失败。所以上限设得太低,看起来就像“简报不发了”。
  • 每次运行都有完整的额度,达到后以 budget_reached 为停止原因暂停。

上限写在 deployment.md 的 budget 里:

budget:
  type: limit
  max_list_cost:
    amount: "500" # 字符串,单位是美分:"500" 就是 5 美元
    currency: USD

对你有什么用

只是用 Claude Code 的人:

  • 你用 Claude Code 的定时任务、/loop 或云端会话做的任何周期性工作,都可以拿开头那六条来对照。最容易忽略的是第 2 条:一个什么都没报的结果,要分得清是真没事还是没读到。
  • 给后台任务的权限按“它被骗了会怎样”来定,而不是按“它正常工作需要什么”来定。

想自己搭一个的人:

  • 从官方的命令和模板开始,改来源和偏好,不要从零写。
  • 上线前手动跑几次,看一次的真实花费,再设上限。
  • 时区两处都要改。
  • 这是测试版功能,字段和命令可能会变,动手时以仓库和官方文档为准。

来源

通过 API 中转站使用这些模型?到 Veridrop 免费检测中转站给你的是不是它声称的模型,或者看看中转站排行榜前 100 名。

相关文章