返回转载

构建高效的智能体自动化

原作者 Lance Martin约 10 分钟读完

基于 Claude 托管智能体的定时简报参考实现逐步讲解:信息源书签、发布确认、台账、偏好记忆与护栏,附常见失败模式。


原文:Lance Martin · claude.dev

AI 让我们的工作越来越快,信息也越来越难跟上。在 Anthropic,我们经常借助一些简单的智能体自动化(agent automation)来应对。它们通常按计划定时运行,在后台收集上下文,再主动告诉我们需要知道的事。但要构建真正好用的智能体自动化并不容易:它们可能悄无声息地失去某个信息源的访问权限,也可能不遵循我们的偏好。

我们基于 Claude 托管智能体(Claude Managed Agents)(beta)构建了一个参考实现:它按计划读取自定义信息源(如 Slack 和 GitHub 仓库),追踪自上次运行以来的变化,并把你需要知道的内容发布出去(如发到 Slack)。本文会逐步讲解每个环节,分享这份参考实现,并提供一条可以在 Claude Code 中运行的命令,帮你自动配置好这个智能体。

获取代码

参考实现在这里。如果想要交互式的引导,可以在 Claude Code 中运行下面的命令。claude-api 技能(skill)会按照本文的指导帮你搭建这个智能体:

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

要运行这份参考实现,你需要一个 Slack 应用(通过清单创建)和一个 GitHub 令牌(token)。仓库提供的文件(如下所示)是 Claude API 资源的配置,包括智能体、它的环境、记忆存储(memory store)、密钥库(vault)和部署(deployment)。

daily-brief/
├── agent.md                        model, tools, instructions
├── deployment.md                   schedule, time zone, budget, input message
├── environment.yaml                network allowlist
├── memory_store_preferences.yaml   user preferences
├── memory_store_state.yaml         the agent's bookmarks, ledger, notes, and run records
├── vault.yaml                      the vault that holds the credentials
├── claude-lock.json                resource IDs, written by ant apply
└── slack/manifest.yaml             one bot app

ant apply 是 ant CLI 中的一条命令,它会读取这些文件,在你的 Claude API 工作区(workspace,即平台存储和运行这些资源的地方)中创建对应资源,并把资源 ID 记录到 claude-lock.json。

下文各节都会用到这条命令。配置完成后,这个自动化会在 Anthropic 的基础设施上按计划运行,你的机器上不需要保持任何程序在运行。

概览

我们要构建的智能体由六个组件构成,按以下顺序介绍:

  • 信息源(Sources)——一份具名的读取位置列表
  • 目的地(Destination)——智能体唯一可以写入的地方
  • 智能体(Agent)——agent.md 中定义的模型、工具和运行步骤
  • 调度(Schedule)——一个 cron 定时计划
  • 记忆(Memory)——你的偏好,以及智能体自己的记忆
  • 护栏(Guardrails)——凡是智能体只需读取的地方都只给只读权限,并为每次运行设置花费上限

智能体架构总览

智能体架构:调度唤醒智能体,智能体从信息源读取内容,再把一份简报发布到唯一的目的地供读者阅读。记忆保存着智能体的状态和读者的偏好,护栏则把智能体包围在内。

信息源

智能体默认读取两个信息源:Slack 频道和 GitHub 拉取请求(pull request)。要读取的频道和仓库列在你的 preferences 文件中。这个模板也可以扩展到其他信息源。

信息源

高亮了信息源的架构图。信息源是只读的,通过 MCP 或 HTTPS 访问,凭据来自密钥库,并为智能体提供输入。

给智能体一套专属的、限定范围的凭据

在托管智能体中,凭据存放在密钥库里。智能体可以引用这些凭据,但真实的值始终留在密钥库中,位于运行 Claude 代码的沙箱(sandbox)之外(详见这里和这里):

  • MCP 服务器(GitHub)。 智能体通过一个运行在沙箱外的代理(proxy)调用 MCP 工具。代理会找到 URL 与该服务器匹配的那条密钥库凭据。
  • Shell(Slack)。 智能体在沙箱内用 bash 工具执行 curl 来调用 Slack API。沙箱里只有一个不透明的占位符 $SLACK_BOT_TOKEN。请求离开沙箱时,平台会针对你允许的主机把它替换成真实的令牌。

用 ant CLI 和仓库里的模板文件创建密钥库:

ant apply vault.yaml

这会在你的 Claude API 工作区(平台存储它的地方)中创建密钥库,并把它的 ID 记录到 claude-lock.json。然后用 TypeScript SDK 把每条凭据添加到密钥库中。下面是添加 Slack 凭据的示例:

