Get Started

Scheduling

Two ways to trigger agents on a schedule: cron schedules for time-based runs, and webhook triggers for event-driven runs from external systems.

Cron schedules

Create a named schedule that runs an agent on a cron expression. Theazo handles overlap prevention — if a previous run is still active when the next trigger fires, the new run is skipped.

schedule.ts
import { Theazo } from 'theazo'

const theazo = new Theazo({ apiKey: 'th_live_...' })

const schedule = await theazo.schedules.create({
  name: 'daily-digest',
  agent: 'digest-writer',
  userId: 'user_123',
  cron: '0 9 * * 1-5',       // 9am Monday–Friday
  timezone: 'America/New_York',
  input: {
    sources: ['hackernews', 'producthunt', 'arxiv'],
    topics:  ['AI', 'infrastructure', 'developer tools'],
    format:  'markdown',
  },
})

console.log(schedule.id)       // 'sch_abc123'
console.log(schedule.enabled)  // true
Cron expressions follow standard 5-field syntax: minute hour day month weekday. All times are stored in UTC and converted using the timezone field. Supported timezones are IANA names (e.g. Europe/London, Asia/Tokyo).

Schedule options

The options accepted by schedules.create(). This table is generated from the @theazo/contracts schema — it can't drift from the SDK.

FieldTypeRequiredDescription
agentstringAgent definition name or ID the schedule runs.
userIdstringyesUser the scheduled runs are attributed to.
namestring
cronstringyesCron expression (5 fields: minute hour day-of-month month day-of-week).
timezonestringIANA timezone for cron evaluation. Default: UTC.
inputRecord<string, unknown>Input passed to each scheduled agent run.

Common cron expressions

'0 * * * *'       // every hour at :00
'*/15 * * * *'    // every 15 minutes
'0 9 * * 1-5'     // 9am weekdays
'0 0 * * 0'       // midnight every Sunday
'0 8 1 * *'       // 8am on the 1st of each month
'30 17 * * 5'     // 5:30pm every Friday

Managing schedules

manage-schedules.ts
// List all schedules
const schedules = await theazo.schedules.list()

// Pause — stops future runs, preserves history
await theazo.schedules.pause('sch_abc123')

// Resume
await theazo.schedules.resume('sch_abc123')

// Delete — removes schedule entirely
await theazo.schedules.delete('sch_abc123')

// Run history for a schedule
const history = await theazo.schedules.history('sch_abc123')
// Returns array of { id, agentId, status, cost, startedAt, completedAt }
// Schedule object shape:
// {
//   id:        'sch_abc123',
//   name:      'daily-digest',
//   agent:     'digest-writer',
//   userId:    'user_123',
//   cron:      '0 9 * * 1-5',
//   timezone:  'America/New_York',
//   status:    'active',        // 'active' | 'paused'
//   createdAt: '2024-01-15T14:00:00Z',
// }

Webhook triggers

Create a trigger to get a unique URL that launches an agent run when called. Use this to trigger agents from external systems — Zapier, GitHub Actions, your own backend, etc.

trigger.ts
const trigger = await theazo.triggers.create({
  name: 'new-signup-onboarding',
  agent: 'onboarding-agent',
  userId: 'user_123',        // the session userId for each run
  type: 'webhook',
})

console.log(trigger.id)     // 'trg_xyz789'
console.log(trigger.url)    // 'https://api.theazo.com/triggers/trg_xyz789/fire'
console.log(trigger.secret) // 'whsec_...' — shown ONCE, store it now

Trigger options

The options accepted by triggers.create(). This table is generated from the @theazo/contracts schema — it can't drift from the SDK.

FieldTypeRequiredDescription
agentstringAgent definition name or ID the trigger runs.
userIdstringyesUser the triggered runs are attributed to.
namestringHuman-readable trigger name.
type'webhook'yesCreates a signed webhook URL that fires the agent when POSTed to.

Calling a trigger

POST to the trigger URL with a JSON body. The body is passed as inputto the agent. Include an x-trigger-signature header for request verification.

// Your external system calls the trigger
const body = JSON.stringify({
  userId:   'new_user_456',
  email:    'alice@acme.com',
  plan:     'pro',
  signedUp: new Date().toISOString(),
})

