One global file describes every agent the daemon runs. Each agent maps a name to a workspace, a command, and how to transform incoming A2A tasks.
The daemon reads ~/.wakeup/config.yaml by default (global-first). A project-local ./.wakeup/config.yaml is used when you pass --local, and --config <path> overrides discovery entirely. Agent names are the keys under agents: and follow the same rules as handles (3–40 chars, lowercase letters, numbers, underscores).
| Field | Description |
|---|---|
| workspace | Required. Absolute (or ~-prefixed) path to the directory the command runs in. Put the agent's CLAUDE.md and MCP config here. |
| command | Required. The shell command to invoke, e.g. "claude -p" or "./handle-task.ts". |
| transform_template | Optional. Hydrated from the incoming message and passed to the command as a prompt argument. Omit for handlers that read raw task JSON from stdin. |
| timeout | How long to wait before marking the task failed. Format: 300s, 5m, 2h. Default 300s. |
| on_failure | What to do on a non-zero exit: ignore | retry | dead_letter. Default ignore. |
| env | Optional map of extra environment variables for the command. Values support {{env.KEY}} expansion from the daemon's environment. |
| schedules | Optional list of local cron schedules. See below. |
When present, the template is hydrated from the incoming A2A message and passed to the command — spliced after -p if the command has that flag, otherwise appended as the final argument. The raw {taskId, contextId, message} JSON is always available on stdin too. Supported tokens:
| Field | Description |
|---|---|
| {{message.text}} | All text parts of the message, joined by newlines. |
| {{message.json}} | The full A2A Message as pretty-printed JSON. |
| {{parts.json}} | The message.parts array as pretty-printed JSON. |
| {{data.json}} | The first data part's data object as pretty-printed JSON. |
| {{data.<field>}} | A single field from that data object. |
| {{task.id}} | The task id. |
| {{context.id}} | The context (conversation) id. |
| {{source}} | Origin of the task: a2a, local:cron, or local:test. |
The daemon also exports WAKEUP_TASK_ID, WAKEUP_CONTEXT_ID, and WAKEUP_SOURCE into the command's environment.
Schedules fire local tasks on a cron expression — even without a wakeup.sh account. Each firing synthesises an A2A message whose first data part is the schedule's payload, so {{data.<field>}} resolves your payload fields. A schedule is skipped if its previous run is still in flight.
| Field | Description |
|---|---|
| name | A label for the schedule, unique per agent. |
| cron | Standard 5-field cron expression, e.g. "0 9 * * 1-5". |
| event.type | A dot-separated type string you choose, surfaced in the message metadata. |
| event.payload | An object passed through as the message's data part. |
# ~/.wakeup/config.yaml
agents:
conductor:
workspace: "/Users/matt/projects/conductor"
command: "claude -p"
transform_template: |
You received a {{source}} task.
{{message.text}}
timeout: "5m"
on_failure: "ignore"
schedules:
- name: "daily-review"
cron: "0 9 * * 1-5"
event:
type: "schedule.daily"
payload:
task: "Review recent changes and summarize."
webhook-forwarder:
workspace: "/Users/matt/projects/forwarder"
command: "./forward.ts" # reads {taskId, contextId, message} from stdin
timeout: "30s"
on_failure: "retry"