Your First Automation
Automations are agents that run on a schedule (cron) or in response to external events (webhooks). Unlike tools, automations are not called by the AI - they run in the background.
What Makes an Automation Different?
| Aspect | Tool | Automation |
|---|---|---|
| Triggered by | AI decides to call it | Cron schedule, webhook, or lifecycle hook |
| Manifest field | tool: {...} | automation: {...} |
| AI access | AI can use it | Can call AI via context.ai |
| Extra context | Standard context | context.ai, context.notify, context.input |
| Output | Returns result to AI | Posts to PIE Assistant session |
Example: Daily Weather Alert
Let's build a simple automation that sends a daily weather notification:
async function handler(input, context) {
// Get weather from an API
const response = await context.fetch(
`https://api.weather.com/v1/current?city=NYC&apiKey=${context.secrets.WEATHER_API_KEY}`
);
if (!response.ok) {
throw new Error('Failed to fetch weather');
}
const weather = JSON.parse(response.body);
// Post notification to PIE Assistant
await context.notify(
`☀️ **Today's Weather**\n\n` +
`Temperature: ${weather.temp}°F\n` +
`Conditions: ${weather.conditions}\n` +
`Humidity: ${weather.humidity}%`,
{
title: 'Daily Weather',
}
);
return { success: true, temp: weather.temp };
}
module.exports = { handler };The Automation Manifest
{
"name": "daily-weather",
"displayName": "Daily Weather",
"tier": "automation",
"automation": {
"triggers": [
{
"type": "cron",
"default": "0 7 * * *",
"description": "Daily at 7am"
}
],
"timeout": 30
},
"developerSecrets": {
"WEATHER_API_KEY": {
"description": "API key for weather service",
"required": true
}
}
}Trigger Types
Cron Triggers
Schedule automations using cron expressions:
{
"automation": {
"triggers": [
{
"type": "cron",
"default": "*/15 * * * *",
"description": "Every 15 minutes"
}
]
}
}Common cron expressions:
0 7 * * *- Daily at 7am*/15 * * * *- Every 15 minutes0 9 * * 1-5- Weekdays at 9am0 0 1 * *- First day of each month
Note: Cron times use the user's configured timezone.
Webhook Triggers
Receive external events via HTTP:
{
"automation": {
"triggers": [
{
"type": "webhook"
}
]
}
}When installed, PIE generates a webhook URL for the agent. External services POST to this URL to trigger the automation.
Agent webhook URL: /api/webhooks/plugin/{pluginId}
This URL is the same for all users of your agent. Your onWebhook handler receives the payload and can determine which user it's for based on the payload content (e.g., email address from Gmail Pub/Sub).
Declaring Webhook Events
You can declare the specific event types your webhook produces. This allows users to create heartbeats that are triggered by specific events from your agent:
{
"automation": {
"triggers": [
{
"type": "webhook",
"eventTypeField": "type",
"events": [
{ "name": "invoice.paid", "description": "Invoice successfully paid" },
{ "name": "charge.failed", "description": "A charge attempt failed" }
]
}
],
"onWebhook": true
}
}eventTypeField— A dot-path to the field in the webhook payload that identifies the event type (e.g.,"type"for Stripe,"_headers.x-github-event"for GitHub).events— The list of event types your webhook can produce. These populate the dropdown when users create event-triggered heartbeats.
For automation plugins, these declared webhook events are the built-in way to expose webhook-triggered Heartbeats. If your plugin also has tool actions, widget actions, or public actions that should trigger Heartbeats, add those separately with manifest.heartbeatEvents.
See the Manifest Reference for full details.
Lifecycle Hooks
Agents can export lifecycle handlers that run at specific moments:
onInstall - After Agent Installation
Run code immediately when a user installs the agent:
async function onInstall(input, context) {
// Initialize default settings, provision resources, etc.
await context.notify('Thanks for installing! Getting things set up...', {
title: 'Installation Started'
});
return { success: true };
}
module.exports = { handler, onInstall };onUninstall - Before Agent Removal
Run cleanup code when a user uninstalls the agent:
async function onUninstall(input, context) {
// Tear down resources, revoke tokens, remove external subscriptions
await context.fetch('https://api.example.com/cleanup', {
method: 'POST',
body: JSON.stringify({ userId: context.userId }),
});
return { success: true };
}
module.exports = { handler, onInstall, onUninstall };onConnect - After OAuth Connection
Run code immediately when a user connects their OAuth account:
async function onConnect(input, context) {
// Set up external subscriptions, welcome the user, etc.
await context.notify('Welcome! Your account is now connected.', {
title: 'Setup Complete'
});
// Example: Set up Gmail push notifications
const watchResp = await context.oauth.fetch('https://gmail.googleapis.com/gmail/v1/users/me/watch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
topicName: context.secrets.PUBSUB_TOPIC,
labelIds: ['INBOX'],
}),
});
return { success: true };
}
module.exports = { handler, onInstall, onConnect };onWebhook - Handle Incoming Webhooks
Process webhook payloads from external services:
async function onWebhook(input, context) {
const payload = input.triggerData;
// Decode Pub/Sub message if needed
if (payload.message?.data) {
const decoded = atob(payload.message.data);
const notification = JSON.parse(decoded);
// Process notification...
}
return { success: true };
}
module.exports = { handler, onInstall, onConnect, onWebhook };onDisconnect - Cleanup (Optional)
Run code when a user disconnects OAuth:
async function onDisconnect(input, context) {
// Clean up external subscriptions
await context.oauth.fetch('https://api.example.com/unsubscribe', {
method: 'POST'
});
return { success: true };
}
module.exports = { handler, onInstall, onConnect, onDisconnect };Automation Context
Automations get additional context APIs not available to tools:
context.ai
Call the AI from within your automation:
// Analyze data with AI
const analysis = await context.ai.analyze({
prompt: 'Classify this email as urgent or not urgent',
data: { subject, from, snippet }
});
// Returns: { label: 'urgent', confidence: 0.95 }
// Summarize content
const summary = await context.ai.summarize(longText);
// Returns: "Brief summary of the content..."context.notify()
Post messages to the user's PIE Assistant session:
await context.notify('Your notification message', {
title: 'Optional Title',
urgent: true, // Highlights the notification
});context.input
Access automation-specific information:
const {
lastRunAt, // Timestamp of last successful run (ms)
triggeredBy, // 'cron', 'webhook', or 'manual'
triggerData, // Webhook payload (if triggered by webhook)
} = input;
// Process only new data since last run
const sinceTime = lastRunAt || (Date.now() - 24 * 60 * 60 * 1000);Combining Tool + Automation
An agent can be both a tool AND an automation by including both fields:
{
"name": "weather",
"tier": "tool",
"tool": {
"name": "get_weather",
"description": "Get current weather for a location"
},
"automation": {
"triggers": [
{ "type": "cron", "default": "0 7 * * *" }
]
}
}This allows:
- AI to call
get_weatherwhen the user asks - Daily automated weather notifications
OAuth in Automations
Automations can use OAuth just like connectors:
{
"automation": {
"triggers": [
{ "type": "cron", "default": "0 7 * * *" }
]
},
"oauth": {
"provider": "google",
"scopes": ["https://www.googleapis.com/auth/gmail.readonly"],
"clientIdSecret": "GOOGLE_CLIENT_ID",
"clientSecretSecret": "GOOGLE_CLIENT_SECRET"
}
}Use context.oauth.fetch() for authenticated API calls:
const response = await context.oauth.fetch(
'https://gmail.googleapis.com/gmail/v1/users/me/messages'
);Error Handling
If an automation fails 3 times consecutively:
- It's automatically disabled
- User receives a notification in PIE Assistant
- User can re-enable it in the Automations page
Handle errors gracefully:
async function handler(input, context) {
try {
// Your automation logic
} catch (error) {
// Notify user of the issue
await context.notify(
`⚠️ Automation error: ${error.message}\n\nPlease check your configuration.`,
{ urgent: true }
);
throw error; // Re-throw to mark run as failed
}
}Heartbeats
Heartbeats are built-in automations that send scheduled messages to the AI assistant — no code required. While agent automations let you run custom code on a schedule, heartbeats provide a simpler alternative: they deliver a preconfigured message to the PIE Assistant session at regular intervals, letting the AI act on it.
Heartbeats vs. Agent Automations
Heartbeats and agent automations share the same scheduling infrastructure. The key difference is what happens when the schedule fires:
| Heartbeat | Agent Automation | |
|---|---|---|
| Requires code | No | Yes |
| What runs | Sends a message to the AI assistant | Executes your handler function |
| Use case | Periodic AI prompts ("Check my inbox", "Summarize today's tasks") | Custom logic with API calls, data processing, notifications |
| Setup | API or UI only | Agent manifest + code |
Trigger Types
Heartbeats support three triggering modes:
- Interval — Run every X minutes, hours, or days. For example, every 30 minutes or every 2 hours.
- Cron expression — Standard cron syntax for precise scheduling (e.g.,
0 9 * * 1-5for weekdays at 9am). - Plugin Event — Trigger when an installed agent receives a specific webhook event. For example, trigger a heartbeat when GitHub receives a
pull_requestevent or when Stripe receives aninvoice.payment_failedevent. The webhook payload is automatically included in the AI prompt as context.
When using Plugin Event triggers, the UI shows a two-step picker: first select the agent (e.g., GitHub), then select the specific event type from the events that agent declares. You can also select "Any event" to trigger on all webhook events from that agent.
Plugin Event heartbeats have a 5-minute cooldown between triggers to prevent excessive AI calls from chatty webhook sources.
Active Hours
You can optionally restrict a heartbeat to a time window using HH:MM format:
activeFrom: "09:00"/activeTo: "17:00"— Only fires during business hours.- Overnight ranges are supported:
activeFrom: "22:00"/activeTo: "06:00"fires from 10pm through 6am.
When outside the active window, scheduled executions are silently skipped.
Delivery
Heartbeat messages are delivered to the PIE Assistant session by default. The AI receives the message as if a user had typed it, and can respond, trigger tools, or take any action it normally would.
Error Handling
Like agent automations, heartbeats auto-disable after 3 consecutive failures. The user is notified in PIE Assistant and can re-enable the heartbeat from the Automations page.
API Endpoints
Manage heartbeats programmatically through the tasks API:
| Method | Endpoint | Description |
|---|---|---|
GET | /api/tasks | List all heartbeats |
POST | /api/tasks | Create a new heartbeat |
PATCH | /api/tasks/:id | Update a heartbeat |
DELETE | /api/tasks/:id | Delete a heartbeat |
POST | /api/tasks/:id/run | Manually trigger a heartbeat |
GET | /api/tasks/:id/runs | View run history for a heartbeat |
Next Steps
- Handling Secrets - Secure credential storage
- OAuth Basics - OAuth authentication
- Context API Reference - Full API documentation