构建高效的 Agent 自动化
英文标题:ArticleOct 8, 2026Building effective agent automationsA reference implementation walkthrough for a scheduled Claude Managed Agents automation, with the config files and six rules for avoiding common failure modes.Claude Platform (opens in new tab)
Anthropic 发布了一套基于 Claude Managed Agents(测试版)的定时自动化参考实现,可定期读取 Slack、GitHub 等自定义信息源,并将每日简报主动发布到 Slack。该方案通过书签记录各信息源的读取进度,避免固定“过去 24 小时”窗口导致的信息遗漏或重复。文章重点提出六项设计原则,包括使用最小权限凭证、识别读取失败、确认消息实际送达、发布前复核状态,以及为每次运行设置支出上限。开发者可使用提供的 GitHub 模板和 Claude Code 命令快速部署包含 Agent、环境、记忆存储、凭证库和定时任务的完整自动化系统。
9.4 KB
380.8 KB
338.1 KB
337.0 KB
336.1 KB
332.8 KB
346.6 KB
339.6 KB
11.3 KB
中文
随着 AI 加速我们的工作,跟上变化正变得越来越困难。在 Anthropic,我们经常使用简单的 Agent 自动化来提供帮助。它们通常按计划运行,在后台收集上下文,并主动告知我们需要了解的信息。但构建有效的 Agent 自动化并不容易:它们可能在无人察觉的情况下失去对某个信息源的访问权限,或者无法遵循我们的偏好。
我们使用 Claude Managed Agents(测试版)构建了一个参考实现:它会按计划读取自定义信息源(例如 Slack 和 GitHub 仓库),跟踪自上次运行以来发生的变化,并发布你需要了解的信息(例如发送到 Slack)。本文将逐步讲解每个环节,分享参考实现,并提供一条可在 Claude Code 中运行的命令,为你配置该 Agent。
获取代码
参考实现位于这里。如需交互式演练,可在 Claude Code 中运行下面的命令。claude-api skill 可以依据本文的指导帮助你设置 Agent:
/claude-api managed-agents-onboard https://claude.dev/blog/building-effective-agent-automations/
要运行这一参考实现,你需要一个 Slack 应用(通过该清单创建)和一个 GitHub token。下方提供的文件是 Claude API 资源的配置,涵盖 Agent、其环境、记忆存储、凭证库和部署。
text 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 工作区中创建资源(平台会在该工作区存储并运行这些资源),并将资源 ID 记录在 claude-lock.json 中。
下文各节将使用该命令。完成配置后,自动化将在 Anthropic 的基础设施上按计划运行,因此你的本地机器无需持续运行。
概览
我们将构建的 Agent 包含六个组件,按以下顺序介绍:
- 信息源(Sources):一个具名的可读取位置列表
- 目标位置(Destination):Agent 可以写入的一个位置
- Agent:
agent.md中定义的模型、工具和运行步骤 - 计划(Schedule):一个 cron 计划
- 记忆(Memory):你的偏好,以及 Agent 自身的记忆
- 护栏(Guardrails):凡是 Agent 仅需读取的位置均使用只读访问权限,并对每次运行设置支出上限

信息源
Agent 默认读取两个信息源:Slack 频道和 GitHub pull request。频道和仓库列在你的 preferences 文件中。该模板可以扩展,以使用其他信息源。