const vaultId = process.env.VAULT_ID!; // the vault's ID, from 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 小时”。运行晚了会留下空档,运行早了又会重复条目。更好的做法是给每个信息源一个书签(bookmark)。每次运行结束时,智能体把它从每个信息源读到的最新条目的时间戳写入同一个文件 bookmarks.json,每个信息源一条记录:"slack": "2026-09-14T13:02:11Z"。

下一次运行从这些书签开始,因此它的读取窗口会自动伸缩,覆盖自上次运行以来的全部内容。书签存放在名为 state 的记忆存储中:这是一个由文本文件组成的文件夹,平台会把它挂载到每次运行的沙箱的 /mnt/memory/ 下,并在多次运行之间保留。智能体用普通的文件工具读写它,agent.md 中的指令会告诉它具体怎么做。

别把读取失败误当成平静的一天

如果某个 MCP 服务器宕机了,或者它的令牌过期了,运行照样会启动,只是缺了该服务器的工具。会话日志里会记一条错误,但智能体从这个信息源什么也看不到,于是报告“没有新内容”。

agent.md 中有三条规则来解决这个问题。当某个信息源失败时,智能体会:让该信息源的书签保持原位不动;用其他信息源的内容写简报;并在简报末尾用一行注明哪些内容没读到(“本次运行无法获取拉取请求”),让读者知情。

目的地

我们的模板发布到一个 Slack 频道,每次运行发一条带日期的帖子。

目的地

高亮了目的地的架构图。先检查今天的简报是否已经发过,然后智能体发布到唯一的目的地,把简报送达读者。

智能体在沙箱中用 bash 工具发布到 Slack,使用的正是它读取时所用的同一个机器人令牌(bot token)。

发帖不需要任何人批准。智能体用一条 bash 命令发送,而内置的 bash 工具默认无需批准即可运行。slack.com 也在智能体环境(即它运行所在的沙箱)的允许列表(allowlist)上。发帖只是一个请求:

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 ..."}'

先确认发布成功,再做记录

一旦确认帖子发出,智能体就会更新它的已报告条目台账(ledger)和书签。如果这些记录与实际发布的内容对不上,可能出两种问题:如果智能体记录了一条其实没发出去的帖子,书签会继续往前推,那些条目就永远不会被报告;如果它因为不确定第一条是否发出而再发一次,读者就会收到两份相同的简报。

agent.md 中有三条规则防止这种情况。第一,智能体先在频道的近期消息中查找今天的标题,如果这一期已经在了就不再发。第二,只有当 Slack 返回 "ok": true 和消息的 ts 时,才算发送成功。第三,智能体只在得到这一确认后才更新台账和书签。如果结果不明确,它就把本次运行标记为“可能已发布”,其他什么都不改,这样就不会丢失任何内容。

智能体在记忆存储中保存一份运行记录(runs/<date>.md)。发帖前它把运行标记为“发布中”,之后标记为带消息 ID 的“已发布”,或者“可能已发布”。

智能体

在 Claude 托管智能体中,智能体是一份带版本的配置:一个模型、一段系统提示词(system prompt)和若干工具。每次运行都按其运行步骤执行,然后停止。

智能体

高亮了智能体的架构图:一个模型,加上一段承载运行循环和判断规则的提示词。

在我们的参考实现中,智能体配置就是 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
---

[Eight numbered run steps; the full text is in agent.md in the repo.]

frontmatter 提供智能体的名称、模型、工具和 MCP 服务器,正文则是智能体的指令。MCP 工具默认需要批准,而运行时没有人在场批准,所以 GitHub 工具集被设为 always_allow,GitHub 令牌则是 read-only(只读)的。

让简报保持简短

agent.md 引导 Claude 写得简洁:

4. Decide. An item earns a line when the reader would act on it today, or it changes a decision they are about to make. When unsure, leave it out. Most days that is a few items, sometimes none. A count ("12 open reviews") is not an item; link the ones that are blocked. An item already in the ledger and still open is carried as one marked line ("still waiting, day 3"), not re-reported; a closed item is dropped without comment. Do not bring back a topic the preferences file has retired.

发布前,重新核查所有仍未关闭的条目

从智能体读取信息源到它发布之间,条目可能已经发生变化。agent.md 指示智能体在发布前一刻重新核查每个条目的实时状态:

5. Verify. The world moved while you read. For every item you will report, re-check its live source just before posting: resolved since you read it, drop it; still open but changed, fix the line; cannot confirm, drop it and list it in the run record's cuts. One stale "still waiting on you" costs more trust than ten missing items, so never hedge an item's status: assert it or drop it. Every link is copied from the source's own link field (a pull request's html_url, a Slack permalink), never assembled by hand.

调度

