CronSynth: Shared Time

Externalized Cron for Autonomous Agents


The Serverless Problem

Autonomous agents increasingly run in serverless environments. Vercel functions. Cloudflare Workers. AWS Lambda. These platforms are perfect for event-driven workloads—they scale instantly, cost nothing when idle, and require zero infrastructure management.

But they have one fatal flaw: no background processes.

You can't run setInterval(). You can't spin up a cron daemon. You can't maintain a persistent timer. When the function returns, it's gone.

This breaks a fundamental agent capability: time-awareness.

Agents need to:

  • Check for updates every 5 minutes
  • Run daily reports at 9 AM
  • Execute trades at market open
  • Retry failed operations after delays
  • Coordinate scheduled operations across swarms

Without background processes, none of this works natively.


The Traditional Workarounds

Platform Cron Jobs

Vercel has cron. Cloudflare has Cron Triggers. But they're:

  • Platform-locked: Can't move your schedules
  • Limited resolution: Often 1-minute minimum, sometimes worse
  • Opaque: No on-chain proof of execution
  • Non-composable: Can't integrate with agent reputation systems

External Schedulers

Services like EasyCron or cron-job.org exist, but they're:

  • Human-focused: Dashboards, not APIs
  • Trust-based: No verification of execution
  • Payment friction: Credit cards, not x402

Self-Hosted Infrastructure

You could run your own scheduler, but then you're:

  • Back to managing servers: Defeating serverless benefits
  • Single point of failure: Your cron server goes down, everything stops
  • Duplicating effort: Every agent operator runs the same infrastructure


What CronSynth Does

CronSynth externalizes scheduling so serverless agents get time-awareness without infrastructure.

Your Agent CronSynth Time

Core Capabilities

Cron Scheduling

  • Standard 5-field cron expressions
  • 1-minute minimum resolution
  • UTC timezone (no ambiguity)
  • Automatic next-run calculation

Reliable Delivery

  • HMAC-signed webhooks
  • 5-second timeout (acknowledge fast, process async)
  • Automatic retry with exponential backoff
  • Delivery receipts for confirmation

Verification

  • On-chain attestation (optional)
  • Run history with timestamps
  • Auditable execution records

Agent-Native Payment

  • x402 micropayments
  • Free tier for basic usage
  • Pay-per-trigger billing
  • No accounts, no API keys


Schedule Registration

API Endpoint

POST /api/schedule

Request

json

{

"webhook_url": "https://my-agent.com/cron/daily-report",

"cron": "0 9 * * *",

"timezone": "UTC",

"metadata": {

"name": "Daily Portfolio Report",

"agent_id": "agent_001"

}

}

Response

json

{

"schedule_id": "sch_a1b2c3d4e5f6",

"cron": "0 9 * * *",

"next_run": "2025-01-28T09:00:00Z",

"webhook_secret": "whsec_...",

"status": "active"

}

Code Example

typescript

import { CronSynthClient } from '@echorift/cronsynth';

const client = new CronSynthClient({

signer: agentWallet,

});

// Register a schedule

const schedule = await client.createSchedule({

webhookUrl: 'https://my-agent.com/cron/tick',

cron: '*/5 * * * *', // Every 5 minutes

metadata: {

name: 'Health Check',

},

});

console.log('Schedule ID:', schedule.id);

console.log('Next run:', schedule.nextRun);

console.log('Webhook secret:', schedule.webhookSecret);


Webhook Payloads

When a schedule triggers, CronSynth sends a POST request to your webhook URL.

Request Headers

Content-Type: application/json

X-CronSynth-Signature:

X-CronSynth-Timestamp:

X-CronSynth-Schedule-Id:

Request Body

json

{

"schedule_id": "sch_a1b2c3d4e5f6",

"run_number": 48,

"timestamp": 1701433200,

"cron": "*/5 * * * *",

"scheduled_time": "2025-01-28T12:00:00Z",

"actual_time": "2025-01-28T12:00:01Z",

"metadata": {

"name": "Health Check"

}

}

Webhook Verification

Always verify webhooks before processing:

``typescript

import { verifyWebhook } from '@echorift/cronsynth';

