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.nextRun)  // '2024-01-16T14:00:00Z' (UTC)
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).

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', {
  limit: 20,
})
// Returns array of { runId, startedAt, completedAt, status, cost }
// 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' | 'deleted'
//   lastRun:   '2024-01-15T14:00:00Z',
//   nextRun:   '2024-01-16T14:00:00Z',
//   runCount:  42,
// }

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

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

Calling a trigger

POST to the trigger URL with a JSON body. The body is passed as inputto the agent. Include an X-Theazo-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-Theazo-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',
//   agent:     'onboarding-agent',
//   userId:    'user_123',
//   url:       'https://api.theazo.com/v1/triggers/trg_xyz789',
//   secret:    null,           // only returned on create
//   createdAt: '2024-01-10T...',
//   lastFired: '2024-01-15T...',
//   fireCount: 127,
// }
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.

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] = { status: 'skipped', reason: 'previous_run_active', startedAt: '...' }
// history[1] = { status: 'completed', cost: { amount: 120, currency: 'usd' }, ... }

// Trigger endpoints do NOT enforce overlap by default.
// Add concurrencyLimit to restrict simultaneous runs from one trigger:
const trigger = await theazo.triggers.create({
  name:             'stripe-webhook',
  agent:            'payment-handler',
  userId:           'system',
  concurrencyLimit: 1,   // at most 1 active run from this trigger at a time
})

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 = [
//   { runId: 'run_2', status: 'running',   startedAt: '...T10:10:00Z' },
//   { runId: null,    status: 'skipped',   startedAt: '...T10:05:00Z', reason: 'previous_run_active' },
//   { runId: 'run_1', status: 'completed', startedAt: '...T10:00: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.get(scheduleId)Promise<Schedule>Fetch a schedule by ID.
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, { limit?, cursor? })Promise<RunHistory[]>Paginated 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. Secret is never returned on list.
theazo.triggers.get(triggerId)Promise<Trigger>Fetch a trigger by ID.
theazo.triggers.delete(triggerId)Promise<void>Delete a trigger. URL stops accepting requests immediately.
Was this page helpful?
Ask anything...⌘I