在 Claude 托管智能体中,智能体只是一份配置文件,真正运行它的是部署。部署指定了智能体、环境以及每次运行的第一条消息,同时还包含调度计划、密钥库、记忆存储和预算。每当调度触发,平台就会启动一个全新的智能体会话(session)。

调度

高亮了调度的架构图:一个定时部署,在每次运行时唤醒智能体。

在我们的模板中,部署写在 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_...]   # the vault you create under Sources
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>".

下面的命令会创建这个部署,以及它按路径引用的智能体、环境和记忆存储。

ant apply deployment.md

如果不想等调度触发就进行测试,可以用 ant beta:deployments run --deployment-id <id> 手动启动一次运行,ID 取自 claude-lock.json。

用你所在的时区计算日期

一个常见的 bug 是:智能体按服务器时区计算日期,结果把今天早上称作“昨天”。在 deployment.md 中,timezone 字段决定运行何时触发,而正文第二行则告诉智能体计算日期时使用哪个时区。

记忆

每次运行都在一个全新的沙箱中启动,不记得上一次的任何事。没有记忆,反馈就留不住。但过时的记忆也会让智能体犯糊涂:一个条目已经解决了,它还报告说仍在等待;或者一个仍未关闭的条目,因为“已经报告过”而被它丢掉。

记忆

高亮了记忆的架构图。状态(State)由智能体读写,偏好(Preferences)由读者写入、智能体只读。

我们的模板维护两个记忆存储,即挂载在 /mnt/memory/ 下的文件夹(见“信息源”一节):

  • preferences(你的,对智能体只读):要读哪些频道和仓库、哪些内容不要写、篇幅上限、目的地,以及何时停止。
  • state(智能体的,可读写):书签、已报告内容的台账、每次运行一条的运行记录、它对你的偏好提出的修改建议,以及关于各信息源行为特点的笔记(“只返回最新的 50 条”)。

ant apply deployment.md 会创建 preferences 存储,但不会创建其中的文件。首次运行前,请用仓库中的 scripts/seed-preferences.sh 把你的 preferences.md 写进去。

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

一个常见问题是把偏好的副本直接固化进提示词,结果你已经改过的规则仍在被继续执行。应该让智能体每次运行都重新读取这个文件。如果读不到,它应该停下来并说明情况,而不是用默认值继续运行。

为已报告的内容记台账,并报告其变化

智能体维护一个台账 ledger.md,记录它报告过的每个条目,这样简报就不会重复。每一行记录条目的报告时间、来源、一个不会变化的 ID(Slack 消息时间戳或拉取请求编号),以及它最后已知的状态:

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

护栏

由于我们的自动化是按计划在“后台”运行的,我们对智能体能做什么、能花多少钱都设了限制。

护栏

高亮了护栏的架构图:智能体周围有一道边界,对轮次、时长和花费设有上限,还有一个由专人盯着的心跳(heartbeat)。

限制它能做的事

智能体会读取别人写的消息和 issue,而这些文本可能被当作指令来解读。所以要限制它在照做时能造成的影响。在我们的示例中,GitHub 令牌和 preferences 存储都是只读的,环境也只能访问允许列表上的主机。被植入的指令仍然可以改变简报的内容,包括借助智能体在多次运行之间保留的笔记;但它没法写入 GitHub,也没法修改你的规则。

Slack 是个例外:发帖用的是同一个令牌,所以只把机器人邀请到它确实需要读取或发帖的频道里。

根据真实运行设定花费上限

花费上限能防止成本失控。先设为一次正常运行成本的三到五倍,等看到真实数据后再逐步收紧。触及上限的运行会暂停而不是失败,所以上限设得太低时,看起来就像简报突然没动静了。这个上限就是 deployment.md 中的 budget。每次运行都有完整的额度,达到上限的运行会以 budget_reached 停止原因暂停:

budget:
  type: limit
  max_list_cost:
    amount: "500" # a string, in cents: "500" is $5.00
    currency: USD

开始上手

我们的参考实现可以归结为六条规则:

  • 每个信息源都从书签处开始读,而不是读固定的时间窗口。
  • 读取失败要报告为“无法读取”,绝不能当成平静的一天。
  • 发布前一刻重新核查每个条目。
  • 只有在 Slack 确认后才算发送成功,然后再更新书签和台账。
  • 每次运行都重新读取偏好,且偏好存放在智能体无法编辑的存储中。
  • 凡是智能体只需读取的地方都只给只读权限,并为每次运行设置花费上限。

Claude Code 可以带你一步步落实本文的指导。首先更新:

claude update

然后使用 claude-api 技能:

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

claude-api 技能会读取这篇文章,提出一套配置方案,把文件写到你项目里的 agents/ 文件夹,并用 ant apply 创建相应资源。请把它当作起点,再根据你的信息源、目的地或记忆偏好定制这个智能体。