Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    pauljphilp

    effect-patterns-scheduling

    pauljphilp/effect-patterns-scheduling
    Coding
    631

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Effect-TS patterns for Scheduling. Use when working with scheduling in Effect-TS applications.

    SKILL.md

    Effect-TS Patterns: Scheduling

    This skill provides 3 curated Effect-TS patterns for scheduling. Use this skill when working on tasks related to:

    • scheduling
    • Best practices in Effect-TS applications
    • Real-world patterns and solutions

    🟢 Beginner Patterns

    Retry Failed Operations

    Rule: Use Effect.retry with a Schedule to handle transient failures gracefully.

    Good Example:

    import { Effect, Schedule, Data } from "effect"
    
    // ============================================
    // 1. Define error types
    // ============================================
    
    class NetworkError extends Data.TaggedError("NetworkError")<{
      readonly message: string
    }> {}
    
    class RateLimitError extends Data.TaggedError("RateLimitError")<{
      readonly retryAfter: number
    }> {}
    
    class NotFoundError extends Data.TaggedError("NotFoundError")<{
      readonly resource: string
    }> {}
    
    // ============================================
    // 2. Simulate a flaky API call
    // ============================================
    
    let callCount = 0
    const fetchData = Effect.gen(function* () {
      callCount++
      yield* Effect.log(`API call attempt ${callCount}`)
    
      // Simulate intermittent failures
      if (callCount < 3) {
        return yield* Effect.fail(new NetworkError({ message: "Connection timeout" }))
      }
    
      return { data: "Success!", attempts: callCount }
    })
    
    // ============================================
    // 3. Basic retry - fixed attempts
    // ============================================
    
    const withBasicRetry = fetchData.pipe(
      Effect.retry(Schedule.recurs(5))  // Retry up to 5 times
    )
    
    // ============================================
    // 4. Retry with delay
    // ============================================
    
    const withDelayedRetry = fetchData.pipe(
      Effect.retry(
        Schedule.spaced("500 millis").pipe(
          Schedule.intersect(Schedule.recurs(5))
        )
      )
    )
    
    // ============================================
    // 5. Retry only specific errors
    // ============================================
    
    const fetchWithErrors = (shouldFail: boolean) =>
      Effect.gen(function* () {
        if (shouldFail) {
          // Randomly fail with different errors
          const random = Math.random()
          if (random < 0.5) {
            return yield* Effect.fail(new NetworkError({ message: "Timeout" }))
          } else if (random < 0.8) {
            return yield* Effect.fail(new RateLimitError({ retryAfter: 1000 }))
          } else {
            return yield* Effect.fail(new NotFoundError({ resource: "user:123" }))
          }
        }
        return "Data fetched!"
      })
    
    // Only retry network and rate limit errors, not NotFoundError
    const retryTransientOnly = fetchWithErrors(true).pipe(
      Effect.retry({
        schedule: Schedule.recurs(3),
        while: (error) =>
          error._tag === "NetworkError" || error._tag === "RateLimitError",
      })
    )
    
    // ============================================
    // 6. Retry with exponential backoff
    // ============================================
    
    const withExponentialBackoff = fetchData.pipe(
      Effect.retry(
        Schedule.exponential("100 millis", 2).pipe(  // 100ms, 200ms, 400ms...
          Schedule.intersect(Schedule.recurs(5))      // Max 5 retries
        )
      )
    )
    
    // ============================================
    // 7. Run and observe
    // ============================================
    
    const program = Effect.gen(function* () {
      yield* Effect.log("Starting retry demo...")
      
      // Reset counter
      callCount = 0
      
      const result = yield* withBasicRetry
      yield* Effect.log(`Final result: ${JSON.stringify(result)}`)
    })
    
    Effect.runPromise(program)
    

    Rationale:

    Use Effect.retry to automatically retry operations that fail due to transient errors like network timeouts.


    Many failures are temporary:

    1. Network issues - Connection drops, timeouts
    2. Rate limits - Too many requests
    3. Resource contention - Database locks
    4. Service restarts - Brief unavailability

    Automatic retries handle these without manual intervention.



    Your First Schedule

    Rule: Use Schedule to control when and how often effects run.

    Good Example:

    import { Effect, Schedule } from "effect"
    
    // ============================================
    // 1. Retry a failing operation
    // ============================================
    
    let attempts = 0
    const flakyOperation = Effect.gen(function* () {
      attempts++
      if (attempts < 3) {
        yield* Effect.log(`Attempt ${attempts} failed`)
        return yield* Effect.fail(new Error("Temporary failure"))
      }
      return `Success on attempt ${attempts}`
    })
    
    // Retry up to 5 times
    const withRetry = flakyOperation.pipe(
      Effect.retry(Schedule.recurs(5))
    )
    
    // ============================================
    // 2. Repeat a successful operation
    // ============================================
    
    const logTime = Effect.gen(function* () {
      const now = new Date().toISOString()
      yield* Effect.log(`Current time: ${now}`)
      return now
    })
    
    // Repeat 3 times
    const repeated = logTime.pipe(
      Effect.repeat(Schedule.recurs(3))
    )
    
    // ============================================
    // 3. Add delays between operations
    // ============================================
    
    // Repeat every second, 5 times
    const polling = logTime.pipe(
      Effect.repeat(
        Schedule.spaced("1 second").pipe(
          Schedule.intersect(Schedule.recurs(5))
        )
      )
    )
    
    // ============================================
    // 4. Common schedule patterns
    // ============================================
    
    // Fixed delay between attempts
    const fixedDelay = Schedule.spaced("500 millis")
    
    // Increasing delay (1s, 2s, 4s, 8s...)
    const exponentialBackoff = Schedule.exponential("1 second")
    
    // Maximum number of attempts
    const limitedAttempts = Schedule.recurs(3)
    
    // Combine: exponential backoff, max 5 attempts
    const retryPolicy = Schedule.exponential("100 millis").pipe(
      Schedule.intersect(Schedule.recurs(5))
    )
    
    // ============================================
    // 5. Run examples
    // ============================================
    
    const program = Effect.gen(function* () {
      yield* Effect.log("--- Retry Example ---")
      const result = yield* withRetry
      yield* Effect.log(`Result: ${result}`)
    
      yield* Effect.log("\n--- Repeat Example ---")
      yield* repeated
    })
    
    Effect.runPromise(program)
    

    Rationale:

    Use Schedule to control timing in Effect programs - retrying failed operations, repeating successful ones, or adding delays.


    Schedules solve common timing problems:

    1. Retries - Try again after failures
    2. Polling - Check for updates periodically
    3. Rate limiting - Control how fast things run
    4. Backoff - Increase delays between attempts


    🟡 Intermediate Patterns

    Scheduling Pattern 1: Repeat an Effect on a Fixed Interval

    Rule: Repeat effects at fixed intervals using Schedule.fixed for steady-state operations and background tasks.

    Good Example:

    This example demonstrates a health check service that polls multiple service endpoints every 30 seconds and reports their status.

    import { Effect, Schedule, Duration } from "effect";
    
    interface ServiceStatus {
      readonly service: string;
      readonly url: string;
      readonly isHealthy: boolean;
      readonly responseTime: number;
      readonly lastChecked: number;
    }
    
    // Mock health check that calls an endpoint
    const checkServiceHealth = (
      url: string,
      service: string
    ): Effect.Effect<ServiceStatus> =>
      Effect.gen(function* () {
        const startTime = Date.now();
    
        // Simulate HTTP call with occasional failures
        const isHealthy = Math.random() > 0.1; // 90% success rate
        const responseTime = Math.random() * 500; // 0-500ms
    
        yield* Effect.sleep(Duration.millis(Math.round(responseTime)));
    
        if (!isHealthy) {
          yield* Effect.fail(new Error(`${service} is unhealthy`));
        }
    
        return {
          service,
          url,
          isHealthy: true,
          responseTime: Math.round(Date.now() - startTime),
          lastChecked: Date.now(),
        };
      });
    
    // Health check for multiple services
    interface HealthCheckConfig {
      readonly services: Array<{
        readonly name: string;
        readonly url: string;
      }>;
      readonly intervalSeconds: number;
    }
    
    // Keep track of service status
    const serviceStatuses = new Map<string, ServiceStatus>();
    
    // Check all services and report status
    const checkAllServices = (
      config: HealthCheckConfig
    ): Effect.Effect<void> =>
      Effect.gen(function* () {
        for (const service of config.services) {
          const status = yield* checkServiceHealth(service.url, service.name).pipe(
            Effect.either
          );
    
          if (status._tag === "Right") {
            serviceStatuses.set(service.name, status.right);
            console.log(
              `✓ ${service.name}: OK (${status.right.responseTime}ms)`
            );
          } else {
            console.log(`✗ ${service.name}: FAILED`);
            // Keep last known status if available
          }
        }
      });
    
    // Create the repeating health check
    const createHealthCheckScheduler = (
      config: HealthCheckConfig
    ): Effect.Effect<void> =>
      checkAllServices(config).pipe(
        // Schedule with fixed interval (fixed = ignore execution time)
        Effect.repeat(
          Schedule.fixed(Duration.seconds(config.intervalSeconds))
        )
      );
    
    // Report current status
    const reportStatus = (): Effect.Effect<void> =>
      Effect.sync(() => {
        if (serviceStatuses.size === 0) {
          console.log("\n[STATUS] No services checked yet");
          return;
        }
    
        console.log("\n[STATUS REPORT]");
        for (const [service, status] of serviceStatuses) {
          const ago = Math.round((Date.now() - status.lastChecked) / 1000);
          console.log(
            `  ${service}: ${status.isHealthy ? "✓" : "✗"} (checked ${ago}s ago)`
          );
        }
      });
    
    // Run health checker in background and check status periodically
    const program = Effect.gen(function* () {
      const config: HealthCheckConfig = {
        services: [
          { name: "API", url: "https://api.example.com/health" },
          { name: "Database", url: "https://db.example.com/health" },
          { name: "Cache", url: "https://cache.example.com/health" },
        ],
        intervalSeconds: 5, // Check every 5 seconds
      };
    
      // Fork the health checker to run in background
      const checker = yield* createHealthCheckScheduler(config).pipe(
        Effect.fork
      );
    
      // Check and report status every 15 seconds for 60 seconds
      yield* reportStatus().pipe(
        Effect.repeat(
          Schedule.addDelay(
            Schedule.recurs(3), // 3 repetitions = 4 total (initial + 3)
            () => Duration.seconds(15)
          )
        )
      );
    
      // Interrupt the background checker
      yield* checker.interrupt();
    });
    
    Effect.runPromise(program);
    

    This pattern:

    1. Defines service health checks that may fail
    2. Uses Schedule.fixed to repeat every 5 seconds
    3. Handles failures gracefully (keeps last known status)
    4. Runs in background while main logic continues
    5. Reports current status at intervals

    Rationale:

    When you need to run an effect repeatedly at regular intervals (e.g., every 5 seconds, every 30 minutes), use Schedule.fixed to specify the interval. This creates a schedule that repeats the effect indefinitely or until a condition stops it, with precise timing between executions.


    Many production systems need periodic operations:

    • Health checks: Poll service availability every 30 seconds
    • Cache refresh: Update cache every 5 minutes
    • Metrics collection: Gather system metrics every 10 seconds
    • Data sync: Sync data with remote service periodically
    • Cleanup tasks: Remove stale data nightly

    Without proper scheduling:

    • Manual polling with while loops wastes CPU (busy-waiting)
    • Thread.sleep blocks threads, preventing other work
    • No automatic restart on failure
    • Difficult to test deterministically

    With Schedule.fixed:

    • Efficient, non-blocking repetition
    • Automatic failure handling and retry
    • Testable with TestClock
    • Clean, declarative syntax


    Recommended Servers
    Vercel Grep
    Vercel Grep
    EasyWeek
    EasyWeek
    PostPulse
    PostPulse
    Repository
    pauljphilp/effectpatterns
    Files