const signature = createHmac('sha256', triggerSecret)
  .update(body)
  .digest('hex')

await fetch(trigger.url, {
  method: 'POST',
  headers: {
    'Content-Type':         'application/json',
    'x-trigger-signature':  signature,
  },
  body,
})

// Theazo launches the agent with input = parsed body
// Agent runs: "Onboard new user alice@acme.com on the pro plan"

Managing triggers

manage-triggers.ts
// List all triggers
const triggers = await theazo.triggers.list()

// Delete a trigger (URL stops accepting requests immediately)
await theazo.triggers.delete('trg_xyz789')
// Trigger object shape:
// {
//   id:      'trg_xyz789',
//   name:    'new-signup-onboarding',
//   url:     'https://api.theazo.com/triggers/trg_xyz789/fire',
//   secret:  'whsec_...',      // shown on create — store it now
//   agent:   'onboarding-agent',
//   userId:  'user_123',
// }
Trigger secrets are shown only once at creation time. Store the secret securely — it cannot be retrieved again. If you lose it, delete the trigger and create a new one.

Event triggers

Instead of a URL, subscribe an agent to a named application event you emit from your own code. When you call events.emit(), every matching event trigger fires — so you can wire "when an email arrives, classify it" without managing webhook URLs and signatures. An optional filter narrows which occurrences fire (all keys must match; string values support * globs).

event-trigger.ts
// 1. Subscribe an agent to an event (optionally filtered)
const trigger = await theazo.triggers.on('email_received', {
  agent:  'email-classifier',
  userId: 'user_123',
  filter: { from: '*@company.com' },   // only company email fires it
})

// 2. Later, emit the event from your app — matching triggers fire
await theazo.events.emit('email_received', {
  userId:  'user_123',                 // scope to this user's triggers
  payload: { from: 'alice@company.com', subject: 'Invoice #42' },
})
// → the classifier agent runs with input = payload

Event trigger options

The options accepted by triggers.on(event, opts). Generated from the@theazo/contracts schema.

FieldTypeRequiredDescription
agentstringAgent definition name or ID the trigger runs.
userIdstringyesUser the triggered runs are attributed to.
namestringHuman-readable trigger name.
type'event'yesFires the agent when a matching application event is emitted.
eventstringyesEvent name to subscribe to (e.g. "email_received"), matched against emitted events.
filterRecord<string, unknown>Optional field match against the event payload — all keys must match (string values support `*` globs); omit to fire on every occurrence.

Emit options

The body of events.emit(event, opts) / POST /v1/events. Omit userId to fan out to every matching trigger on the platform. Firing is asynchronous — the agents run off the request path; results appear in each trigger's history.

FieldTypeRequiredDescription
eventstringyesEvent name to emit (matches event-trigger subscriptions).
userIdstringScope firing to this user's event triggers. Omit to fire every matching trigger on the platform.
payloadRecord<string, unknown>Event data — matched against each trigger filter and passed as the agent input.

Trigger events

trigger.fired

A webhook trigger was called. Includes triggerId, the request body as input, and the agentId that was launched.

trigger.rejected

A webhook trigger call was rejected due to an invalid signature or rate limit.

Overlap prevention

For cron schedules, Theazo checks if the previous run is still active before launching the next one. If it is, the scheduled run is skipped and logged as skipped in history.

overlap.ts
// Schedule history includes skipped runs
const history = await theazo.schedules.history('sch_abc123')

// history[0] = { id: 'exec_2', agentId: null, status: 'skipped', startedAt: '...', completedAt: null }
// history[1] = { id: 'exec_1', agentId: 'agt_...', status: 'completed', cost: { amount: 120, currency: 'usd' }, startedAt: '...', completedAt: '...' }

// Trigger endpoints do NOT enforce overlap — each call launches an
// independent run. Enforce concurrency in your own backend before
// calling the trigger URL if you need it.
const trigger = await theazo.triggers.create({
  name:   'stripe-webhook',
  agent:  'payment-handler',
  userId: 'system',
  type:   'webhook',
})

How scheduling works

Behind the scenes, Theazo runs a cron tick worker, creates ephemeral sessions for each scheduled run, and enforces overlap prevention to guarantee exactly-once execution.

Cron tick worker

