Skip to content

Morning briefing job

A cron job that wakes an agent every weekday morning, has it read the state of your workspace, and leaves a written briefing waiting for you.

ShippedFor people running agent work6 pages in this section

What you build

A scheduled job that runs before you sit down. Every weekday at 08:30 the daemon dispatches an agent into your workspace, the agent reads what changed and what is still open, and it writes a briefing you read instead of reconstructing.

Nothing about it is interactive. The scheduler fires on its own clock, the run is recorded in automation_runs whether or not anyone is watching, and a failed run retries on a backoff you set.

Use it when you want the state of a project summarized on a fixed rhythm rather than on demand.

The artifact

Config-backed jobs live in config.toml. This is the whole definition.

config.toml
[[automation.jobs]]
scope = "workspace"
workspace = "/Users/you/src/checkout-api"
name = "morning-briefing"
agent = "operator"
prompt = """
Write this morning's briefing for the checkout-api workspace.

Cover, in this order:
1. What changed in the working tree and on the current branch since yesterday.
2. Any session that stopped with an error, and the stop reason.
3. Open tasks under .compozy/tasks that are still pending.
4. Anything that needs a decision from a person today.

Keep it under 300 words. Lead with what needs attention, not with what is fine.
"""
schedule = { mode = "cron", expr = "30 8 * * 1-5" }
retry = { strategy = "backoff", max_retries = 2, base_delay = "5m" }
fire_limit = { max = 4, window = "24h" }

Schedules are evaluated in the automation timezone, which defaults to UTC — set it explicitly if 08:30 should mean your local morning.

Run it

Before loading this definition or creating the dynamic job, make sure operator is an effective agent definition for this workspace. The scheduler stores the agent name, but the dispatcher resolves the agent and its provider when it starts the session. Configure the provider and its credentials first, then inspect the resolved definition:

compozy agent info operator --workspace /Users/you/src/checkout-api

For a custom automation agent, create a workspace-local or global definition before this step and make sure its effective runtime has a configured provider. See Agent definitions for the resolution cascade.

Create the same job dynamically, without editing config:

job="$(
  compozy automation jobs create \
  --name morning-briefing \
  --scope workspace \
  --workspace /Users/you/src/checkout-api \
  --schedule "30 8 * * 1-5" \
  --catch-up-policy run_once_on_catchup \
  --misfire-grace-seconds 120 \
  --agent operator \
  --prompt "Write this morning's briefing for the checkout-api workspace." \
  -o json
)"
job_id="$(printf '%s' "$job" | jq -er '.id')"

Then confirm it registered, fire it once by hand instead of waiting for tomorrow, and read the run history. This uses jq to read the generated ID from the create response; a job name is not accepted by the trigger or run-history commands.

compozy automation jobs get "$job_id" -o json
compozy automation jobs trigger "$job_id"
compozy automation runs --job-id "$job_id" -o json

How it works

The scheduler owns the clock

Compozy keeps durable scheduler state, so a job does not silently skip because the daemon restarted. catch-up-policy run_once_on_catchup runs a single make-up execution after downtime instead of replaying every missed fire, and the misfire grace window bounds how late a fire may still count.

The dispatcher applies the limits

Every fire passes the fire-limit and concurrency checks before a run is created. fire_limit caps this job at 4 runs per day no matter what triggers it; the global automation concurrency limit defaults to 5 active jobs across the daemon.

The run is a real session

An agent target starts an agent session with the prompt. It is durable and inspectable like any other session — you can open it later and read exactly what the agent saw and did.

Failure is retried, then recorded

With strategy = "backoff" a failed run is redispatched up to max_retries times starting at base_delay. When retries are exhausted the run lands in history as failed rather than disappearing.

Point it at a Loop instead

Set target_kind = "loop" and a loop_target to run a published Loop on the schedule instead of a bare prompt. Scheduled jobs pass static loop_target.inputs only — a clock fire has no activation envelope, so input_mapping is rejected.

Next steps

On this page