Skip to main content
This example schedules a reporter. Every morning a heartbeat trigger spawns a session that reads the last 24 hours of merged PRs and direct-to-main commits, checks whether they deployed cleanly, and writes a structured digest — delivered as the run’s report and, when Slack is connected, as one summary line in the channel with the full digest threaded beneath it. Use it when “what shipped yesterday?” is a question someone answers by scrolling, and you want the answer produced on a clock by an agent that is incapable of changing anything.

How it works

A heartbeat trigger has no event key: it declares kind: heartbeat with a cron expression and an optional timezone (default UTC). At apply time, auto creates one durable Temporal schedule per heartbeat trigger; each tick records a normal internal event and routes it like any other. Only the declaring agent’s heartbeat trigger can match its synthetic event key, and each tick is deduplicated by schedule and scheduled time, so missed-tick catch-ups never double-fire. See cron and webhooks. The tick payload is small but load-bearing:
The prompt anchors the reporting window on {{heartbeat.scheduledAt}} — the scheduled time, not the moment the session happened to start — so the window stays exact even when a run starts late. The other design decision is that the reporter is read-only by construction: the mount grants contents: read, the GitHub tool surface is the read/search set, and the instructions forbid running tests or builds. A reporter that cannot write cannot break anything.

Install from the consolidated agent package

The packaged Ship Digest role is published in @auto/agents:
.auto/agents/ship-digest.yaml
Slack delivery is zero-configuration: the template uses the standard slack connection name and #dev channel as optional: true wiring — apply skips it while no slack connection exists and activates it on the next apply once one does; until then the run report stands alone. Merge for GitHub Sync to apply.

The full configuration

.auto/fragments/environments/agent-runtime.yaml
.auto/agents/ship-digest.yaml
Details that matter:
  • depth: 300 on the mount. Analyzing a time window needs more git history than a default shallow clone; the prompt still teaches the agent to git fetch --shallow-since when the window outruns the checkout.
  • issues: write exists only for the opt-in tracking-issue fallback described in the prompt; the default delivery writes nothing to GitHub. Tighten it to issues: read and drop issue_write/add_issue_comment if you never want that path available.
  • The mention trigger is a second, human entry point — an unscheduled digest on request. A manual or mention run has no heartbeat payload, so the request itself must define the window; the mention message tells the agent to ask for it when it is missing.

Walkthrough

1

Apply creates the schedule

On apply, auto reconciles one Temporal schedule for the digest-heartbeat trigger. Edit the cron or timezone and re-apply, and the schedule updates; delete the trigger and the schedule goes with it.
2

08:00 America/Los_Angeles: the tick fires

The schedule records a heartbeat event carrying heartbeat.scheduledAt and routes it. The trigger’s routing: { kind: spawn } starts a fresh digest session for this tick. Duplicate ticks are impossible: the event’s dedup key is the schedule id plus the scheduled timestamp.
3

The session gathers the window

The sandbox boots with acme/widgets@main mounted (300 commits deep). The agent computes the 24-hour window ending at scheduledAt, then collects merged PRs (search_pull_requests), direct-to-main commits (git log --first-parent with explicit ISO bounds), per-PR bodies and diffs (pull_request_read), and CI runs on main (actions_list) to confirm the merges deployed.
4

The digest lands twice

The session’s final message is the digest itself — readable later from the project’s sessions view. When Slack is connected, the agent also calls chat.send once for the one-sentence summary in #dev, keeps the returned threadId, and threads the full digest as a single reply, so the channel stays scannable.

Variations

  • Pick your clock. cron accepts standard 5-field expressions (0 8 * * 1-5 for weekdays) and timezone any IANA zone; the string is passed to the underlying scheduler verbatim.
  • Weekly instead of daily. cron: 0 8 * * 1 and change the prompt’s window arithmetic from 24 hours to 7 days — the window is defined entirely in the prompt.
  • Richer destinations. Add a hosted connection tool (Notion is a built-in provider) or any remote MCP server and publish the full digest there, letting the Slack message carry a link instead of a thread. See tools.
  • Digest more than one repo. Add a second git mount and extend the prompt’s gather list; mounts merge by mountPath, so imports and overrides compose cleanly.
  • Smoke test without waiting for the cron. Start a session directly from the web app with the message “Produce a digest for the last 24 hours ending now.” and confirm the report and the Slack thread. Manual runs have no {{heartbeat.scheduledAt}}, so the message supplies the window explicitly.