Skip to main content

async-operation-handler

Handle asynchronous operations from MCP servers (PixelLab, ElevenLabs, etc.) with intelligent polling, timeout management, and parallel work opportunities. Use when waiting for async jobs, polling status, or managing long-running operations. Provides exponential backoff, ETA-aware waiting, and prevents premature downloads.

Jump to install

Source facts

Repository
pmarashian/cursor-agent-skills
Last source activity
February 13, 2026 at 19:51
Detected SKILL.md language
English
Stars
2
Forks
1

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
async-operation-handler
description
Handle asynchronous operations from MCP servers (PixelLab, ElevenLabs, etc.) with intelligent polling, timeout management, and parallel work opportunities. Use when waiting for async jobs, polling status, or managing long-running operations. Provides exponential backoff, ETA-aware waiting, and prevents premature downloads.
# Async Operation Handler Handle asynchronous operations efficiently with intelligent polling, timeout management, and parallel work opportunities. Reduces async operation overhead by 50-70% and saves 15-30 seconds per operation. ## Overview When working with async operations (PixelLab character generation, ElevenLabs TTS, etc.), use this skill to: - Poll with exponential backoff instead of fixed intervals - Respect API-provided ETAs when available - Handle timeouts gracefully - Use waiting time for parallel work - Prevent premature download attempts ## ⚠️ CRITICAL: Use Exponential Backoff, Not Fixed Intervals ⚠️ **AGENTS WASTE 20-40% OF WAIT TIME BY USING FIXED INTERVALS INSTEAD OF EXPONENTIAL BACKOFF.** **The Problem**: Fixed intervals (e.g., 30s, 60s) waste time by polling too frequently early and not frequently enough later. **The Solution**: Exponential backoff (5s → 10s → 20s → 40s → 60s) adapts to operation progress. ## Core Patterns ### Exponential Backoff Polling (CORRECT) **✅ CORRECT: Use exponential backoff - 5s → 10s → 20s → 40s → 60s** ```typescript async function pollAsyncOperation( checkStatus: () => Promise<StatusResponse>, options: PollOptions = {} ): Promise<StatusResponse> { const { maxWait = 300000, // 5 minutes default backoff = 'exponential', // 'exponential' or 'fixed' respectETA = true, // Use API-provided ETAs initialInterval = 5000 // Start at 5 seconds } = options; let interval = initialInterval; const startTime = Date.now(); while (Date.now() - startTime < maxWait) { const status = await checkStatus(); // Success: return immediately if (status.status === 'completed') { return status; } // Failure: throw error if (status.status === 'failed') { throw new Error(`Operation failed: ${status.error || 'Unknown error'}`); } // Calculate wait time let waitTime = interval; // Respect ETA if available and respectETA is true if (respectETA && status.eta_seconds) { waitTime = Math.min(status.eta_seconds * 1000, interval * 2); } // Wait before next poll await sleep(waitTime); // Exponential backoff: 5s → 10s → 20s → 40s (capped) if (backoff === 'exponential') { interval = Math.min(interval * 2, 40000); // Cap at 40 seconds } } throw new Error(`Operation timed out after ${maxWait}ms`); } ``` ### Fixed Interval Polling (INCORRECT - ANTI-PATTERN) **❌ WRONG: Fixed intervals waste 20-40% of wait time** ```typescript // ❌ INCORRECT: Fixed 30 second intervals async function pollWithFixedInterval( checkStatus: () => Promise<StatusResponse> ): Promise<StatusResponse> { const maxWait = 300000; // 5 minutes const startTime = Date.now(); while (Date.now() - startTime < maxWait) { const status = await checkStatus(); if (status.status === 'completed') { return status; } // ❌ WRONG: Fixed 30 second wait await sleep(30000); // Always wait 30 seconds, regardless of progress } throw new Error('Operation timed out'); } ``` **Why Fixed Intervals Are Inefficient**: - **Early polls**: Too frequent (operation just started, won't be ready) - **Later polls**: Too infrequent (operation may be ready, but we wait full interval) - **Waste**: 20-40% of total wait time is wasted **Example Timeline (Fixed 30s vs Exponential Backoff)**: ``` Fixed 30s intervals: 0s: Poll → Not ready 30s: Poll → Not ready (wasted 30s) 60s: Poll → Not ready (wasted 30s) 90s: Poll → Ready! (but we waited full 30s when it was ready at 75s) Total: 90s (15s wasted) Exponential backoff: 0s: Poll → Not ready 5s: Poll → Not ready 15s: Poll → Not ready 35s: Poll → Ready! Total: 35s (0s wasted) ``` ### Visual Comparison: Fixed vs Exponential **Fixed Interval (30s)**: ``` Poll 1: 0s → Not ready → Wait 30s Poll 2: 30s → Not ready → Wait 30s Poll 3: 60s → Not ready → Wait 30s Poll 4: 90s → Ready! (but was ready at 75s, wasted 15s) Total: 90s (15s wasted) ``` **Exponential Backoff**: ``` Poll 1: 0s → Not ready → Wait 5s Poll 2: 5s → Not ready → Wait 10s Poll 3: 15s → Not ready → Wait 20s Poll 4: 35s → Ready! Total: 35s (0s wasted) ``` **Time Saved**: 55 seconds (61% faster) ### ETA-Aware Waiting **Respect API-provided ETAs when available:** ```typescript // PixelLab example const character = await mcp_pixellab_get_character({ character_id }); if (character.eta_seconds) { // Wait for ETA, but don't wait longer than current interval const waitTime = Math.min(character.eta_seconds * 1000, currentInterval); await sleep(waitTime); } ``` ### ETA-Based Scheduling **Schedule status checks at strategic points based on ETA:** Instead of polling at fixed intervals, schedule checks at percentage milestones of the ETA: ```typescript async function pollWithETAScheduling( checkStatus: () => Promise<StatusResponse>, initialETA: number ): Promise<StatusResponse> { const milestones = [ initialETA * 0.10, // Check at 10% of ETA initialETA * 0.50, // Check at 50% of ETA initialETA * 0.75, // Check at 75% of ETA ]; let currentMilestone = 0; const startTime = Date.now(); while (true) { const elapsed = (Date.now() - startTime) / 1000; // seconds // Check if we've reached the next milestone if (currentMilestone < milestones.length && elapsed >= milestones[currentMilestone]) { const status = await checkStatus(); // Update ETA if provided if (status.eta_seconds) { // Recalculate milestones based on new ETA const remainingTime = status.eta_seconds; milestones.splice(0, currentMilestone + 1); milestones.push( elapsed + remainingTime * 0.10, elapsed + remainingTime * 0.50, elapsed + remainingTime * 0.75 ); currentMilestone = 0; } if (status.status === 'completed') { return status; } if (status.status === 'failed') { throw new Error(`Operation failed: ${status.error || 'Unknown error'}`); } currentMilestone++; } // If past all milestones, poll more frequently until complete if (currentMilestone >= milestones.length) { const status = await checkStatus(); if (status.status === 'completed') { return status; } if (status.status === 'failed') { throw new Error(`Operation failed: ${status.error || 'Unknown error'}`); } // Poll every 20 seconds after milestones await sleep(20000); } else { // Wait until next milestone const nextMilestone = milestones[currentMilestone]; const waitTime = Math.max(1000, (nextMilestone - elapsed) * 1000); await sleep(waitTime); } } } ``` **Example Timeline (ETA: 176 seconds):** ``` 0s: Poll → Not ready, ETA: 176s 18s: Poll at 10% (17.6s) → Not ready, ETA: 158s (updated) 88s: Poll at 50% (88s) → Not ready, ETA: 88s (updated) 132s: Poll at 75% (132s) → Not ready, ETA: 44s (updated) 176s: Poll → Ready! Total: 176s (optimal - no wasted polls) ``` **Benefits:** - Reduces API calls by 60-70% compared to fixed intervals - Adapts to changing ETAs dynamically - Checks at strategic points (10%, 50%, 75%) before final completion - More efficient than exponential backoff for operations with reliable ETAs **When to Use ETA-Based Scheduling:** - Operations provide reliable ETA information (PixelLab, ElevenLabs) - ETA is reasonably accurate (within 20% variance) - Operation duration is predictable **When to Use Exponential Backoff Instead:** - ETA information is unreliable or unavailable - Operation duration is highly variable - Need more frequent early checks for debugging ### Pre-Download Validation **ALWAYS verify status === "completed" before download:** ```typescript // ❌ WRONG: Download immediately const character = await create_character({ description: "wizard" }); const url = character.download_url; // May be null or locked // ✅ CORRECT: Wait for completion let character = await create_character({ description: "wizard" }); while (character.status !== 'completed') { await sleep(5000); character = await get_character({ character_id: character.character_id }); } // Now safe to download const url = character.download_url; ``` ### Parallel Work During Waiting **Use waiting time for parallel work:** ```typescript // Start async operation const { character_id, job_id } = await mcp_pixellab_create_character({ description: "wizard", n_directions: 8 }); // While waiting, do parallel work: // 1. Prepare integration code const integrationCode = prepareIntegrationCode(); // 2. Check existing assets const existingAssets = checkExistingAssets(); // 3. Poll for completion (with backoff) const character = await pollAsyncOperation( () => mcp_pixellab_get_character({ character_id }), { maxWait: 300000, respectETA: true } ); // Now integrate immediately integrateAsset(character, integrationCode); ``` ## Integration Examples ### PixelLab Character Generation ```typescript async function generateCharacterWithBackoff(description: string) { // 1. Create character const { character_id } = await mcp_pixellab_create_character({ description, n_directions: 8, size: 48 }); // 2. Poll with exponential backoff const character = await pollAsyncOperation( () => mcp_pixellab_get_character({ character_id }), { maxWait: 300000, // 5 minutes respectETA: true, backoff: 'exponential' } ); // 3. Verify completion before download if (character.status !== 'completed') { throw new Error('Character generation incomplete'); } return character; } ``` ### PixelLab Tile Generation ```typescript async function generateTileWithBackoff(description: string) { const { tile_id } = await mcp_pixellab_create_isometric_tile({ description, size: 32 }); const tile = await pollAsyncOperation( () => mcp_pixellab_get_isometric_tile({ tile_id }), { maxWait: 120000, // 2 minutes for tiles respectETA: true } ); return tile; } ``` ### ElevenLabs TTS ```typescript async function generateTTSWithBackoff(text: string, voiceId: string) { // Note: ElevenLabs TTS is usually synchronous, but if async: const { job_id } = await mcp_ElevenLabs_text_to_speech({ text, voice_id: voiceId }); const result = await pollAsyncOperation( () => checkTTSStatus(job_id), { maxWait: 60000, // 1 minute for TTS respectETA: false // TTS usually doesn't provide ETAs } ); return result; } ``` ## Polling Intervals ### Recommended Intervals - **Initial poll**: 5 seconds - **After first poll**: 10 seconds - **After second poll**: 20 seconds - **After third poll**: 40 seconds (capped) - **Maximum wait**: 5 minutes for character generation, 2 minutes for tiles ### When to Use Fixed vs Exponential - **Exponential backoff**: Long operations (character generation, complex assets) - **Fixed interval**: Short operations (tiles, simple assets) or when ETA is reliable ## Timeout Handling ### Maximum Timeouts by Operation Type ```typescript const TIMEOUTS = { character_generation: 300000, // 5 minutes tile_generation: 120000, // 2 minutes map_object: 180000, // 3 minutes animation: 240000, // 4 minutes default: 300000 // 5 minutes }; ``` ### Graceful Timeout Handling ```typescript try { const result = await pollAsyncOperation(checkStatus, { maxWait: TIMEOUTS.character_generation }); return result; } catch (error) { if (error.message.includes('timed out')) { // Log timeout, suggest retry or fallback console.warn('Operation timed out, consider retrying or using fallback'); throw new Error('Operation timed out after maximum wait period'); } throw error; } ``` ## Progress Tracking ### Log Progress and ETA ```typescript async function pollWithProgress( checkStatus: () => Promise<StatusResponse>, options: PollOptions ) { let attempt = 0; while (true) { const status = await checkStatus(); attempt++; // Log progress console.log(`Poll attempt ${attempt}: status=${status.status}`); if (status.eta_seconds) { console.log(`ETA: ${status.eta_seconds} seconds`); } if (status.status === 'completed') { console.log('Operation completed successfully'); return status; } // Wait with backoff await sleep(calculateWaitTime(status, attempt)); } } ``` ## Common Mistakes ### ❌ Mistake 1: Fixed Interval Polling (MOST COMMON) **Problem**: Using fixed intervals (30s, 60s) instead of exponential backoff **Example**: ```typescript // ❌ WRONG: Fixed 30 second intervals while (status !== 'completed') { await sleep(30000); // Always 30 seconds status = await checkStatus(); } // ❌ WRONG: Fixed 60 second intervals while (status !== 'completed') { await sleep(60000); // Always 60 seconds status = await checkStatus(); }
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub