Automations
An automation is a rule: when X happens, do Y — described in natural language. The agent executes the instructions, so automations have access to every action, tool, and MCP server the agent can use in an interactive chat.
Automations extend recurring jobs with event triggers, natural-language conditions, and outbound HTTP via the web-request tool. They live as markdown resources under jobs/ — the same storage and format as recurring jobs, with extra frontmatter fields.
Two trigger types
| Type | Fires when | Key field |
|---|---|---|
schedule |
A cron expression matches (same as recurring jobs) | schedule (cron) |
event |
A matching event is emitted on the framework event bus | event (name) |
Event triggers can include a condition — a natural-language string evaluated by Haiku against the event payload before dispatch. If the condition doesn't match, the automation is silently skipped.
Creating automations
By asking the agent
"When someone books a meeting with a @builder.io email, message me in Slack."
The agent discovers available events, confirms the plan, and writes the automation for you.
From the settings UI
Automations appear in the settings panel. Users can view, enable/disable, and delete them there.
Via the API
Write a jobs/<name>.md resource directly:
import { resourcePut } from "@agent-native/core/resources";
await resourcePut(
ownerEmail,
"jobs/slack-on-builder-booking.md",
`---
schedule: ""
enabled: true
triggerType: event
event: calendar.booking.created
condition: "attendee email ends with @builder.io"
mode: agentic
domain: calendar
createdBy: [email protected]
runAs: creator
---
Send a Slack message to #sales with the booking details.
Use the web-request tool to POST to \${keys.SLACK_WEBHOOK}.`,
);
The markdown format
Each automation is a markdown file with YAML frontmatter and a body containing natural-language instructions.
Frontmatter fields
| Field | Type | Default | Description |
|---|---|---|---|
schedule |
cron expression | "" |
Cron expression (required for schedule triggers) |
enabled |
boolean | true |
Whether the automation is active |
triggerType |
"schedule" | "event" |
"schedule" |
How the automation fires |
event |
string | (optional) | Event name to subscribe to (event triggers only) |
condition |
string | (optional) | Natural-language condition evaluated before dispatch |
mode |
"agentic" | "deterministic" |
"agentic" |
Full agent loop vs. fixed action set |
domain |
string | (optional) | Grouping tag (mail, calendar, clips, etc.) |
createdBy |
(auto) | Owner email | |
orgId |
string | (auto) | Org scope; inherited from the creator's active org |
runAs |
"creator" | "shared" |
"creator" |
Whose API key and permissions to use |
lastRun |
ISO timestamp | (managed) | Written by the dispatcher after each run |
lastStatus |
"success" | "error" | "running" | "skipped" |
(managed) | Latest outcome |
lastError |
string | (managed) | Error message if the last run failed |
The event bus
Integrations register events at module load time. The bus validates payloads against Standard Schema definitions and dispatches to subscribers.
Built-in events
| 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 |
Call manage-automations with action=list-events from the agent to see all registered events with descriptions and payload schemas for the current template.
Emitting custom events
Register an event type in a server plugin, then emit it from actions or webhook handlers:
import { registerEvent, emit } from "@agent-native/core/event-bus";
import { z } from "zod";
// Register the event type (once, at module load)
registerEvent({
name: "order.completed",
description: "A customer completed an order",
payloadSchema: z.object({
orderId: z.string(),
customerEmail: z.string(),
total: z.number(),
}),
example: {
orderId: "ord_123",
customerEmail: "[email protected]",
total: 49.99,
},
});
// Emit the event (from an action, webhook handler, etc.)
emit(
"order.completed",
{
orderId: "ord_123",
customerEmail: "[email protected]",
total: 49.99,
},
{ owner: "[email protected]" },
);
The owner in the emit metadata scopes which automations fire — only automations owned by the same user (or shared automations) are evaluated.
Conditions
Conditions are natural-language strings evaluated by Claude Haiku against the event payload. This is a yes/no classification, not a generation task.
- Empty or missing condition = unconditional (always fires).
- Results are memoized (SHA-256 of condition + payload) with a 5-minute TTL and 500-entry LRU cache.
- Payload is truncated to 4000 characters before sending to Haiku.
- On API failure, the condition evaluates to
false(safe default — the automation is skipped).
Examples of conditions:
"attendee email ends with @builder.io""the order total is greater than $100""the message contains the word 'urgent'"
The web-request tool
Automations use the web-request tool for outbound HTTP. It supports ${keys.NAME} placeholders in the URL, headers, and body:
POST to ${keys.SLACK_WEBHOOK}
Headers: {"Authorization": "Bearer ${keys.API_TOKEN}"}
Body: {"text": "New booking from ${attendeeEmail}"}
Placeholders are resolved server-side after the agent emits the tool call — the raw secret value never enters the agent's context.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
url |
string | — | Full URL. May contain ${keys.NAME} references. |
method |
string | GET |
HTTP method (GET, POST, PUT, PATCH, DELETE, HEAD). |
headers |
string | {} |
JSON object of headers. May contain ${keys.NAME}. |
body |
string | — | Request body. May contain ${keys.NAME}. |
timeout_ms |
number | 15000 | Timeout in milliseconds (max 30000). |
Keys
Keys are ad-hoc secrets created by users or the agent for automation use (e.g. SLACK_WEBHOOK, HUBSPOT_API_KEY). They differ from registered secrets (registerRequiredSecret) in that they have no template-defined metadata or onboarding step.
- Created via the settings UI or the
/_agent-native/secrets/adhocAPI. - Each key can have a URL allowlist that restricts which origins the key can be sent to (origin-level matching).
- The raw value is never exposed to the AI — only
${keys.NAME}placeholders appear in the agent's context. - Resolution falls back from user scope to workspace scope, so users can override shared keys.
Agent tools
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 confirms with user first) |
fire-test |
Emit a test.event.fired event to validate automations |
Additional tool: web-request — outbound HTTP with ${keys.NAME} substitution.
API endpoints
| Endpoint | Method | Description |
|---|---|---|
/_agent-native/automations |
GET | List all automations (parsed) |
/_agent-native/automations/fire-test |
POST | Emit a test.event.fired event |
/_agent-native/secrets/adhoc |
GET | List ad-hoc keys (no values) |
/_agent-native/secrets/adhoc |
POST | Create or update an ad-hoc key |
/_agent-native/secrets/adhoc/:name |
DELETE | Delete an ad-hoc key |
How dispatch works
- The trigger dispatcher subscribes to events at server startup.
- When an event fires, the dispatcher loads all enabled event-triggered automations matching that event name.
- Ownership scoping: only automations owned by the event's owner (or shared automations) are evaluated.
- For each matching automation, the condition (if any) is evaluated by Haiku.
- If the condition passes, the dispatcher runs a full
runAgentLoopwith the automation body as the prompt and the event payload as context. - The agent has access to all tools — actions,
web-request, MCP servers, sub-agents. - On completion,
lastRun,lastStatus, andlastErrorare written back to the resource frontmatter. - A 5-minute timeout prevents runaway automations.
Example
User: "When someone books with a @builder.io email, message me in Slack."
Agent flow:
- Calls
manage-automationswithaction=list-events— findscalendar.booking.created. - Confirms the plan with the user.
- Calls
manage-automationswithaction=define:name:slack-on-builder-bookingtrigger_type:eventevent:calendar.booking.createdcondition:attendee email ends with @builder.iomode:agenticdomain:calendarbody:Send a Slack message to #sales with the booking details. Use the web-request tool to POST to ${keys.SLACK_WEBHOOK}.
- The automation is saved as
jobs/slack-on-builder-booking.mdand begins listening immediately.
What's next
- Recurring Jobs — schedule-triggered automations reuse the same scheduler
- Actions — automations can call any registered action via the agent loop
- Security — input validation and secret handling