为 Agent 提供专属且范围受限的凭证
在 Managed Agents 中,凭证存储在 vaults 内。Agent 可以引用这些凭证,但真实值会留在凭证库中,不会进入 Claude 代码运行所在的沙箱(参见这里和这里):
-
MCP 服务器(GitHub):Agent 通过运行在沙箱外的代理调用 MCP 工具。代理会查找 URL 与服务器匹配的凭证库凭证。
-
Shell(Slack):Agent 使用沙箱内 bash 工具,通过 curl 调用 Slack API。沙箱仅保存一个不透明占位符
$SLACK_BOT_TOKEN。当请求离开沙箱时,平台会针对你允许访问的主机替换为真实 token。
使用 ant CLI 和仓库中的模板文件创建凭证库:
shell ant apply vault.yaml
这会在你的 Claude API 工作区中创建凭证库,由平台负责存储,并将其 ID 记录到 claude-lock.json。随后,使用 TypeScript SDK 将每项凭证添加到凭证库。以下示例展示了如何添加 Slack 凭证:
typescript 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 字段中。
从上次读取的位置继续
一个常见错误是要求 Agent 读取固定的时间窗口,例如“过去 24 小时”。一次延迟运行会留下空档,而一次提前运行则会重复内容。相反,应为每个信息源提供一个书签。在每次运行结束时,Agent 会将它从每个信息源读取到的最新条目的时间戳写入一个文件 bookmarks.json,每个信息源对应一个条目:"slack": "2026-09-14T13:02:11Z"。
下一次运行从这些书签开始,因此其读取窗口会延长或缩短,以覆盖自上次运行以来的全部内容。书签保存在名为 state 的记忆存储中:这是一个文本文件夹,平台会在每次运行时将其挂载到沙箱的 /mnt/memory/ 下,并在运行之间持续保留。Agent 使用常规文件工具读取和写入它,agent.md 中的指令会告诉它具体方法。
不要将读取失败误判为平静的一天
如果 MCP 服务器宕机或其 token 已过期,运行仍会启动,只是没有该服务器的工具。会话日志会记录错误,但 Agent 看不到该信息源的任何内容,于是会报告“没有新内容”。
agent.md 中的三条规则有助于解决这一问题。当某个信息源失败时,Agent 会:保持该信息源的书签不变;根据其他信息源编写简报;并在简报末尾添加一行,说明无法读取什么(“本次运行无法获取 pull request”),以便读者了解情况。
目标位置
我们的模板每次运行时都会向一个 Slack 频道发布一则带日期的消息。

Agent 使用其沙箱内的 bash 工具发布到 Slack,使用的 bot token 与读取时相同。
无需任何人审批该消息。Agent 使用 bash 命令发送消息,而内置 bash 工具默认无需请求批准即可运行。slack.com 也位于 Agent environment 的允许列表中,即其运行所在的沙箱。发送消息只需要一个请求:
shell
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 就会更新其已报告条目台账和书签。如果这些记录与实际发布的内容不一致,可能出现两类问题:如果 Agent 记录了一条实际上未送达的消息,书签就会向前推进,这些条目将永远不会被报告;如果 Agent 因无法确定第一次是否送达而再次发布,读者将收到两次相同的简报。
agent.md 中的三条规则可以避免这个问题。首先,Agent 会在频道的近期消息中查找当天的标题;如果该版本已经存在,就不再发布。其次,只有当 Slack 返回 "ok": true 和消息 ts 时,才视为发送成功。第三,只有在获得这一确认后,Agent 才更新台账和书签。如果结果不明确,它会将本次运行标记为“可能已发布”,而不更改其他任何内容,从而避免信息丢失。
Agent 会在其记忆存储中保留运行记录(runs/<date>.md)。它会在发布前将运行标记为“正在发布”,然后标记为“已发布”并记录消息 ID,或标记为“可能已发布”。
Agent
在 Claude Managed Agents 中,一个 agent 是一份版本化配置:包括模型、系统提示词和工具。每次运行均遵循其运行步骤并在完成后停止。

在我们的参考实现中,Agent 配置为 agent.md:
markdown
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 提供 Agent 名称、模型、工具和 MCP 服务器。正文提供 Agent 指令。MCP 工具默认会请求批准,而此时没有人可以进行批准,因此 GitHub 工具集被设为 always_allow,GitHub token 则为只读权限。
保持简报简短
agent.md 会引导 Claude 保持简洁:
text 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 读取信息源和发布消息之间,事项的状态可能发生变化。agent.md 指示 Agent 在发布前重新检查每个事项的实时状态:
text 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 Managed Agents 中,Agent 只是一份配置文件;由一个 deployment 来运行它。部署指定 Agent、环境和每次运行的第一条消息,同时也包含计划、凭证库、记忆存储和预算。每当计划触发时,平台都会启动一个全新的 Agent session。

在我们的模板中,部署配置保存在 deployment.md 中,其正文为第一条消息:
markdown
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
English / 原文
As AI accelerates our work, it's getting harder to keep up. At Anthropic, simple agent automations are frequently used to help. They often run on a schedule, gather context in the background, and proactively tell us what we need to know. But itâs difficult to build effective agent automations: they can lose access to a source without anyone noticing or fail to follow our preferences.
Using Claude Managed Agents (beta), we built a reference implementation that reads custom sources (e.g., Slack and GitHub repos) on a schedule, tracks what changed since the last run, and posts what you need to know (e.g., to Slack). In this article, we walk through each step, share a reference implementation, and provide a command to run in Claude Code that configures the agent for you.
GET THE CODE
The reference implementation is here. For an interactive walkthrough, run the command below in Claude Code. The claude-api skill can help set up the agent following the guidance in this article:
PROMPTCopy
/claude-api managed-agents-onboard https://claude.dev/blog/building-effective-agent-automations/
For this reference implementation, you need a Slack app (create it from the manifest) and a GitHub token. The provided files (shown below) are configuration for Claude API resources, including the agent, its environment, memory stores, vault, and deployment.
CODETextCopy
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, a command in the ant CLI, reads these files, creates the resources in your Claude API workspace (where the platform stores and runs them), and records the IDs in claude-lock.json.
We'll use this command in the sections below. Once configured, the automation runs on a schedule on Anthropic's infrastructure, so nothing has to stay running on your machine.
OVERVIEW
The agent weâll build has six components, covered in this order:
- Sources - A named list of places to read
- Destination - One place the agent may write
- Agent - The model, tools, and run steps in agent.md
- Schedule - A cron schedule
- Memory - Your preferences and the agent's own memory
- Guardrails - Read-only access wherever the agent only reads, and a spending cap per run

