CtrlK
BlogDocsLog inGet started
Tessl Logo
SkillPublic

automations

Manual, event-triggered, webhook-triggered, and schedule-triggered automations with optional event conditions. Use when creating automations, wiring events or webhooks, running one on demand, or understanding triggers.

Quality

80%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide
SecuritybySnyk

Low

Low-risk findings worth noting

Fix and improve this skill with Tessl

tessl review fix ./.agents/skills/automations/SKILL.md
SKILL.md
Quality
Evals
Security

Automations

Rule

Automations are the user-facing umbrella for agent-executed tasks that run on demand, in response to events or webhooks, or on a cron schedule. Schedule, Event, Manual, and Webhook are the persisted 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.

The Persisted Trigger Types

TypeFires whenTrigger-specific fields
scheduleCron expression matches (same as recurring jobs)schedule (required), timezone
eventA matching event is emitted on the event busevent (required), condition (optional)
manualA user explicitly invokes run-nowNone
webhookA POST reaches its generated webhook endpointNone

Event triggers can optionally include a condition -- a natural-language string evaluated by a model against the event payload before dispatch. If the condition does not match, the automation is skipped. Manual automations have no schedule, timezone, event, or condition; they never fire from the scheduler or event bus and run only through manage-automations action=run-now. Webhook automations run only when their generated endpoint is called. The endpoint path contains a secret token; share it only with the intended webhook provider.

The UI's Email received choice is not a persisted trigger type. It is a specialized event editor that persists triggerType: event with event: mail.message.received and represents email filters as the event condition.

