home / skills / andrueandersoncs / claude-skill-effect-ts / scheduling

scheduling skill

/skills/scheduling

This skill helps you understand and implement scheduling patterns, retry policies, and backoff strategies using Effect.

npx playbooks add skill andrueandersoncs/claude-skill-effect-ts --skill scheduling

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
6.2 KB
---
name: Scheduling
description: This skill should be used when the user asks about "Effect Schedule", "retry schedules", "repetition", "Schedule.exponential", "Schedule.spaced", "Schedule.recurs", "cron scheduling", "backoff strategy", "schedule combinators", "Effect.repeat", "Effect.retry", "polling", or needs to understand how Effect handles scheduled operations and retry policies.
version: 1.0.0
---

# Scheduling in Effect

## Overview

Effect's `Schedule` type describes patterns for:

- **Retrying** failed operations
- **Repeating** successful operations
- **Polling** at intervals
- **Backoff strategies** for resilience

```typescript
Schedule<Out, In, Requirements>
//       ^^^  ^^ Output and input types
```

## Built-In Schedules

### Fixed Intervals

```typescript
import { Schedule } from "effect"

const everySecond = Schedule.spaced("1 second")

const fixed = Schedule.fixed("500 millis")
```

### Recurrence Limits

```typescript
const fiveTimes = Schedule.recurs(5)

const once = Schedule.once

const forever = Schedule.forever
```

### Exponential Backoff

```typescript
const exponential = Schedule.exponential("100 millis")

const capped = Schedule.exponential("100 millis").pipe(
  Schedule.upTo("30 seconds")
)

const jittered = Schedule.exponential("100 millis").pipe(
  Schedule.jittered
)
```

### Time-Based Limits

```typescript
const forOneMinute = Schedule.spaced("1 second").pipe(
  Schedule.upTo("1 minute")
)

const untilSuccess = Schedule.recurWhile(
  (result) => result.status === "pending"
)
```

## Using Schedules

### Effect.retry - Retry on Failure

```typescript
const resilientFetch = fetchData().pipe(
  Effect.retry(
    Schedule.exponential("1 second").pipe(
      Schedule.compose(Schedule.recurs(5))
    )
  )
)
```

### Effect.repeat - Repeat on Success

```typescript
const polling = checkStatus().pipe(
  Effect.repeat(Schedule.spaced("5 seconds"))
)
```

### Effect.schedule - Full Control

```typescript
const scheduled = effect.pipe(
  Effect.schedule(mySchedule)
)
```

## Schedule Combinators

### Composing Schedules

```typescript
const exponentialWithLimit = Schedule.exponential("1 second").pipe(
  Schedule.compose(Schedule.recurs(10))
)

const eitherSchedule = Schedule.union(
  Schedule.spaced("1 second"),
  Schedule.recurs(5)
)
```

### Adding Jitter

```typescript
const jittered = Schedule.exponential("1 second").pipe(
  Schedule.jittered
)

const customJitter = Schedule.exponential("1 second").pipe(
  Schedule.jittered({ min: 0.8, max: 1.2 })
)
```

### Delaying First Execution

```typescript
const delayed = Schedule.spaced("1 second").pipe(
  Schedule.delayed(() => "5 seconds")
)
```

### Resetting Schedule

```typescript
const resetting = Schedule.exponential("1 second").pipe(
  Schedule.resetAfter("1 minute")
)
```

## Conditional Retrying

### Retry While Condition

```typescript
// Use Match.tag for error type checking in predicates
const retryTransient = effect.pipe(
  Effect.retry({
    schedule: Schedule.exponential("1 second"),
    while: (error) =>
      Match.value(error).pipe(
        Match.tag("TransientError", () => true),
        Match.orElse(() => false)
      )
  })
)
```

### Retry Until Condition

```typescript
const retryUntilFatal = effect.pipe(
  Effect.retry({
    schedule: Schedule.recurs(10),
    until: (error) =>
      Match.value(error).pipe(
        Match.tag("FatalError", () => true),
        Match.orElse(() => false)
      )
  })
)
```

## Cron Scheduling

```typescript
import { Cron } from "effect"

const daily = Cron.parse("0 0 * * *")

const hourly = Cron.parse("0 * * * *")

const cronSchedule = Schedule.cron(daily)
```

## Schedule Outputs

Schedules can produce values:

```typescript
const withElapsed = Schedule.elapsed

const withCount = Schedule.count

const collecting = Schedule.collectAll<number>()
```

### Using Schedule Output

```typescript
const [result, elapsed] = yield* effect.pipe(
  Effect.retry(
    Schedule.exponential("1 second").pipe(
      Schedule.compose(Schedule.elapsed)
    )
  )
)
console.log(`Took ${elapsed}ms after retries`)
```

