Scheduled Agents That Fail Loudly

A daily-brief agent is easy to build and easy to get quietly wrong. This course takes Anthropic’s reference implementation apart, argues for what it gets right (bookmarks, a ledger, verify-then-post), and says where its guardrails and its own failure table stop short.

7 modules
4 commit-first scenarios
3 predict-the-number
~70 min

Spine: claude.dev, Building effective agent automations (Lance Martin and CJ Avilla, 8 Oct 2026) · reference code: the daily-brief quickstart · platform behaviour: Managed Agents docs and scheduled deployments

How to read this course

The source is an Anthropic post about an Anthropic product, currently in beta, and it reports no failure rates and no cost figures from a real deployment. Everything below the article’s own claims is argument, and I mark which is which. The docs overview I checked describes scheduled deployments only in passing and states that Managed Agents behaviour “may change between releases”, so treat field names (max_list_cost, budget_reached) as the article’s, not as verified API contract.

Modules 2, 3, 4, 5 and 6 make you commit to an answer before the reveal. The wrong answers are the intuitive ones, which is the point.

Course Modules

  1. What the reference agent isFoundations
  2. The quiet-failure problemScenario
  3. Bookmarks beat time windowsPredict
  4. Verify, post, then recordScenario
  5. Guardrails and the budget capScenario + Predict
  6. Where the article stops shortFailure clinic
  7. Quiz, flashcards & Monday checklistReview
1

What the reference agent is

Six components, eight files, one post per weekday
Learning objectives
  • Name the six components of a scheduled agent automation
  • Say which parts of the daily-brief agent need a model and which do not
  • State the article’s central claim in one sentence

The artefact

The post walks through daily-brief, a quickstart that reads a list of Slack channels and GitHub pull requests each weekday morning, works out what changed since its last run, and posts one dated brief to a Slack channel. It runs on Claude Managed Agents, which is in beta, and is deployed with the ant apply CLI command. After deployment it runs on Anthropic’s infrastructure, not yours.

The article breaks any such automation into six parts:

ComponentIn daily-briefWhere it lives
SourcesSlack channels, GitHub PRsPreferences store
DestinationOne Slack channelPreferences store
AgentModel, tools, numbered run stepsagent.md
ScheduleCron 32 7 * * 1-5, America/New_Yorkdeployment.md
MemoryRead-only preferences; read-write stateTwo memory stores
GuardrailsRead-only tokens, network allowlist, per-run budgetVault, environment, deployment

Each firing starts a fresh session. That one fact explains most of the design: the agent remembers nothing between runs unless it wrote it down, so everything it needs to be correct tomorrow has to be in the state store today.

The thesis

Source claim

Simple scheduled agents “fail quietly”: they lose access to a source without anyone noticing, or ignore what the reader wanted. The six rules in the article all push the same way: make the run report on its own health.

My restatement, which is sharper than the article’s: an unattended agent has two outputs, the brief and a statement of how much to trust the brief. Most builders ship only the first. A brief that is wrong looks identical to a brief that is right, and nobody is in the room when it runs, so the second output is the only monitoring you get.

What needs a model

Look at the run steps and sort them. Listing PRs since a timestamp, reading the preferences file, checking a ledger for duplicates, and calling chat.postMessage are deterministic. Deciding which items “the reader would act on today” and writing the line is judgment. We come back to this in module 6, because it changes the architecture you should choose.

Takeaways
  • Six components: sources, destination, agent, schedule, memory, guardrails.
  • Fresh session per run, so state must be written to the memory store.
  • The real product is a trust statement about the brief, not just the brief.
2

The quiet-failure problem

A failed read looks exactly like a quiet day
Learning objectives
  • Explain why “nothing new” and “could not read” must be different outputs
  • Choose the right behaviour when one of several sources fails

Why this is the hard part

A scheduled agent has no user to say “that looks wrong.” Token rotated, channel archived, bot removed from a channel, rate limit, a repo renamed: each produces an empty result, and an empty result is also what a genuinely quiet Tuesday produces. If the agent treats empty as quiet, the reader learns to trust a brief that has been silently missing a source for three weeks.

Source fix

On a failed read, keep that source’s bookmark where it was, report from the sources that did work, and add a line naming the unreadable one.

Keeping the bookmark is the part people skip. If you advance it anyway, the items that arrived during the outage are gone for good; if you keep it, the next successful run picks them up.

Where I disagree

