# Understanding the Automation Event System and Cron-Based Triggers in Craft Agents

> Explore Craft Agents automation event system and cron-based triggers. Learn how the publish/subscribe architecture executes actions on specific events using the SchedulerTick event for scheduled tasks.

- Repository: [Craft Docs/craft-agents-oss](https://github.com/lukilabs/craft-agents-oss)
- Tags: deep-dive
- Published: 2026-04-18

---

**Craft Agents’ automation event system is a publish/subscribe architecture that executes actions when specific events occur, using cron-based triggers exclusively through the `SchedulerTick` event to run scheduled automations.**

The `lukilabs/craft-agents-oss` repository implements this system to orchestrate prompts, webhooks, and other actions based on both application-level events and time-based schedules. The core implementation spans the `packages/shared/src/automations` and `packages/shared/src/scheduler` directories, providing a type-safe, rate-limited mechanism for event-driven automation.

## Core Components of the Automation Event System

### Event Taxonomy

The system recognizes three event categories defined in [`packages/shared/src/automations/types.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/automations/types.ts):

- **AppEvent**: Native Craft events such as `LabelAdd`, `PermissionModeChange`, `SessionStatusChange`, and **`SchedulerTick`**
- **AgentEvent**: Claude SDK events including `PreToolUse`, `PostToolUse`, `Notification`, and `UserPromptSubmit`
- **AutomationEvent**: The union type `AppEvent | AgentEvent` used throughout the automation engine

The `SchedulerTick` event is the only AppEvent that supports cron-based scheduling, serving as the temporal heartbeat for the entire system.

### The WorkspaceEventBus

All events flow through the **`WorkspaceEventBus`** class in [`packages/shared/src/automations/event-bus.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/automations/event-bus.ts). This publish/subscribe hub exposes two primary methods:

- `bus.on(event, handler)` – Registers an event handler
- `bus.emit(event, payload)` – Distributes events to registered handlers

The bus enforces rate limits per event type. Specifically, **`SchedulerTick`** is capped at **60 events per minute** to prevent runaway execution, as implemented in lines 86-92 of [`event-bus.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/event-bus.ts).

## How Cron-Based Triggers Work

### The SchedulerTick Event

The **`SchedulerService`** in [`packages/shared/src/scheduler/scheduler-service.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/scheduler/scheduler-service.ts) generates the `SchedulerTick` event. Upon startup, it aligns to the next minute boundary and emits a payload every 60 seconds containing:

- UTC timestamp
- Local time string
- Hour, minute, and day-of-week values

The `AutomationSystem` instantiates `SchedulerService` and forwards each tick to the `WorkspaceEventBus`, as shown in lines 292-294 of [`packages/shared/src/automations/automation-system.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/automations/automation-system.ts).

### Cron Expression Matching

Cron evaluation occurs in [`packages/shared/src/automations/cron-matcher.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/automations/cron-matcher.ts) via the **`matchesCron`** function. This utility:

1. Parses the cron expression using the `croner` library
2. Checks if the current minute matches the schedule
3. Supports optional IANA timezone strings

The function signature is:

```typescript
export function matchesCron(cronExpression: string, timezone?: string): boolean

```

`matchesCron` is re-exported publicly in [`packages/shared/src/automations/index.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/automations/index.ts) (lines 102-104) for use in custom automation logic.

### Validation and Constraints

The validation layer in [`packages/shared/src/automations/validation.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/automations/validation.ts) enforces a critical constraint: **cron expressions are only valid for `SchedulerTick` events**. If a configuration includes a `cron` field on any other event type, the validator rejects it with a descriptive warning (lines 163-170).

Additionally, the `AutomationMatcher` type definition in [`types.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/types.ts) (lines 150-155) specifies that the `cron` field must accompany a `SchedulerTick` matcher, ensuring type safety across the codebase.

## Implementation Examples

### Configuring a Cron-Based Automation

Define a weekday morning automation in your [`automations.json`](https://github.com/lukilabs/craft-agents-oss/blob/main/automations.json):

```json
{
  "automations": {
    "SchedulerTick": [
      {
        "id": "daily001",
        "name": "Morning Summary",
        "cron": "0 9 * * 1-5",
        "timezone": "Europe/Budapest",
        "actions": [
          { "type": "prompt", "prompt": "Generate the daily summary for today." }
        ]
      }
    ]
  }
}

```

The `cron` field `"0 9 * * 1-5"` executes at 09:00 on weekdays only.

### Programmatic Cron Checking

Use `matchesCron` in custom extensions:

```typescript
import { matchesCron } from '@craft-agents/shared/automations';

// Check if current time matches "every hour at minute 15"
if (matchesCron('15 * * * *')) {
  console.log('Executing hourly task at 15 minutes past the hour');
}

```

### Subscribing to SchedulerTick Events

Monitor system ticks for logging or metrics:

```typescript
import { WorkspaceEventBus } from '@craft-agents/shared/automations';
import type { SchedulerTickPayload } from '@craft-agents/shared/scheduler';

const bus = new WorkspaceEventBus();

bus.on('SchedulerTick', async (payload: SchedulerTickPayload) => {
  console.log(`[${payload.localTime}] Tick received - Day: ${payload.dayName}`);
});

```

Remember that the bus enforces a 60 events per minute limit on `SchedulerTick` to prevent resource exhaustion.

## Summary

- The **automation event system** in Craft Agents uses a publish/subscribe architecture centered on the `WorkspaceEventBus` to route events to registered handlers.
- **Cron-based triggers** are exclusively tied to the `SchedulerTick` event, emitted every minute by the `SchedulerService` with full timestamp and calendar data.
- The **`matchesCron`** function in [`cron-matcher.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/cron-matcher.ts) evaluates cron expressions against the current tick, supporting IANA timezones for geographically aware scheduling.
- Validation in [`validation.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/validation.ts) strictly prevents cron expressions on non-scheduler events, ensuring architectural consistency.
- Rate limiting on `SchedulerTick` (60 events per minute) protects the system from runaway automation loops.

## Frequently Asked Questions

### Can I use cron expressions with events other than SchedulerTick?

No. The validation layer in [`packages/shared/src/automations/validation.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/automations/validation.ts) explicitly rejects configurations that include a `cron` field on any event type other than `SchedulerTick`. This design ensures that time-based scheduling only occurs through the dedicated minute-granular heartbeat provided by the `SchedulerService`.

### How does the system handle timezone specifications in cron expressions?

The `matchesCron` function accepts an optional IANA timezone string (e.g., `"America/New_York"` or `"Europe/Budapest"`). When provided, the function evaluates the cron expression against the local time in that timezone rather than UTC. This allows automations to fire at consistent local times regardless of daylight saving transitions or server location.

### What happens if an automation takes longer than a minute to execute?

The `SchedulerService` emits `SchedulerTick` events strictly every 60 seconds, and the `WorkspaceEventBus` enforces a rate limit of 60 ticks per minute. However, the system does not wait for previous automation actions to complete before emitting the next tick. Long-running actions may overlap with subsequent ticks, so automations should be designed to be idempotent or include their own concurrency controls if overlapping execution would cause issues.

### Where is the cron matching logic implemented and can I use it independently?

The cron matching logic resides in [`packages/shared/src/automations/cron-matcher.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/automations/cron-matcher.ts) within the `matchesCron` function. This utility is re-exported publicly from [`packages/shared/src/automations/index.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/automations/index.ts), making it available for import as `matchesCron` from the `@craft-agents/shared/automations` package. You can use it independently to check if the current time matches any cron expression, with optional timezone support.