SOURCES
The agent reads two default sources: Slack channels and GitHub pull requests. The channels and repos are listed in your preferences file. The template can be extended to use other sources.

Give the agent its own, scoped credentials
With Managed Agents, credentials live in vaults. The agent can reference these credentials but the real values stay in the vault, outside the sandbox where Claude's code runs (see here and here):
-
MCP servers (GitHub). The agent calls MCP tools through a proxy that runs outside the sandbox. The proxy finds the vault credential whose URL matches the serverâs.
-
The shell (Slack). The agent calls the Slack API with curl using the bash tool inside the sandbox. The sandbox only holds an opaque placeholder,
$SLACK_BOT_TOKEN. As the request leaves the sandbox, the platform swaps in the real token for hosts you allow.
Create the vault using the ant CLI and the template file in the repo:
CODEShellCopy
ant apply vault.yaml
This creates the vault in your Claude API workspace, where the platform stores it, and records its ID in claude-lock.json. Then add each credential to the vault with the TypeScript SDK. Here is an example showing addition of the Slack credential:
CODETypeScriptCopy
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 },
},
});
After creating the vault and adding each credential, attach the vault to the deployment. Copy the vault ID from claude-lock.json into vault_ids in the deployment file, deployment.md.
Read from where you left off
A common mistake is asking the agent to read a fixed window like "the last 24 hours." A late run leaves a gap and an early run repeats items. Instead, give the agent a bookmark per source. At the end of each run, the agent writes the timestamp of the newest item it read from each source to one file, bookmarks.json, with an entry per source: "slack": "2026-09-14T13:02:11Z".
The next run starts from those bookmarks, so its window stretches or shrinks to cover everything since the last run. The bookmarks live in the memory store called state: a folder of text files that the platform mounts into every run's sandbox under /mnt/memory/ and keeps between runs. The agent reads and writes it with its ordinary file tools, and the instructions in agent.md tell it how.
Don't mistake a failed read for a quiet day
If an MCP server is down or its token has expired, the run still starts, just without that server's tools. The session logs an error, but the agent sees nothing from that source and reports "nothing new."
Three rules in agent.md help fix this. When a source fails, the agent will: keep the sourceâs bookmark where it is, write the brief from the other sources, and end the brief with one line naming what it couldn't read ("pull requests unavailable this run"), so the reader is made aware.
DESTINATION
Our template posts to one Slack channel, with a dated post each time it runs.

The agent posts to Slack using the bash tool in its sandbox, using the same bot token it reads with.
Nothing has to approve the post. The agent sends it with a bash command, and the built-in bash tool runs without asking for approval by default. slack.com is also on the allowlist of the agent's environment, the sandbox it runs in. The post is one request:
CODEShellCopy
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 ..."}'
Confirm the post landed before recording it
Once a post is confirmed, the agent updates its ledger of reported items and its bookmarks. If those records don't match what was actually posted, two things can go wrong. If the agent records a post that never landed, the bookmarks move on and those items are never reported. If it posts again because it isn't sure the first post landed, readers get the same brief twice.
Three rules in agent.md prevent this. First, the agent looks for today's title in the channel's recent messages and doesn't post if the edition is already there. Second, the post counts as sent only if Slack returns "ok": true and a message ts. Third, the agent updates the ledger and bookmarks only after that confirmation. If the result is unclear, it marks the run "maybe posted" and changes nothing else, so nothing is lost.
The agent keeps a run record in its memory store (runs/<date>.md). It marks the run "posting" before the post, then "posted" with the message ID, or "maybe posted."
AGENT
In Claude Managed Agents, an agent is a versioned configuration: a model, a system prompt, and tools. Each run follows its run steps and stops.

