ask is not a safety control on triggered runs. If a prompt still occurs at run time, for example on a reuse trigger whose bound session was created with an ask policy, the platform approves it automatically so the run can finish and records the resolution as auto-approved in the session transcript. Anything the agent must never do needs a deny rule.
How to Run Agents on a Schedule or From a Webhookpublic
Last verified 9 Oct 2026
DigitalOcean Harness Runtime provides managed, hardware-isolated microVM sandboxes with built-in tools such as Chromium to run harnesses and execute arbitrary code. Rich lifecycle APIs preserve conversational history and working state across sessions, letting you pause, resume, and fork work to control costs and adapt to the nonlinear nature of agentic workflows. Scale complete agents such as Claude Code or use sandboxes independently for code execution, all through the same service. See What You Can Build for example use cases.
Triggers run Harness Runtime agents without you starting a session by hand. A trigger ties together what to run (an environment spec), when to run it (a schedule or an inbound webhook), and where the result goes (email or Slack).
A cron trigger fires on a schedule, which suits recurring work such as a nightly dependency audit or a Monday summary of open pull requests. A webhook trigger fires when an external system calls a URL you are given, such as a push to a repository or an alert firing. Webhook requests are signature-verified and de-duplicated, so a sender that retries does not run your agent twice.
Write a Trigger-Ready Spec
A triggered run is unattended, so no one is at a terminal to answer approval prompts. Triggers reject a spec whose permissions default is ask, that contains any rule with action: ask, or that has no permissions block at all. Creating a trigger from such a spec fails at create time.
Copy the spec you use for interactive sessions to your environment spec file, then replace its permissions block with an allow default and explicit deny rules:
permissions:
default: allow
rules:
- tool: bash
match: { command: "rm -rf *" }
action: denyA default: deny is also accepted. On some adapters, including Codex CLI, a deny catch-all is enforced as a prompt rather than a hard block, so prefer an allow default with targeted deny rules for triggered runs. For how rules are evaluated, see Configure Agent Permissions.
Choose a Session Mode
Every trigger firing runs in one of two session modes:
- fresh: Each firing starts a new session from a spec file, does the work, and tears the session down. Use this when runs are independent of each other, such as a nightly audit.
- reuse: Each firing runs in a specific session, so the agent keeps its workspace, history, and context. The session must be paused when the trigger fires. The trigger wakes the session, runs the prompt, and then pauses the session again. Use this when the agent should keep working on the same checkout.
A session must be paused before you can bind it to a reuse trigger. To find sessions you can bind, list the reusable ones:
doctl harness-runtime triggers list-reusable-sessionsTreat a session bound to a reuse trigger as dedicated to that trigger. Attaching to the session, sending it a message, forwarding a port to it, or resuming it wakes the session. A firing that arrives while the session is awake waits about 3 minutes for the session to become available, then fails. Firings keep failing until you pause the session or it auto-pauses after 15 minutes of inactivity.
To keep a reuse trigger’s firings running, if you work in the bound session yourself, pause it when you finish. Let your own turn finish first, because pausing mid-turn freezes that turn:
doctl harness-runtime pause <session>Do not cancel a turn that the trigger started. In an attached session, pressing Ctrl+C while a turn is running cancels that turn. A cancelled turn fails the trigger’s execution, and the run ends with a turn cancelled failure.
For hands-on work, use a separate session, or create the trigger with --session-mode fresh so each firing gets its own session.
Create a Scheduled Trigger
Create the trigger with the schedule, the prompt to send on each firing, and where to deliver the output:
doctl harness-runtime triggers create \
--kind cron \
--name nightly-audit \
--session-mode fresh \
--spec codex-trigger-spec.yaml \
--prompt "Clone <your-repository-url> and run a dependency audit. Summarize anything outdated." \
--cron-expr "0 2 * * *" \
--timezone America/New_York \
--output-mode email \
--output-email <your-email-address>The flags control the following:
--name: Unique across your team’s triggers.--session-modeand--spec: Infreshmode, every firing starts a new session from this spec. Forreusemode, pass--bound-session-idinstead of--spec.--prompt: The message sent to the agent on each firing, exactly as if you had typed it in an attached terminal.--cron-exprand--timezone: A standard five-field cron expression, interpreted in the IANA timezone you give.--output-mode: Where the agent’s final output is delivered. Useemail,slackwith an incoming webhook URL passed to--output-slack-webhook, ornone.
Create a Webhook Trigger
A webhook trigger takes the same form as a cron trigger without the schedule, plus a --provider that tells the platform how to verify signatures:
doctl harness-runtime triggers create \
--kind webhook \
--name pr-reviewer \
--session-mode fresh \
--spec codex-trigger-spec.yaml \
--provider github \
--prompt "Review this pull request event and summarize the changes: {{payload}}" \
--output-mode slack \
--output-slack-webhook <your-slack-incoming-webhook-url>The response includes the trigger’s webhook URL and secret. The secret is shown once, so copy it before you move on, then paste both values into the sending system. For GitHub, this is the repository’s webhook settings. The URL is authenticated by this secret rather than your DigitalOcean token. If you lose the secret, issue a new one with doctl harness-runtime triggers rotate-secret.
Supported providers are github, gitlab, and custom, which is plain HMAC and the default. To see the current list, run doctl harness-runtime triggers list-providers.
You can reference the inbound event in the prompt with two placeholders. Use {{payload}} to embed the entire delivery body verbatim, or {{.field}} and {{.a.b}} to pull a single field out of a JSON payload, such as {{.pull_request.html_url}}. A missing field becomes an empty string. A scheduled trigger cannot use these placeholders because a scheduled firing has no payload.
Review Trigger Executions
Each firing is recorded as an execution. List the executions for a trigger, then inspect one:
doctl harness-runtime triggers list-executions <trigger-id>
doctl harness-runtime triggers get-execution <trigger-id> <execution-id>An execution moves through pending and running, then ends as succeeded or failed with a failure reason. The list view shows status at a glance, and get-execution includes the inbound payload and the agent’s rendered output.
Every execution records its session ID, so you can replay the session’s event history with doctl harness-runtime logs <session-id>. Very old or long histories may show only recent events. To go the other way and find which trigger produced a session, use get-by-session.
Use doctl harness-runtime triggers list-executions, doctl harness-runtime triggers get-execution, and doctl harness-runtime logs to read the results of a reuse trigger. These commands do not wake the bound session, so they do not interfere with the next firing.
If an execution fails with The bound session did not become available in time for this run., the reuse trigger’s bound session was awake when the trigger fired. Pause the session with doctl harness-runtime pause <session> so the next firing can run. To avoid this failure, see Choose a Session Mode.
Manage Triggers
Use the following commands to inspect and change existing triggers:
| Command | What it does |
|---|---|
doctl harness-runtime triggers list |
Show all your triggers and their status |
doctl harness-runtime triggers get <trigger-id> |
Show details for one trigger |
doctl harness-runtime triggers update <trigger-id> |
Change the prompt, schedule, spec, or output settings |
doctl harness-runtime triggers pause <trigger-id> |
Stop firing and keep the configuration |
doctl harness-runtime triggers resume <trigger-id> |
Start firing again from the next event or tick |
doctl harness-runtime triggers rotate-secret <trigger-id> |
Issue a new webhook secret, shown once |
doctl harness-runtime triggers delete <trigger-id> |
Remove the trigger |
Note the following before you rely on these commands:
- A trigger’s kind and webhook provider cannot be changed after creation. Create a new trigger instead.
- Resuming a paused trigger does not replay firings that were missed while it was paused.
- Deleting a reuse trigger unbinds its session but does not destroy the session.