The article stops at naming the unreadable source inside the brief. That still depends on someone reading the brief closely. If all three sources fail, you post a brief that says so, which is correct, but a person scanning for action items will skim it. A total failure should not look like a brief at all; it should arrive as an alert in a different place. We return to this in module 5.

Takeaways
  • Distinguish empty from unreadable, per source, every run.
  • Do not advance a bookmark past a failed read.
  • Partial failure goes in the brief; total failure needs a separate alert path.
3

Bookmarks beat time windows

Gaps, duplicates, and an off-by-one the article skips
Learning objectives
  • Compute how many items a fixed window loses after a skipped or late run
  • Describe how a per-source bookmark removes both failure modes
  • Name the one case bookmarks do not fix

The mechanism

The tempting design reads “the last 24 hours.” It works as long as every run fires on time and succeeds. The article’s alternative: store the newest timestamp read from each source in bookmarks.json in the state store, and next run read everything newer. The window becomes whatever the gap actually was.

Do the arithmetic before reading on. A channel receives 6 messages an hour, all day.

An extension of the article’s claim

The article says a fixed window leaves gaps on late runs and duplicates on early ones. Working it through, an early run does both: if today’s run fires three hours early, it re-reports three hours of yesterday’s items, and tomorrow’s on-time run starts three hours after today’s run ended, so three hours of items fall in nobody’s window. Window designs do not just have two failure modes; they lose data on any timing deviation in either direction.

What bookmarks do not fix

A timestamp bookmark assumes the source orders items by the field you bookmark. Slack messages are ordered by ts, which is fine. GitHub PRs are not: a PR opened last week can be updated, reviewed or merged today, and the bookmark on “created” will never see it again. You need to bookmark updated_at and then handle the fact that most updated PRs are not news. That is exactly what the article’s ledger of reported items and its rule to re-check live status before posting are for. Bookmark and ledger solve different problems: the bookmark answers “what have I not looked at yet,” the ledger answers “what have I already told the reader.”

▶ Animated recap · 1:36
Two hosts put numbers on a fixed 24-hour window — a skipped run loses 144 messages, a late one 18, an early one duplicates and then loses — and show why a per-source bookmark is immune to timing. Best watched after the predict exercises above.
Takeaways
  • A skipped run loses a whole window; a late run loses the lateness; an early run both duplicates and, next day, loses.
  • Bookmark per source, on the field the source actually orders and updates by.
  • Bookmark = what I have read. Ledger = what I have told you. You need both.
4

Verify, post, then record

Ordering of side effects decides whether a crash loses or duplicates
Learning objectives
  • Order the check, post, and state-write steps and predict what each crash point leaves behind
  • Judge the article’s dedupe-by-title technique

Three rules, one idea

Three of the article’s fixes are really one rule about the order of side effects. Re-check each item’s live status right before posting and drop what you cannot confirm. Count the post as sent only when Slack returns "ok": true with a message ts. Update the ledger and bookmarks only after that confirmation. Do the irreversible external effect, confirm it, then commit your own bookkeeping.

That ordering means the worst case is a duplicate post, never a lost one. For a daily brief a duplicate is a mild annoyance and a missing brief is the failure the whole design exists to prevent, so this is the right trade. Test whether you can see why.

The dedupe check

Source claim

Before posting, look for today’s brief title in recent channel messages; if the agent cannot tell whether a post landed, mark the run “maybe posted.”

This is a reasonable idempotency guard and also a weak one. It reads channel history through a different API path than the post, so it can lag the write. It matches on a human-readable title that the agent itself generates and could vary. And “maybe posted” is a status a human has to resolve. As far as I know chat.postMessage has no idempotency key, so the best you can do is to derive a deterministic marker (date plus a short hash of the destination) and put it in the message text or metadata, then search for that marker rather than for prose. The cost is a few characters of slightly ugly footer; the gain is that the check stops depending on the model’s wording.

Takeaways
  • Confirm, then record: a crash then duplicates instead of loses.
  • “Maybe posted” is an honest state; make sure someone can see it.
  • Dedupe on a deterministic marker, not on a title the model writes.
5

Guardrails and the budget cap

What read-only really limits, and what a low cap looks like
Learning objectives
  • State what the article’s read-only design does and does not protect against
  • Compute the real worst-case monthly cost of a per-run cap
  • Design an alert path that does not depend on the agent

The article’s security claim

Source claim

Because the GitHub token and the preferences store are read-only and the network is limited to an allowlist, a prompt injection “can distort the brief but can’t write to GitHub or change the rules.”