In our reference implementation, the agent configuration is agent.md:
CODEMarkdownCopy
---
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.]
The frontmatter supplies the agent name, model, tools, and MCP servers. The body provides the agent instructions. MCP tools ask for approval by default and nobody is there to give it, so the GitHub toolset is set to always_allow and the GitHub token is read-only.
Keep the brief short
agent.md steers Claude toward brevity:
CODETextCopy
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.
Re-check anything still open right before posting
Items can change between when the agent reads sources and when it posts. Just before posting, agent.md instructs the agent to re-check the live status of each item:
CODETextCopy
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.
SCHEDULE
With Claude Managed Agents, the agent is only a configuration file; a deployment runs it. The deployment names the agent, environment, and the first message of each run. It also holds the schedule, vault, memory stores, and budget. Each time the schedule fires, the platform starts a fresh agent session.

In our template, the deployment is captured in deployment.md, with the first message as its body:
CODEMarkdownCopy
---
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>".
This creates the deployment with the agent, environment and memory stores it names by path.
CODEShellCopy
ant apply deployment.md
To test it without waiting for the schedule, start a run by hand with ant beta:deployments run --deployment-id <id>, using the ID from claude-lock.json.
Compute dates in your time zone
A common bug is the agent calling this morning "yesterday" because it computes dates in the server's time zone. In deployment.md, the timezone field sets when the run fires and the second line of the body tells the agent which zone to use for dates.
MEMORY
Each run starts in a fresh sandbox with no memory of the last one. Without memory, feedback doesn't stick. However, stale memory can confuse the agent: it reports an item as still waiting after it was resolved, or drops one that's still open because it was "already reported."

Our template keeps two memory stores, the folders mounted under /mnt/memory/ (see Sources):
-
preferences (yours, read-only to the agent): which channels and repos to read, what to leave out, the length cap, the destination, and when to stop.
-
state (the agent's, read-write): the bookmarks, a ledger of what it reported, one record per run, changes it proposes to your preferences, and notes on how each source behaves ("returns only the newest 50 items").
ant apply deployment.md creates the preferences store, but not the file in it. Before the first run, write your preferences.md there with the repo's scripts/seed-preferences.sh.
Re-read your preferences at the start of every run
A common problem occurs when a copy of the preferences is baked into the prompt, which keeps applying rules you've already changed. Have the agent read the file fresh every run. If it can't read the file, it should stop and say so instead of running on defaults.
Keep a ledger of what you've already reported, and report the change
The agent keeps a ledger, ledger.md, of every item it has reported, so the brief doesn't repeat itself. Each line records when the item was reported, where it came from, an ID that doesn't change (a Slack message timestamp or a pull request number), and its last known status:
CODETextCopy
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
GUARDRAILS
Because our automation runs in the âbackgroundâ on a schedule, we set limits on what the agent can do and what it can spend.

Limits on what it can do
The agent reads messages and issues that other people wrote, and that text can be interpreted as instructions. Limit what the agent could do if it followed them. In our example, the GitHub token and the preferences store are read-only, and the environment only reaches the hosts on its allowlist. A planted instruction can still change what the brief says, including through the notes the agent keeps between runs. It can't write to GitHub or edit your rules.
Slack is the exception: the same token posts, so invite the bot only where it needs to read or post.
Set the spending cap from real runs
A spending cap protects you from runaway costs. Start at three to five times the cost of a normal run, then tighten it as you see real numbers. A run that hits its cap pauses instead of failing, so a cap set too low looks like a brief that went quiet. The cap is the budget in deployment.md. Every run gets the full amount, and a run that reaches it pauses with a budget_reached stop reason:
CODEYAMLCopy
budget:
type: limit
max_list_cost:
amount: "500" # a string, in cents: "500" is $5.00
currency: USD
GETTING STARTED
Our reference implementation comes down to six rules:
- Read each source from a bookmark, not a fixed time window.
- Report a failed read as unreadable, never as a quiet day.
- Re-check every item right before posting.
- Count a post as sent only when Slack confirms it, then update bookmarks and ledger.
- Re-read your preferences every run, from a store the agent can't edit.
- Give the agent read-only access wherever it only reads, and cap what each run can spend.
Claude Code can walk you through the guidance provided in this article. First, update:
CODEShellCopy
claude update
Then, use the claude-api skill:
PROMPTCopy
/claude-api managed-agents-onboard https://claude.dev/blog/building-effective-agent-automations/
The claude-api skill reads this post, proposes a setup, writes the files to an agents/ folder in your project, and creates the resources with ant apply. Treat this as a starting point, and customize the agent for your sources, destination, or memory preferences.