scheduling

SKILL.md

Scheduling in Effect

Overview

Effect's Schedule type describes patterns for:

  • Retrying failed operations
  • Repeating successful operations
  • Polling at intervals
  • Backoff strategies for resilience
Schedule<Out, In, Requirements>;
//       ^^^  ^^ Output and input types

Built-In Schedules

Fixed Intervals

import { Schedule } from "effect";

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

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

Recurrence Limits

const fiveTimes = Schedule.recurs(5);

const once = Schedule.once;

const forever = Schedule.forever;

Exponential Backoff

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

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

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

Effect.repeat - Repeat on Success

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

Effect.schedule - Full Control

const scheduled = effect.pipe(Effect.schedule(mySchedule));

Schedule Combinators

Composing Schedules

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

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

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

Resetting Schedule

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

Conditional Retrying

Retry While Condition

// 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

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

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:

const withElapsed = Schedule.elapsed;

const withCount = Schedule.count;

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

Using Schedule Output

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

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

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

Circuit Breaker Pattern

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

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
Weekly Installs
8
GitHub Stars
5
First Seen
Jan 24, 2026
Installed on
claude-code7
opencode6
gemini-cli6
github-copilot6
codex6
antigravity5