That is true and it is narrower than it sounds. The credentials design is good: the sandbox sees only a placeholder $SLACK_BOT_TOKEN and the platform swaps in the real token for allowed hosts, so a leaked environment variable is not a leaked token. But look at what the agent can do with its legitimate powers. It reads untrusted text (PR titles, bodies, Slack messages) and it holds a bot token that can post to Slack. An instruction hidden in a PR description can ask the agent to include content from a different channel in today’s brief. Read-only on GitHub does not stop that, and the destination channel may have a wider audience than the source. The right threat model is not “can it write to GitHub” but “what can it read, and who can see where it posts.”

The budget cap

The article suggests starting the per-run cap at 3 to 5 times a normal run’s cost and tightening it, with an example of 500 cents, or $5. It also warns that a run that hits the cap pauses with budget_reached, so a cap set too low looks like a quiet day. That last point is the same quiet-failure problem again, now caused by the guardrail itself.

The missing component: a heartbeat

Every control so far lives inside the agent or its platform. If the run is paused, never fires, or posts nothing, the agent cannot tell you. Add a check outside the agent: after the scheduled time, something independent (a second cheap scheduled job, or a monitor on the channel) asks “is there a brief for today?” and alerts a different channel if not. That check also catches budget_reached, expired credentials, a disabled schedule, and an archived destination, none of which the run itself can report. It is the cheapest piece of the whole system and the article does not include it.

Takeaways
  • Read-only limits writes, not exfiltration into the destination channel.
  • A per-run cap is a blast-radius limit; multiply it out to see the monthly ceiling.
  • A silent pause looks like a quiet day: monitor from outside the agent.
6

Where the article stops short

Self-serving, version-specific, or unmeasured — plus a failure clinic
Learning objectives
  • Separate the article’s durable lessons from its product-specific ones
  • Decide when a scheduled agent should be a script with a model call inside it
  • Diagnose symptoms back to the rule they violate

What holds up

The six rules are sound engineering for any unattended job, not just agent jobs: incremental reads from a checkpoint, explicit failure reporting, verification before side effects, confirm-then-commit, config re-read each run, least privilege and a spending limit. None depends on Claude or on Managed Agents. That is the highest compliment I can pay them: they would survive a platform change.

What to discount

Unmeasured. The failure table is a list of things that went wrong, not a distribution. There is no count of how often each occurred, no cost per run, no comparison with a plain script. Without that you cannot rank the fixes, and I suspect the ranking is lopsided: wrong dates and duplicates are common and cheap, prompt injection is rare and expensive.

Product-specific. Field names, the ant CLI, claude-lock.json, the beta header, and the budget fields are one product’s surface at one moment. The docs say Managed Agents is a beta whose behaviour may change.

Self-serving by default. The recommended setup path is to run a Claude Code skill against the article’s URL and let it write an agents/ folder and create cloud resources. Convenient, and an instruction-from-a-web-page pipeline in the very post about prompt injection. The article does say to treat the output as a starting point; I would go further and read every generated file, especially vault.yaml and the network allowlist, before applying.

The architecture question. Most of the daily-brief run steps are deterministic. A small script on a cron could fetch since the bookmark, check the ledger, and post, calling a model once to rank and phrase the items. That version is cheaper per run, testable without a model, and cannot get a bookmark or ledger step wrong because the model decided to skip it. The agent version wins when the sources are messy and the steps genuinely vary, and loses when they do not. My position: start with the script-plus-one-model-call and promote to an agent only when a step needs the model to choose its own tools. The article never makes this comparison, and it is the most useful decision in the whole area.

Takeaways
  • The rules transfer; the field names do not.
  • Ask for a failure distribution and a plain-script baseline before trusting a failure table.
  • Deterministic steps belong in code, judgment in the model.
7

Quiz, flashcards & Monday checklist

Retrieval practice, then what to do differently

Quiz

Flashcards

Click a card to flip it.

Monday checklist for any scheduled agent

  1. List each source and write down, for each, the field you will bookmark and what “unreadable” looks like.
  2. Decide where the bookmark and the ledger are written, and make both writes happen only after the post is confirmed.
  3. Replace title-based dedupe with a deterministic marker.
  4. Write the threat model as “what can it read, and who sees where it posts,” then trim connectors and channels to match.
  5. Compute the monthly worst case of your budget cap and write it next to the cron expression.
  6. Add an external heartbeat that alerts somewhere other than the destination channel.
  7. Ask whether this should be a script with one model call.