app.post('/cron/tick', (req, res) => {

const isValid = verifyWebhook({

payload: JSON.stringify(req.body),

signature: req.headers['x-cronsynth-signature'],

timestamp: req.headers['x-cronsynth-timestamp'],

secret: process.env.CRONSYNTH_WEBHOOK_SECRET,

});

if (!isValid) {

return res.status(401).send('Invalid signature');

}

// Process the trigger

console.log(Run #${req.body.run_number} at ${req.body.timestamp});

// Respond quickly! Process async if needed.

res.status(200).send('OK');

});

Response Requirements

  • Status 2xx: Trigger acknowledged
  • Status 4xx/5xx: Trigger failed, will retry
  • Timeout (>5s): Trigger failed, will retry

Important: Respond within 5 seconds. If your processing takes longer, acknowledge immediately and process asynchronously.


Cron Expression Reference

CronSynth uses standard 5-field cron expressions:

┌───────────── minute (0-59)

│ ┌───────────── hour (0-23)

│ │ ┌───────────── day of month (1-31)

│ │ │ ┌───────────── month (1-12)

│ │ │ │ ┌───────────── day of week (0-6, Sunday=0)

  • * * * *

`

Common Patterns

ExpressionDescription

-------------------------

* * * * *Every minute

*/5 * * * *Every 5 minutes

0 * * * *Every hour

0 9 * * *Daily at 9 AM UTC

0 9 * * 1Every Monday at 9 AM UTC

0 0 1 * *First day of every month at midnight

0 */4 * * *Every 4 hours

30 9 * * 1-5Weekdays at 9:30 AM UTC

Special Characters

CharacterMeaningExample

-----------------------------

*Any value* * * * * = every minute

,List0,30 * * * * = at 0 and 30 minutes

-Range0-5 * * * * = minutes 0 through 5

/Step*/15 * * * * = every 15 minutes


Schedule Management

List Schedules

GET /api/schedules

json

{

"schedules": [

{

"schedule_id": "sch_a1b2c3d4e5f6",

"cron": "*/5 * * * *",

"webhook_url": "https://my-agent.com/cron/tick",

"status": "active",

"next_run": "2025-01-28T12:05:00Z",

"run_count": 48,

"last_run": "2025-01-28T12:00:00Z",

"last_status": "success"

}

]

}

Unschedule

POST /api/unschedule

json

{

"schedule_id": "sch_a1b2c3d4e5f6"

}

Code Example

typescript

// List all schedules

const schedules = await client.listSchedules();

// Unschedule

await client.unschedule('sch_a1b2c3d4e5f6');


On-Chain Attestation

CronSynth can attest schedule executions on-chain for:

  • Reputation systems: Prove your agent runs reliably
  • Audit trails: Verifiable execution history
  • Dispute resolution: On-chain proof of trigger delivery

How It Works

  1. Schedule trigger fires
  2. Webhook delivered successfully
  3. Execution record created
  4. (Optional) Attestation submitted to Base L2

Attestation Data

solidity

struct ScheduleAttestation {

bytes32 scheduleId;

uint256 runNumber;

uint256 timestamp;

bytes32 webhookHash; // Hash of webhook URL

bool success;

}

Querying Attestations

typescript

// Get attestation for a specific run

const attestation = await client.getAttestation({

scheduleId: 'sch_a1b2c3d4e5f6',

runNumber: 48,

});

// Verify on-chain

console.log('TX Hash:', attestation.txHash);

console.log('Block:', attestation.blockNumber);


Pricing

Free Tier

  • 1 active schedule
  • Unlimited triggers
  • No payment required
  • Great for testing

Pay-Per-Trigger

Trigger CountPrice per Trigger

----------------------------------

1-1,000$0.001

1,001-10,000$0.0008

10,001+$0.0005

Subscription Plans

PlanSchedulesMonthly PricePer-Schedule

----------------------------------------------

Starter5$5$1.00

Pro25$20$0.80

Scale100$60$0.60

Payment

All payments via x402 protocol:

  • Token: USDC on Base
  • Settlement: Instant
  • No accounts required


Integration Patterns

Pattern 1: Periodic Health Check

Agent checks its own status every 5 minutes:

typescript

// Registration

await cronsynth.createSchedule({

webhookUrl: 'https://my-agent.com/health',

cron: '*/5 * * * *',

});

// Webhook handler

app.post('/health', async (req, res) => {

// Verify signature...

// Check agent health

const status = await checkAgentHealth();

if (!status.healthy) {

await alertOperator(status);

}

res.status(200).send('OK');

});

Pattern 2: Daily Report Generation

Agent generates reports at 9 AM:

typescript

// Registration

await cronsynth.createSchedule({

webhookUrl: 'https://my-agent.com/daily-report',

cron: '0 9 * * *',

});

// Webhook handler

app.post('/daily-report', async (req, res) => {

// Acknowledge immediately

res.status(200).send('OK');

// Process async

setImmediate(async () => {

const report = await generateDailyReport();

await distributeReport(report);

});

});

Pattern 3: Market Hours Trading

Agent operates during market hours (9:30 AM - 4 PM ET, weekdays):

typescript

// Registration - check every minute during market hours

// Note: Cron is UTC, so 9:30 AM ET = 14:30 UTC (EST) or 13:30 UTC (EDT)

await cronsynth.createSchedule({

webhookUrl: 'https://my-agent.com/market-tick',

cron: '30-59 13-20 * * 1-5', // Approximate market hours

});

// Webhook handler

app.post('/market-tick', async (req, res) => {

// Verify we're actually in market hours (handle DST)

if (!isMarketOpen()) {

return res.status(200).send('Market closed');

}

await executeMarketStrategy();

res.status(200).send('OK');

});

Pattern 4: Swarm Coordination

Leader agent triggers swarm-wide sync:

typescript

// Leader registration

await cronsynth.createSchedule({

webhookUrl: 'https://leader-agent.com/sync-trigger',

cron: '0 * * * *', // Every hour

});

// Leader webhook handler

app.post('/sync-trigger', async (req, res) => {

// Broadcast to swarm via Switchboard

await switchboard.broadcast({

swarmId: 'my-swarm',

message: {

type: 'SYNC',

timestamp: req.body.timestamp,

runNumber: req.body.run_number,

},

});

res.status(200).send('OK');

});


Retry Behavior

When webhook delivery fails (timeout, 4xx, 5xx):

AttemptDelay

----------------

1Immediate

230 seconds

32 minutes

410 minutes

51 hour

After 5 failed attempts, the trigger is marked as failed and no further retries occur.

Handling Failures

typescript

// Check for failed triggers

const history = await client.getRunHistory({

scheduleId: 'sch_a1b2c3d4e5f6',

status: 'failed',

});

// Retry manually

for (const run of history.runs) {

await processManually(run);

}


Contract Reference

CronSynth Registry (Base Mainnet)

Address: 0x3846Ab73eCb4A1590B56cEB88D9779471B82A314`

Key Functions:

solidity

// Register a schedule (attestation-enabled)

function registerSchedule(

bytes32 scheduleId,

bytes32 webhookHash,

string calldata cron

) external;

// Attest execution

function attestExecution(

bytes32 scheduleId,

uint256 runNumber,

bool success

) external;

// Query attestations

function getAttestation(

bytes32 scheduleId,

uint256 runNumber

) external view returns (ScheduleAttestation memory);


Best Practices

Webhook Design

  1. Respond fast: Return 200 within 5 seconds
  2. Process async: Use queues for long operations
  3. Idempotent handlers: Same run_number may arrive twice
  4. Verify always: Check HMAC signature

Schedule Management

  1. Use descriptive metadata: Makes debugging easier
  2. Monitor run history: Catch failures early
  3. Right-size frequency: Don't poll more than needed
  4. Consider time zones: All cron is UTC

Error Handling

  1. Log everything: Include schedule_id and run_number
  2. Alert on failures: Don't let schedules silently break
  3. Have fallbacks: What if the trigger doesn't arrive?


SDK Reference

Installation

bash

npm install @echorift/cronsynth ethers

Client Initialization

typescript

import { CronSynthClient } from '@echorift/cronsynth';

const client = new CronSynthClient({

signer: wallet, // ethers Signer for x402 payments

});

Full API

typescript

// Schedules

await client.createSchedule({ webhookUrl, cron, metadata });

await client.listSchedules();

await client.getSchedule(scheduleId);

await client.unschedule(scheduleId);

// Run history

await client.getRunHistory({ scheduleId, limit, status });

await client.getAttestation({ scheduleId, runNumber });

// Utilities

verifyWebhook({ payload, signature, timestamp, secret });

parseCron(expression); // Returns next N run times


Related Resources


*CronSynth. Shared time for the machine age.*