The backend runs a tick worker every 60 seconds. On each tick, the worker queries all enabled schedules where nextRunAt <= now, creates an ephemeral session and agent for each due schedule, dispatches the agent to the agent-run BullMQ queue, and advancesnextRunAt to the next cron occurrence.

To prevent double-firing when multiple worker replicas are running, the tick acquires a Redis lock (SET NX EX) before processing. If the lock is already held, the tick is a no-op. This guarantees idempotent execution — each schedule fires at most once per interval regardless of how many workers are active.

// Simplified tick loop (internal — runs every 60s)
// 1. Acquire Redis lock: SET scheduling:tick:lock <workerId> NX EX 55
// 2. Query: SELECT * FROM schedules WHERE status = 'active' AND next_run_at <= NOW()
// 3. For each due schedule:
//    a. Check if previous run is still active (overlap prevention)
//    b. Create ephemeral session + agent
//    c. Enqueue to 'agent-run' BullMQ queue
//    d. UPDATE schedules SET next_run_at = <next occurrence> WHERE id = <scheduleId>
// 4. Release lock on completion

Overlap prevention details

When the tick worker finds a due schedule, it first checks whether the previous run is still in an active state (running or booting). If the previous run has not yet completed, the new run is skipped entirely. Skipped runs are recorded in the schedule's execution history with status skipped and reason previous_run_active.

The default overlap policy is overlap: 'skip'. This means the schedule simply waits for the next cron tick after the active run completes — no runs are queued or retried. The nextRunAt is still advanced even on a skip, so the schedule stays on its regular cadence.

// Example: what happens when a run overlaps
//
// Schedule: '*/5 * * * *' (every 5 minutes)
//
// 10:00 — tick fires, run_1 starts           → status: 'running'
// 10:05 — tick fires, run_1 still running    → status: 'skipped', reason: 'previous_run_active'
// 10:10 — tick fires, run_1 completed at 10:07 → run_2 starts normally
//
// history = [
//   { id: 'exec_2', agentId: 'agt_2', status: 'running',   startedAt: '...T10:10:00Z', completedAt: null },
//   { id: 'exec_s', agentId: null,    status: 'skipped',   startedAt: '...T10:05:00Z', completedAt: null },
//   { id: 'exec_1', agentId: 'agt_1', status: 'completed', startedAt: '...T10:00:00Z', completedAt: '...T10:07:00Z', cost: { amount: 85, currency: 'usd' } },
// ]

Ephemeral sessions

Each scheduled run creates a temporary (ephemeral) session that auto-terminates when the agent completes. These sessions are not reused across runs — every cron tick produces a fresh session and agent pair. This keeps scheduled runs fully isolated from each other and from interactive sessions.

Compute cost, model tokens, and sandbox time for ephemeral sessions are tracked under the schedule owner's platformId. Costs appear in billing alongside interactive usage and are tagged with the schedule ID for filtering.

// Ephemeral session lifecycle (internal)
//
// 1. Tick worker creates session:
//    { userId: schedule.userId, ephemeral: true, scheduleId: 'sch_abc123' }
//
// 2. Agent is created inside the session and dispatched to agent-run queue
//
// 3. Agent completes (or fails) → session auto-terminates
//    - Sandbox is destroyed
//    - Usage event recorded: { platformId, scheduleId, cost, tokens, duration }
//
// 4. Session is marked 'terminated' — never reused

API reference

Schedules

theazo.schedules.create(config)Promise<Schedule>Create a new cron schedule.
theazo.schedules.list()Promise<Schedule[]>List all schedules for this platform.
theazo.schedules.pause(scheduleId)Promise<void>Pause a schedule. Future triggers are skipped until resumed.
theazo.schedules.resume(scheduleId)Promise<void>Resume a paused schedule.
theazo.schedules.delete(scheduleId)Promise<void>Permanently delete a schedule.
theazo.schedules.history(scheduleId)Promise<ScheduleExecution[]>Execution history for a schedule.

Triggers

theazo.triggers.create(config)Promise<Trigger>Create a webhook trigger. Returns secret once — store it immediately.
theazo.triggers.list()Promise<Trigger[]>List all triggers.
theazo.triggers.delete(triggerId)Promise<void>Delete a trigger. URL stops accepting requests immediately.
Was this page helpful?