## Common Patterns

### API Retry with Backoff

```typescript
const apiCall = fetchFromApi().pipe(
  Effect.retry(
    Schedule.exponential("500 millis").pipe(
      Schedule.jittered,
      Schedule.compose(Schedule.recurs(5)),
      Schedule.upTo("30 seconds")
    )
  )
)
```

### Polling with Timeout

```typescript
const poll = checkJobStatus(jobId).pipe(
  Effect.repeat(
    Schedule.spaced("2 seconds").pipe(
      Schedule.upTo("5 minutes")
    )
  ),
  Effect.timeout("5 minutes")
)
```

### Circuit Breaker Pattern

```typescript
const circuitBreaker = (effect: Effect.Effect<A, E>) => {
  let failures = 0
  const maxFailures = 5
  const resetTimeout = "30 seconds"

  return effect.pipe(
    Effect.retry(
      Schedule.exponential("1 second").pipe(
        Schedule.compose(Schedule.recurs(3)),
        Schedule.tapOutput(() =>
          Effect.sync(() => { failures++ })
        )
      )
    )
  )
}
```

### Retry with Logging

```typescript
const retryWithLogs = effect.pipe(
  Effect.retry(
    Schedule.exponential("1 second").pipe(
      Schedule.compose(Schedule.recurs(5)),
      Schedule.tapInput((error) =>
        Effect.log(`Retrying after error: ${error}`)
      )
    )
  )
)
```

## Schedule Reference

| Schedule | Pattern |
|----------|---------|
| `Schedule.forever` | Never stops |
| `Schedule.once` | Single execution |
| `Schedule.recurs(n)` | Exactly n times |
| `Schedule.spaced(d)` | Fixed delay d |
| `Schedule.fixed(d)` | Fixed interval from start |
| `Schedule.exponential(d)` | d, 2d, 4d, 8d... |
| `Schedule.fibonacci(d)` | d, d, 2d, 3d, 5d... |
| `Schedule.linear(d)` | d, 2d, 3d, 4d... |

## Best Practices

1. **Always add recurs limit** - Avoid infinite retries
2. **Use jitter for distributed systems** - Prevents thundering herd
3. **Cap exponential backoff** - Use upTo() for max delay
4. **Log retry attempts** - Use tapInput for visibility
5. **Different schedules for different errors** - Transient vs permanent

## Additional Resources

For comprehensive scheduling documentation, consult `${CLAUDE_PLUGIN_ROOT}/references/llms-full.txt`.

Search for these sections:
- "Built-In Schedules" for schedule types
- "Schedule Combinators" for composition
- "Repetition" for repeat patterns
- "Retrying" for retry patterns
- "Cron" for cron expressions

Overview

This skill explains how to use Effect's scheduling primitives to retry, repeat, poll, and apply backoff strategies. It highlights built-in schedules, combinators, cron integration, and common patterns for resilient, observable operations. Use it to choose and compose schedules for retries, polling, and timed repetitions.

How this skill works

Schedules describe timing and stop conditions and can be attached to effects via Effect.retry, Effect.repeat, or Effect.schedule. Built-in schedules include fixed intervals, spaced delays, exponential and linear backoffs, recurrences, and cron-based triggers. Combinators let you compose, cap, jitter, delay, reset, and extract schedule outputs (count, elapsed, collected values).

When to use it

  • Retrying failed operations with backoff (e.g., API calls)
  • Polling a service or job status on an interval
  • Repeating successful tasks (e.g., heartbeat or sync)
  • Implementing circuit-breaker-like reset or failure counting
  • Scheduling tasks with cron expressions for calendar-based triggers

Best practices

  • Always include a recurs or upTo limit to avoid infinite retries
  • Add jitter to distributed retries to prevent thundering-herd effects
  • Cap exponential backoff using upTo to bound maximum delay
  • Log or tapInput on retry attempts for observability
  • Differentiate schedules by error type (transient vs permanent)

Example use cases

  • API retry with exponential backoff, jitter, a max delay, and a max attempt count
  • Polling a job status every few seconds with an overall timeout
  • Repeating a success-based check every fixed interval using Effect.repeat
  • Using Schedule.cron(parse) to run daily or hourly tasks
  • Counting failures and resetting the schedule after a cooldown to implement circuit-breaker behavior

FAQ

How do I stop retries on a specific error?

Use Effect.retry with a while or until predicate that inspects the error and returns false/true to stop retries for fatal errors.

How can I avoid synchronized retries across many clients?

Apply Schedule.jittered (or custom jitter range) to add randomized variation to backoff intervals and prevent thundering-herd spikes.