Automations
Automations
An automation is background work in src/automation/+<name>.automation.ts: scheduled, event-driven, webhook-triggered, or started by hand. Besides an integration’s durable bodies (a feed’s read, an outbound rule’s run), it is the only place for external I/O — HTTP, AI, mail and chat sends, file rendering and geocoding. The literal declares the triggers and the authority; the body attaches with a.run(…), exactly once.
// src/automation/+permit_expiry_watch.automation.ts (templates/construction)
import { automation } from '@norbital-ai/bolt';
import { DAILY_0600, digestOutput, jsonRows } from '../lib/digest.ts';
const a = automation({
description:
'Sweeps the active and expiring permits to work every morning at 6am and publishes the first 25 as a JSON extract …',
on: DAILY_0600,
runAs: ['construction_read'],
output: digestOutput
});
export default a;
a.run(function* (_, ctx) {
const { rows } = yield* ctx.read('permits_to_work', {
where: { status: { in: ['active', 'expiring_soon'] } },
orderBy: { requested_date: 'asc' },
limit: 25
});
return { generated_at: ctx.now, count: rows.length, rows: jsonRows(rows) };
});
// src/lib/digest.ts (excerpt)
export const digestOutput = {
kind: 'object',
fields: {
generated_at: { kind: 'instant' },
count: { kind: 'int' },
rows: { kind: 'list', of: { kind: 'json' } }
}
} as const;
export const DAILY_0600 = { cron: '0 6 * * *' } as const; Triggers
{ cron, tz? }— five fields or a macro, in the workspace zone unlesstzsays otherwise.{ created | updated | deleted: '<collection>', where?, delay? }—updatedmay also namefields.whereis evaluated once on the firing write; the run receives theidsof the rows that fired (adeletedrun also gets their pre-images) and re-checks their state itself.{ webhook: '/<path>', verify: { scheme, secret } }— with the schemesbearer,hmac-sha256,svix,stripe,slackandmeta.- No
on— started only by hand:bolt.startfrom a page, a toolbarstartitem,ctx.schedulefrom an action or another run, or the agent.
// src/automation/+review_job_assignment_suspicion.automation.ts (templates/field-operations, excerpt)
const review = automation({
description: 'When an assignment is filed or changes: inspects photos awaiting facts, then reviews …',
on: [
{ created: 'job_assignments' },
{
updated: 'job_assignments',
fields: ['suspicion_checked_at'],
where: { suspicion_checked_at: { isNull: true } }
}
],
input: {
/** Event runs: the assignments that fired. */
ids: { kind: 'list', of: { kind: 'id', of: 'job_assignments' }, optional: true },
/** Manual runs may target one unchecked assignment. */
assignment_id: { kind: 'id', of: 'job_assignments', optional: true }
},
output: { kind: 'object', fields: { assignment_count: { kind: 'int' } /* … */ } },
runAs: ['suspicion_review_automation'],
concurrency: { max: 1 }
});
export default review; Authority
runAs names the policies the run acts under, and its reads and writes are judged against their grants like anyone else’s. runAs: 'trigger' runs as whoever started it and is allowed only on an automation with no on. A member may start an automation when one of their policies lists it under automations.
The run
A run is one invocation. Its ctx reads, acts, schedules and notifies as its runAs, and adds send, request, web, files, ai and geo, plus progress, attempt and cause. The declared output is the run’s readable result, shown by RunStatus and on the runs page. Work that does not fit one invocation fans out with ctx.schedule per chunk, or schedules its own continuation.
Scheduling
ctx.schedule(name, input) queues a run as soon as possible; ctx.schedule(name, input, { at, key? }) queues it later, and a queued run with the same key is replaced. retry: { attempts, backoff? } retries only transient failures, and concurrency: { max } bounds parallel runs. Nothing polls: every future run is a queued row with a due time.
Automation, action or transform?
- Transform — derivations and refusals inside the write itself.
- Action — a named, typed write a caller waits for, with no I/O.
- Automation — anything with I/O or AI, anything on a clock or after a commit, and bulk work that reports progress.