Event-triggered and schedule-triggered automations with natural-language conditions. Use when creating automations, wiring events, or understanding how triggers fire.
64
81%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Low
Low-risk findings worth noting
Automations are the user-facing umbrella for agent-executed tasks that fire in
response to events or on a cron schedule. Scheduled and Event are the
two trigger types. Each automation is a markdown resource under jobs/ with
YAML frontmatter describing when and how it fires, and a body containing
natural-language instructions the agent follows.
Recurring Jobs is the legacy name and API for scheduled automations.
manage-jobs, jobs/, and /agent#jobs remain stable compatibility surfaces.
Use manage-automations for new personal or organization automations, including
per-automation model overrides and MCP allowlists. Keep manage-jobs for
existing schedule-only integrations and delivery metadata.
| Type | Fires when | Key field |
|---|---|---|
schedule | Cron expression matches (same as recurring jobs) | schedule (cron) |
event | A matching event is emitted on the event bus | event (event name) |
Event triggers can optionally include a condition -- a natural-language string evaluated by Haiku against the event payload before dispatch. If the condition does not match, the automation is skipped.
/settings/automations; open it with open-settings-page page automations).manage-automations with action=list-events to discover available events.manage-automations with action=define to write a jobs/<name>.md resource.lastRun, lastStatus, lastError) is written back to the resource frontmatter.Trigger acquisition stays separate by design: the scheduler decides when a cron expression is due, while the event dispatcher matches event names, owners, and conditions. Everything after a trigger is accepted uses the same execution lifecycle.
---
schedule: ""
enabled: true
triggerType: event
event: calendar.booking.created
condition: "attendee email ends with @example.com"
mode: agentic
domain: calendar
createdBy: user@example.com
runAs: creator
---
Send a Slack message to #sales with the booking details.
Use the web-request tool with ${keys.SLACK_WEBHOOK}.| Field | Type | Purpose |
|---|---|---|
schedule | string | Cron expression; new scheduled automations default to once per hour (0 * * * *) when omitted |
enabled | boolean | Whether the automation is active |
triggerType | "schedule" | "event" | How the automation fires |
event | string? | Event name to subscribe to (event triggers) |
condition | string? | Natural-language condition evaluated before dispatch |
mode | "agentic" | Full agent loop (only supported mode; "deterministic" was removed — never implemented, rejected at define time) |
model | string? | Override the model for this trigger's agent loop |
reasoningEffort | string? | Override reasoning effort for this trigger's model; omitted uses the model's default |
domain | string? | Grouping tag (mail, calendar, clips, etc.) |
createdBy | string? | Creator email; required for organization event automations |
orgId | string? | Organization scope |
runAs | "creator" | "shared" | Execution identity; trigger-aware automations use creator |
mcpTools | string[]? | Exact MCP tool allowlist for this automation |
lastRun | string? | ISO timestamp of last execution |
lastStatus | string? | success, error, running, skipped, or paused |
lastError | string? | The real cause of the last failed run |
lastErrorCode | string? | Typed code of the last failure (see Failure handling) |
consecutiveFailures | number? | Run of identical lastErrorCode failures |
pausedReason | string? | Set with enabled: false when the framework paused it |
pausedAt | string? | ISO timestamp of that pause |
All automation operations are accessed through a single manage-automations tool with an action parameter:
| Action | Purpose |
|---|---|
list-events | Discover all registered events with descriptions and payload schemas |
list | List all automations with status, filter by domain or enabled |
define | Create a new automation (name, trigger type, event, condition, body) |
update | Update an existing automation (enabled, condition, body) |
delete | Delete an automation (always confirm with user first) |
fire-test | Emit a test.event.fired event to validate automations |
run-now | Run one automation immediately with its real actions and side effects |
Additional tool: web-request — outbound HTTP with ${keys.NAME} substitution.
manage-automations accepts personal or organization scope and supports
model, reasoning_effort, and mcpTools on define/update. An MCP allowlist
is enforced, not advisory: every named tool must resolve in the creator's
request context or the run fails clearly, and the runner never widens access
beyond the configured names.
Organization event automations are visible to organization members but always run as their creator:
createdBy is required and runAs must be creator.Integrations register events at module load time. The bus validates payloads against Standard Schema definitions and dispatches to subscribers.
import { registerEvent, emit } from "@agent-native/core/event-bus";
import { z } from "zod";
// Register an event type (typically in a server plugin)
registerEvent({
name: "calendar.booking.created",
description: "A new calendar booking was created",
payloadSchema: z.object({
bookingId: z.string(),
attendeeEmail: z.string(),
startTime: z.string(),
}),
example: { bookingId: "abc", attendeeEmail: "jane@co.com", startTime: "2025-01-15T10:00:00Z" },
});
// Emit the event (from an action, webhook handler, etc.)
emit("calendar.booking.created", {
bookingId: "abc",
attendeeEmail: "jane@co.com",
startTime: "2025-01-15T10:00:00Z",
}, { owner: "user@example.com" });| Event | Source |
|---|---|
test.event.fired | Manual / manage-automations action=fire-test |
agent.turn.completed | Agent chat |
calendar.* | Calendar integration |
clip.* | Clips integration |
mail.* | Mail integration |
| Function | Purpose |
|---|---|
registerEvent | Declare an event type with schema |
emit | Fire an event (validates payload) |
subscribe | Listen for an event (returns subscription ID) |
unsubscribe | Remove a subscription by ID |
listEvents | List all registered event definitions |
When an automation has a condition, the dispatcher calls the configured fast/classification model to classify whether the event payload satisfies the condition. This is a yes/no classification, not a generation task. The exact model ID lives in condition-evaluator.ts.
false (safe default -- skips the automation).web-request Tool and KeysAutomations use the web-request tool for outbound HTTP. It supports ${keys.NAME} placeholders in the URL, headers, and body. These are resolved server-side after the agent emits the tool call -- the raw secret value never enters the agent's context.
/_agent-native/secrets/adhoc API.resolveKeyReferences() resolves placeholders, falling back from user scope to workspace scope.validateUrlAllowlist() checks the resolved URL against per-key allowlists (origin-level matching).${keys.NAME} and synthetic example.com identities.An automation fails once, with its real cause, instead of re-failing every
tick. The runner classifies each failure (jobs/automation-outcome.ts):
| Class | Codes | Pauses after |
|---|---|---|
| Precondition | missing_credentials, missing_tools, owner_missing, owner_reserved, config_invalid | 3 identical (owner/identity failures: immediately) |
| Runtime | the run's own code, e.g. http_502 | 5, with a widening gap between attempts |
lastFailedEventId): queue retries of the same event never pause it alone.agent_runs row exists: no
"Job:" thread per tick. The failure is recorded on the automation and its run
history only. Never write a generic "ended with status: errored"; surface the
run's own error.enabled: false, lastStatus: paused, and
pausedReason/pausedAt. The owner is emailed once by the run that paused
it (for organization and shared jobs: the creator while still a member,
otherwise an org owner or admin). Enabling the automation clears the pause
and the streak. One paused for an absent credential (missing_credentials)
resumes by itself once its identity has a usable LLM credential; a rejected
key stays paused until the owner enables it again.running, remote
reconcile); it never moves the streak, and resumes the job only on success.automation_paused
(error_code, failure_kind, consecutive_failures, surface) and
automation_resumed (via) carry automation_hash, the first 12 hex
characters of sha256 of <app>|<name>, never the name. A failed run's
capture carries its own thread, run and automation name in
extra.failureContext.runAs: shared and organization jobs run as the organization, never as the
creator's personal connection: only an org admin can connect the provider..test, .invalid, .example, example.com|org|net) in production, are
disabled with owner_missing / owner_reserved. Absence is proven only by a
populated built-in "user" table (case-insensitive); a lookup error, or a
deployment whose accounts live elsewhere (custom getSession), is never read
as "deleted".automation_runs stuck running for over 25
hours (interrupted, automation_run_abandoned) and A2A tasks idle for 24
hours (failed, a2a_task_abandoned), a bounded batch at a time.The full-page Agent surface's Automations tab is the primary management
surface for scheduled and event-triggered automations. Users can view status,
enable/disable, inspect, and delete automations there. Its URL remains
/agent#jobs for compatibility even though the visible tab is Automations.
Creation typically happens through the agent chat.
User: "When someone books a meeting with a @example.com email, message me in Slack."
Agent flow:
manage-automations with action=list-events to find calendar.booking.created.manage-automations with action=define:
name: slack-on-example-bookingtrigger_type: eventevent: calendar.booking.createdcondition: attendee email ends with @example.commode: agenticdomain: calendarbody: Send a Slack message to #sales with the booking details. Use the web-request tool to POST to ${keys.SLACK_WEBHOOK}.| File | Purpose |
|---|---|
packages/core/src/triggers/types.ts | TriggerFrontmatter interface |
packages/core/src/triggers/actions.ts | Agent tools (define, list, update, delete, test) |
packages/core/src/triggers/dispatcher.ts | Event subscription and agentic dispatch |
packages/core/src/jobs/background-automation-runner.ts | Shared schedule/event execution lifecycle |
packages/core/src/jobs/automation-outcome.ts | Failure classification, pause thresholds, reserved identities |
packages/core/src/jobs/stale-reaper.ts | Bounded reaping of stuck runs and A2A tasks |
packages/core/src/triggers/condition-evaluator.ts | Haiku condition classification with caching |
packages/core/src/event-bus/ | Event bus (register, emit, subscribe) |
packages/core/src/tools/fetch-tool.ts | web-request tool with key substitution |
packages/core/src/secrets/substitution.ts | resolveKeyReferences() and validateUrlAllowlist() |
recurring-jobs -- schedule-triggered automations reuse the same schedulersecrets -- ad-hoc keys and ${keys.NAME} substitutionactions -- automations can call any registered action via the agent loopdelegate-to-agent -- agentic mode runs a full runAgentLoopa941a2e
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.