How It Works

  1. User asks the agent to create an automation (or uses the app's Automations page in Settings, /settings/automations; open it with open-settings-page page automations).
  2. For an event trigger, the agent calls manage-automations with action=list-events to discover the exact registered event name and payload.
  3. Agent calls manage-automations with action=define to write a jobs/<name>.md resource.
  4. The scheduler acquires due schedule triggers, the event dispatcher acquires matching event triggers, the webhook route accepts authenticated endpoint calls, and manual triggers wait for action=run-now.
  5. Event acquisition enforces owner and organization scope and evaluates any condition against the event payload.
  6. Schedule, event, webhook, and explicit run-now acquisition converge on the shared background-automation runner, which validates identity, resolves the configured model and MCP allowlist, runs the agent loop, handles continuation and delivery, and records usage.
  7. Status (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, the event dispatcher matches event names, owners, and conditions, the webhook route authenticates its endpoint token, and run-now is the only acquisition path for manual automations. Everything after a trigger is accepted uses the same execution lifecycle.

Markdown Format

---
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}.

Frontmatter Fields

FieldTypePurpose
schedulestringCron expression; new scheduled automations default to once per hour (0 * * * *) when omitted
enabledbooleanWhether the automation is active
triggerType"schedule" | "event" | "manual" | "webhook"How the automation fires
eventstring?Event name to subscribe to (event triggers only)
conditionstring?Natural-language event condition (event triggers only)
mode"agentic"Full agent loop (only supported mode; "deterministic" was removed — never implemented, rejected at define time)
modelstring?Override the model for this trigger's agent loop
reasoningEffortstring?Override reasoning effort for this trigger's model; omitted uses the model's default
domainstring?Grouping tag (mail, calendar, clips, etc.)
createdBystring?Creator email; required for organization event automations
orgIdstring?Organization scope
runAs"creator" | "shared"Execution identity; trigger-aware automations use creator
mcpToolsstring[]?Exact MCP tool allowlist for this automation
lastRunstring?ISO timestamp of last execution
lastStatusstring?success, error, running, skipped, or paused
lastErrorstring?The last failure cause or declared skip reason
lastErrorCodestring?Typed code of the last failure (see Failure handling)
consecutiveFailuresnumber?Run of identical lastErrorCode failures
pausedReasonstring?Set with enabled: false when the framework paused it
pausedAtstring?ISO timestamp of that pause

Agent Tools

All automation operations are accessed through a single manage-automations tool with an action parameter:

ActionPurpose
list-eventsDiscover registered event names, descriptions, and payload schemas before defining or changing an event trigger
listList automations and their trigger, status, model, tools, and delivery metadata
defineCreate an automation after confirming the summary with the user; requires name, trigger_type, and body
updateUpdate an existing automation in its original scope without changing its creator
deleteDelete an automation (always confirm with user first)
fire-testEmit a test.event.fired event to validate event automations
run-nowExplicitly run one automation immediately with real actions and side effects

Use list-events only when selecting an event trigger; schedule, manual, and webhook triggers do not need event discovery. For Email received, use the registered mail.message.received event rather than inventing an email trigger type.

For define and update, send trigger_type: schedule with a cron schedule, trigger_type: event with an exact registered event and optional condition, or trigger_type: manual/webhook with none of those trigger-specific fields. Webhook definitions return a secret endpoint path; share it only with the intended provider. Pass the same scope when updating an existing automation. manage-automations supports model, reasoning_effort, and mcpTools on define/update; the MCP allowlist is enforced, not advisory.

Use run-now only for an explicit user-authorized execution. It can run any persisted trigger type immediately, is the sole execution path for manual automations, returns a durable run id, and does not change the next scheduled run.

Additional tool: web-request — outbound HTTP with ${keys.NAME} substitution.

During an automation run, automation-no-op declares that no action or report was needed. Pass a short reason (1–500 characters), such as "No urgent mail found." The runner records skipped in the list and Past runs without counting a failure or sending a report. This declaration cannot cover a failed tool, rejected send, or missing credentials. Closing prose alone does not declare a no-op: a run needs confirmed work, delivered output, or this structured signal.

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

Organization event automations are visible to organization members but always run as their creator:

  • createdBy is required and runAs must be creator.
  • An organization event automation matches only events emitted for that creator. Organization visibility does not turn an event into a broadcast.
  • Organization admins may update or delete an automation, but cannot replace its creator or retarget its execution identity.
  • Before every run, the framework verifies that the creator still exists and is still a member of the organization. A removed creator or unreadable membership state prevents execution.

The Event Bus

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" });

Built-in Events

EventSource
test.event.firedManual / manage-automations action=fire-test
agent.turn.completedAgent chat
calendar.*Calendar integration
clip.*Clips integration
mail.*Mail integration

Event Bus API

FunctionPurpose
registerEventDeclare an event type with schema
emitFire an event (validates payload)
subscribeListen for an event (returns subscription ID)
unsubscribeRemove a subscription by ID
listEventsList all registered event definitions

Condition Evaluator

When an automation has a condition, the dispatcher resolves the owner's agent engine through the same resolveEngine path chat and the automation's own run use (owner key, deployment fallback, or Builder Gateway) and asks it a yes/no classification question. This is a yes/no classification, not a generation task; it is never hardcoded to a single provider, so an owner using Builder Gateway, OpenRouter, or any other configured provider can use conditions, not only Anthropic.

  • Empty or missing condition = unconditional (always fires).
  • Results are memoized by condition, payload, execution identity, resolved engine, and model, with a 5-minute TTL and 500-entry LRU cache. The background runner's resolved engine and model are reused for the classifier, including configured engine, deployment-key, and automation model settings.
  • Payload is truncated to 4000 characters before sending to the model.
  • On a classifier failure (no usable credential, provider error, malformed response), evaluation throws; the trigger dispatcher records it as an error (or a missing_credentials precondition) rather than silently skipping.

The web-request Tool and Keys

Automations 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.

  • Keys are ad-hoc secrets created by the user on Settings › API keys or through the /_agent-native/secrets/adhoc API.
  • Each key can have a URL allowlist that restricts which origins the key can be sent to.
  • resolveKeyReferences() resolves placeholders, falling back from user scope to workspace scope.
  • validateUrlAllowlist() checks the resolved URL against per-key allowlists (origin-level matching).
  • Automation definitions, examples, event payloads, and prompts must not hardcode real API keys, webhook URLs, tokens, private Builder/internal data, or customer data. Use ${keys.NAME} and synthetic example.com identities.

Failure Handling

An automation fails once, with its real cause, instead of re-failing every tick. The runner classifies each failure (jobs/automation-outcome.ts):

ClassCodesPauses after
Preconditionmissing_credentials, missing_tools, owner_missing, owner_reserved, config_invalid, connection_required3 identical (owner/identity failures: immediately)
Runtimethe run's own code, e.g. http_5025, with a widening gap between attempts
  • An event or webhook automation counts a failure once per event (lastFailedEventId): queue retries of the same event never pause it alone.
  • Preconditions are checked before a thread or 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.
  • connection_required is the exception to that: a run only learns a provider is not connected by running and yielding to the connection request, so every failed attempt leaves a thread and an agent_runs row. It skips the runtime backoff, pauses after 3 identical failures, and stays paused until the owner enables the automation again; connecting the provider alone does not resume it.
  • A paused automation has 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.
  • Transient failures (spent credits, an unreadable credential store, a remote host, 429/5xx) pause like runtime ones, but the scheduler lifts the pause for one probe run on the same backoff (at most 6h). A failing probe pauses again at once without a new email; a successful one clears the streak.
  • A paused job's "Run now" is still settled (stale running, remote reconcile); it never moves the streak, and resumes the job only on success.
  • Pauses and automatic resumes are measurable: 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.
  • Jobs owned by users that no longer exist, or by reserved test identities (.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".
  • The scheduler tick also closes 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.

UI

The full-page Agent surface's Automations tab is the primary management surface for schedule, event, manual, and webhook automations. Users can create, edit, run now, view status, enable/disable, inspect, and delete automations there. Its URL remains /agent#jobs for compatibility even though the visible tab is Automations.

Example

User: "When someone books a meeting with a @example.com email, message me in Slack."

Agent flow:

  1. Calls manage-automations with action=list-events to find calendar.booking.created.
  2. Confirms the plan with the user.
  3. Calls manage-automations with action=define:
    • name: slack-on-example-booking
    • trigger_type: event
    • event: calendar.booking.created
    • condition: attendee email ends with @example.com
    • mode: agentic
    • domain: calendar
    • body: Send a Slack message to #sales with the booking details. Use the web-request tool to POST to ${keys.SLACK_WEBHOOK}.

Key Files

FilePurpose
packages/core/src/triggers/types.tsTriggerFrontmatter interface
packages/core/src/triggers/actions.tsAgent tools (define, list, update, delete, test)
packages/core/src/triggers/dispatcher.tsEvent subscription and agentic dispatch
packages/core/src/jobs/background-automation-runner.tsShared schedule/event/manual/webhook execution lifecycle
packages/core/src/jobs/automation-outcome.tsFailure classification, pause thresholds, reserved identities
packages/core/src/jobs/stale-reaper.tsBounded reaping of stuck runs and A2A tasks
packages/core/src/triggers/condition-evaluator.tsEngine-resolved condition classification with caching
packages/core/src/event-bus/Event bus (register, emit, subscribe)
packages/core/src/tools/fetch-tool.tsweb-request tool with key substitution
packages/core/src/secrets/substitution.tsresolveKeyReferences() and validateUrlAllowlist()

Related Skills

  • recurring-jobs -- schedule-triggered automations reuse the same scheduler
  • secrets -- ad-hoc keys and ${keys.NAME} substitution
  • actions -- automations can call any registered action via the agent loop
  • delegate-to-agent -- agentic mode runs a full runAgentLoop
Repository

BuilderIO/agent-native

Path

.agents/skills/automations/SKILL.md

Last updated
First committed

Is this your skill?

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.