> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.athenaintel.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.athenaintel.com/_mcp/server.

# Execute AOPs

> Execute Agent Operating Procedures (AOPs) with the TypeScript SDK using asynchronous execution

This guide shows how to execute Agent Operating Procedures (AOPs) using the TypeScript SDK with **asynchronous execution** - the recommended approach for production applications. AOPs are pre-configured AI workflows that can perform complex tasks like research, analysis, and content generation with optional user inputs for customization.

**Why Async Execution?** Long-running AOPs can take minutes to complete. Asynchronous execution prevents timeouts, provides real-time progress tracking, and ensures reliable execution of complex workflows.

Key features:

* **Production-ready async execution** - Prevents timeouts for long-running workflows
* **Real-time progress monitoring** - Track execution status with polling
* **Robust error handling** - Comprehensive error handling and retry logic
* **Full TypeScript support** - Complete type safety with proper interfaces
* **Thread-based tracking** - Monitor execution progress via thread IDs

### Install Package

#### pnpm

```bash
pnpm add @athenaintel/sdk
```

#### bun

```bash
bun add @athenaintel/sdk
```

#### yarn

```bash
yarn add @athenaintel/sdk
```

#### npm

```bash
npm install @athenaintel/sdk
```

### Set Up Client

```typescript
import type { AthenaIntelligence } from '@athenaintel/sdk';
import { AthenaIntelligenceClient, AthenaIntelligenceError } from '@athenaintel/sdk';

// Production client setup
const client = new AthenaIntelligenceClient({
  apiKey: process.env.ATHENA_API_KEY,
});

// Custom API endpoint (if needed)
const customClient = new AthenaIntelligenceClient({
  apiKey: process.env.ATHENA_API_KEY,
  baseUrl: 'https://your-custom-api.example.com',
});

// Local development
const devClient = new AthenaIntelligenceClient({
  apiKey: process.env.ATHENA_API_KEY,
  baseUrl: 'http://localhost:8000',
});
```

### TypeScript Type Definitions

Define proper interfaces to avoid using `any` types:

```typescript
// Content types for message content
type MessageContent = string | Array<{ type: string; text?: string }>;

// Progress update interface
interface ProgressUpdate {
  attempt: number;
  maxAttempts: number;
  status: string;
  threadId: string;
  updatedAt: string;
}

// AOP execution result
interface AOPExecutionResult {
  status: 'completed' | 'failed' | 'timeout' | 'error';
  threadId: string;
  result?: string;
  error?: string;
  conversationAssetId?: string;
  messageCount?: number | null;
  threadStatus?: AthenaIntelligence.ThreadStatusResponseOut;
}

// Complete execution response
interface ExecutionResponse {
  startResponse: AthenaIntelligence.AopAsyncExecuteResponseOut;
  finalResult: AOPExecutionResult;
  success: boolean;
  error?: string;
  statusCode?: number;
}
```

### Content Helper Functions

Type-safe helper functions to extract text from message content:

```typescript
// Helper function to extract text from message content
function extractMessageText(content: MessageContent): string {
  if (typeof content === 'string') {
    return content;
  } else if (Array.isArray(content)) {
    return content
      .filter(part => part.type === 'text' && part.text)
      .map(part => part.text!)
      .join('\n');
  }
  return '';
}

// Helper function to safely get final assistant message text
function getFinalAssistantText(conversation: AthenaIntelligence.ConversationResult | undefined): string | null {
  if (!conversation?.last_assistant_message?.content) {
    return null;
  }
  return extractMessageText(conversation.last_assistant_message.content);
}

// Helper to safely extract text from conversation asset
function getConversationAssetText(conversationAsset: AthenaIntelligence.ConversationAssetInfo | undefined): string | null {
  if (!conversationAsset?.last_message?.content) {
    return null;
  }
  return extractMessageText(conversationAsset.last_message.content);
}
```

### Async AOP Execution Pattern

The recommended approach - `executeAsync` **starts** execution and returns a thread_id for tracking:

```typescript
// Execute AOP asynchronously and monitor until completion
async function executeAOPAsync(
  assetId: string, 
  userInputs: Record<string, string> = {}
): Promise<ExecutionResponse> {
  try {
    console.log('🚀 Starting async AOP execution...');
    
    // Step 1: Start async execution (returns immediately with thread_id)
    const asyncResponse = await client.aop.executeAsync({
      asset_id: assetId,
      user_inputs: userInputs,
    });
    
    // executeAsync only STARTS the execution - it does NOT complete it
    console.log('✅ AOP execution initiated (running in background)');
    console.log('Thread ID:', asyncResponse.thread_id);
    console.log('Status:', asyncResponse.status); // Will be "started" or similar
    console.log('AOP Title:', asyncResponse.aop_title);
    
    // Step 2: Monitor execution until it actually completes
    const result = await monitorExecution(asyncResponse.thread_id);
    
    return {
      startResponse: asyncResponse,
      finalResult: result,
      success: true,
    };
  } catch (error) {
    console.error('AOP execution failed:', error);
    
    if (error instanceof AthenaIntelligenceError) {
      return {
        success: false,
        error: `API Error: ${error.statusCode} - ${error.message}`,
        statusCode: error.statusCode,
      } as ExecutionResponse;
    }
    
    return {
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error occurred',
    } as ExecutionResponse;
  }
}
```

### Production-Ready Progress Monitoring

Monitor execution progress using `threads.getStatus` with comprehensive error handling:

```typescript
async function monitorExecution(threadId: string): Promise<AOPExecutionResult> {
  const maxAttempts = 60; // 5 minutes with 5-second intervals
  const pollInterval = 5000; // 5 seconds between polls
  let consecutiveErrors = 0;
  const maxConsecutiveErrors = 3;
  
  console.log(`📊 Starting progress monitoring for thread: ${threadId}`);
  
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      // Poll thread status
      const statusResponse = await client.threads.getStatus(threadId);
      
      // Reset error counter on successful response
      consecutiveErrors = 0;
      
      console.log(`[${attempt}/${maxAttempts}] Status: ${statusResponse.status} (Updated: ${statusResponse.updated_at})`);
      
      // Check if execution completed
      if (statusResponse.status === 'completed') {
        console.log('🎉 Thread execution finished successfully!');
        
        // Extract final result safely
        const conversationAsset = statusResponse.conversation_asset;
        if (conversationAsset) {
          const finalText = getConversationAssetText(conversationAsset);
          
          return {
            status: 'completed',
            threadId,
            result: finalText || 'Thread completed but no final message available',
            conversationAssetId: conversationAsset.conversation_asset_id,
            messageCount: conversationAsset.num_messages,
            threadStatus: statusResponse,
          };
        } else {
          return {
            status: 'completed',
            threadId,
            result: 'Thread completed but no conversation asset available',
            threadStatus: statusResponse,
          };
        }
      } else if (statusResponse.status === 'failed') {
        console.error('❌ Thread execution failed');
        return {
          status: 'failed',
          threadId,
          error: 'Thread execution failed',
          threadStatus: statusResponse,
        };
      } else {
        // Still running
        console.log(`⏳ Still running... (${statusResponse.status})`);
        
        // Wait before next poll
        await new Promise(resolve => setTimeout(resolve, pollInterval));
      }
    } catch (error) {
      consecutiveErrors++;
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      console.error(`Status check failed (${consecutiveErrors}/${maxConsecutiveErrors}):`, errorMessage);
      
      // Fail fast if too many consecutive errors
      if (consecutiveErrors >= maxConsecutiveErrors) {
        return {
          status: 'error',
          threadId,
          error: `Too many consecutive status check failures: ${errorMessage}`,
        };
      }
      
      // Wait before retrying
      await new Promise(resolve => setTimeout(resolve, pollInterval));
    }
  }
  
  console.warn('⏰ Polling timeout reached');
  return {
    status: 'timeout',
    threadId,
    error: 'Monitoring timeout reached - execution may still be running',
  };
}
```

### Basic Usage Example

Execute an AOP with custom parameters:

```typescript
async function basicExample() {
  const result = await executeAOPAsync(
    'asset_9249292-d118-42d3-95b4-00eccfe0754f',
    {
      company: 'Acme Corp',
      quarter: 'Q1 2024',
      analysis_type: 'comprehensive',
    }
  );
  
  if (result.success) {
    console.log('✅ Execution completed successfully');
    console.log('Final result:', result.finalResult.result);
  } else {
    console.error('❌ Execution failed:', result.error);
  }
}

// Run the example
basicExample();
```

### Advanced Usage with Custom Monitoring

Customize monitoring behavior with progress callbacks:

```typescript
// Custom monitoring with progress callbacks
async function executeWithCustomMonitoring(
  assetId: string,
  userInputs: Record<string, string>,
  onProgress?: (status: ProgressUpdate) => void
): Promise<AOPExecutionResult> {
  try {
    // Start execution (returns immediately)
    const asyncResponse = await client.aop.executeAsync({
      asset_id: assetId,
      user_inputs: userInputs,
    });
    
    console.log('🚀 AOP initiated:', asyncResponse.aop_title);
    console.log('📍 Thread ID:', asyncResponse.thread_id);
    
    // Monitor with callbacks
    const result = await monitorWithCallback(asyncResponse.thread_id, onProgress);
    
    return result;
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : 'Unknown error';
    throw new Error(`AOP execution failed: ${errorMessage}`);
  }
}

async function monitorWithCallback(
  threadId: string,
  onProgress?: (status: ProgressUpdate) => void
): Promise<AOPExecutionResult> {
  const maxAttempts = 120; // 10 minutes for longer workflows
  const pollInterval = 5000;
  
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      const status = await client.threads.getStatus(threadId);
      
      // Call progress callback if provided
      if (onProgress) {
        onProgress({
          attempt,
          maxAttempts,
          status: status.status,
          updatedAt: status.updated_at,
          threadId: status.thread_id,
        });
      }
      
      if (status.status === 'completed') {
        const finalText = getConversationAssetText(status.conversation_asset);
        return {
          status: 'completed',
          threadId,
          result: finalText || 'No final result available',
          threadStatus: status,
        };
      } else if (status.status === 'failed') {
        throw new Error('Thread execution failed');
      }
      
      await new Promise(resolve => setTimeout(resolve, pollInterval));
    } catch (error) {
      if (attempt === maxAttempts) {
        throw error;
      }
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      console.warn(`Status check failed, retrying... (${errorMessage})`);
      await new Promise(resolve => setTimeout(resolve, pollInterval));
    }
  }
  
  throw new Error('Monitoring timeout reached');
}
```

### Multiple AOP Execution

Execute multiple AOPs concurrently:

```typescript
interface AOPConfig {
  name: string;
  assetId: string;
  inputs: Record<string, string>;
}

interface AOPStartResult {
  name: string;
  threadId?: string;
  aopTitle?: string;
  success: boolean;
  error?: string;
}

async function executeMultipleAOPs() {
  const aopConfigs: AOPConfig[] = [
    {
      name: 'Market Research',
      assetId: 'asset_market_research',
      inputs: { company: 'Tesla', quarter: 'Q3 2024' },
    },
    {
      name: 'Competitor Analysis',
      assetId: 'asset_competitor_analysis',
      inputs: { company: 'Tesla', competitors: 'Ford,GM,Rivian' },
    },
    {
      name: 'Financial Summary',
      assetId: 'asset_financial_analysis',
      inputs: { company: 'Tesla', period: 'quarterly' },
    },
  ];
  
  console.log(`🔄 Starting ${aopConfigs.length} AOPs concurrently...`);
  
  // Step 1: Start all AOPs asynchronously (all return immediately with thread_ids)
  const startPromises = aopConfigs.map(async (config): Promise<AOPStartResult> => {
    try {
      const response = await client.aop.executeAsync({
        asset_id: config.assetId,
        user_inputs: config.inputs,
      });
      
      console.log(`✅ ${config.name} started with thread: ${response.thread_id}`);
      
      return {
        name: config.name,
        threadId: response.thread_id,
        aopTitle: response.aop_title,
        success: true,
      };
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      return {
        name: config.name,
        error: errorMessage,
        success: false,
      };
    }
  });
  
  const startResults = await Promise.all(startPromises);
  
  // Step 2: Monitor all successful starts until completion
  const monitorPromises = startResults
    .filter(result => result.success && result.threadId)
    .map(async (result) => {
      try {
        const finalResult = await monitorExecution(result.threadId!);
        return {
          name: result.name,
          threadId: result.threadId,
          ...finalResult,
        };
      } catch (error) {
        const errorMessage = error instanceof Error ? error.message : 'Unknown error';
        return {
          name: result.name,
          threadId: result.threadId,
          status: 'error' as const,
          error: errorMessage,
        };
      }
    });
  
  const finalResults = await Promise.all(monitorPromises);
  
  // Log results
  console.log('\n=== Execution Results ===');
  finalResults.forEach(result => {
    console.log(`${result.name}: ${result.status}`);
    if (result.result) {
      console.log(`  Result: ${result.result.substring(0, 100)}...`);
    }
    if (result.error) {
      console.log(`  Error: ${result.error}`);
    }
  });
  
  return finalResults;
}
```

### Error Handling and Retry Logic

Implement comprehensive error handling for production:

```typescript
async function executeAOPWithRetry(
  assetId: string,
  userInputs: Record<string, string>,
  maxRetries: number = 3
): Promise<ExecutionResponse> {
  let lastError: Error | null = null;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      console.log(`🔄 Attempt ${attempt}/${maxRetries} for AOP execution`);
      
      const result = await executeAOPAsync(assetId, userInputs);
      
      if (result.success) {
        console.log(`✅ AOP succeeded on attempt ${attempt}`);
        return result;
      } else {
        throw new Error(result.error || 'AOP execution failed');
      }
    } catch (error) {
      lastError = error as Error;
      
      // Don't retry on client errors (4xx)
      if (error instanceof AthenaIntelligenceError && 
          error.statusCode >= 400 && error.statusCode < 500) {
        console.error(`❌ Client error (${error.statusCode}): ${error.message}`);
        throw error;
      }
      
      // Retry on server errors or network issues
      if (attempt < maxRetries) {
        const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
        console.log(`⏳ Waiting ${delay}ms before retry...`);
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }
  
  console.error('❌ AOP failed after all retry attempts');
  throw lastError;
}
```

### Type-Safe Options Interface

Define comprehensive options for AOP execution:

```typescript
interface AOPExecutionOptions {
  maxAttempts?: number;
  pollInterval?: number;
  onProgress?: (status: ProgressUpdate) => void;
  retries?: number;
  timeout?: number;
}

// Type-safe AOP executor
async function executeAOPTyped(
  assetId: string,
  userInputs: Record<string, string> = {},
  options: AOPExecutionOptions = {}
): Promise<AOPExecutionResult> {
  const {
    maxAttempts = 60,
    pollInterval = 5000,
    onProgress,
    timeout = 600000, // 10 minutes
  } = options;
  
  try {
    // Start execution (returns immediately with thread_id)
    const asyncResponse = await client.aop.executeAsync({
      asset_id: assetId,
      user_inputs: userInputs,
    });
    
    console.log('🚀 AOP initiated with thread:', asyncResponse.thread_id);
    
    // Monitor with timeout
    const result = await Promise.race([
      monitorExecutionTyped(asyncResponse.thread_id, maxAttempts, pollInterval, onProgress),
      createTimeoutPromise(timeout, asyncResponse.thread_id),
    ]);
    
    return result;
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : 'Unknown error';
    return {
      status: 'error',
      threadId: 'unknown',
      error: errorMessage,
    };
  }
}

async function monitorExecutionTyped(
  threadId: string,
  maxAttempts: number,
  pollInterval: number,
  onProgress?: (status: ProgressUpdate) => void
): Promise<AOPExecutionResult> {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      const status = await client.threads.getStatus(threadId);
      
      if (onProgress) {
        onProgress({
          attempt,
          maxAttempts,
          status: status.status,
          threadId: status.thread_id,
          updatedAt: status.updated_at,
        });
      }
      
      if (status.status === 'completed') {
        return {
          status: 'completed',
          threadId,
          result: getConversationAssetText(status.conversation_asset) || 'No result available',
          conversationAssetId: status.conversation_asset?.conversation_asset_id,
          messageCount: status.conversation_asset?.num_messages,
          threadStatus: status,
        };
      } else if (status.status === 'failed') {
        return {
          status: 'failed',
          threadId,
          error: 'Thread execution failed',
          threadStatus: status,
        };
      }
      
      await new Promise(resolve => setTimeout(resolve, pollInterval));
    } catch (error) {
      if (attempt === maxAttempts) {
        const errorMessage = error instanceof Error ? error.message : 'Unknown error';
        return {
          status: 'error',
          threadId,
          error: `Monitoring failed: ${errorMessage}`,
        };
      }
    }
  }
  
  return {
    status: 'timeout',
    threadId,
    error: 'Monitoring timeout reached',
  };
}

function createTimeoutPromise(timeoutMs: number, threadId: string): Promise<AOPExecutionResult> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({
        status: 'timeout',
        threadId,
        error: `Execution timeout after ${timeoutMs}ms`,
      });
    }, timeoutMs);
  });
}
```

### Complete Production Example

Here's a complete production-ready class with full type safety:

```typescript
import type { AthenaIntelligence } from '@athenaintel/sdk';
import { AthenaIntelligenceClient, AthenaIntelligenceError } from '@athenaintel/sdk';

class AOPExecutor {
  private client: AthenaIntelligenceClient;
  
  constructor(apiKey: string, baseUrl?: string) {
    this.client = new AthenaIntelligenceClient({
      apiKey,
      baseUrl,
    });
  }
  
  async executeAOP(
    assetId: string,
    userInputs: Record<string, string> = {},
    options: AOPExecutionOptions = {}
  ): Promise<AOPExecutionResult> {
    const { timeout = 600000, onProgress, retries = 3 } = options;
    
    let attempt = 0;
    let lastError: Error | undefined;
    
    while (attempt < retries) {
      try {
        console.log(`🚀 Starting AOP execution (attempt ${attempt + 1}/${retries})`);
        
        // Start async execution (returns immediately with thread_id)
        const startResponse = await this.client.aop.executeAsync({
          asset_id: assetId,
          user_inputs: userInputs,
        });
        
        console.log(`✅ AOP initiated: ${startResponse.aop_title}`);
        console.log(`📍 Thread ID: ${startResponse.thread_id}`);
        
        // Monitor with timeout
        const result = await Promise.race([
          this.monitorExecution(startResponse.thread_id, onProgress),
          this.createTimeoutPromise(timeout, startResponse.thread_id),
        ]);
        
        if (result.status === 'completed') {
          console.log('🎉 Thread execution finished successfully');
        }
        
        return result;
      } catch (error) {
        attempt++;
        lastError = error as Error;
        console.error(`❌ Attempt ${attempt} failed:`, lastError.message);
        
        if (attempt >= retries) {
          break;
        }
        
        // Exponential backoff
        const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
    
    // All retries failed
    return {
      status: 'error',
      threadId: 'unknown',
      error: lastError?.message || 'AOP execution failed after all retries',
    };
  }
  
  private async monitorExecution(
    threadId: string, 
    onProgress?: (update: ProgressUpdate) => void
  ): Promise<AOPExecutionResult> {
    const maxAttempts = 120; // Allow up to 10 minutes
    const pollInterval = 5000;
    
    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
      const status = await this.client.threads.getStatus(threadId);
      
      const update: ProgressUpdate = {
        attempt,
        maxAttempts,
        status: status.status,
        threadId: status.thread_id,
        updatedAt: status.updated_at,
      };
      
      if (onProgress) {
        onProgress(update);
      }
      
      console.log(`[${attempt}/${maxAttempts}] Status: ${status.status}`);
      
      if (status.status === 'completed') {
        const result = getConversationAssetText(status.conversation_asset);
        return {
          status: 'completed',
          threadId,
          result: result || 'Thread finished successfully',
          conversationAssetId: status.conversation_asset?.conversation_asset_id,
          messageCount: status.conversation_asset?.num_messages,
          threadStatus: status,
        };
      } else if (status.status === 'failed') {
        return {
          status: 'failed',
          threadId,
          error: 'Thread execution failed',
          threadStatus: status,
        };
      }
      
      await new Promise(resolve => setTimeout(resolve, pollInterval));
    }
    
    return {
      status: 'timeout',
      threadId,
      error: 'Maximum polling attempts reached',
    };
  }
  
  private createTimeoutPromise(timeoutMs: number, threadId: string): Promise<AOPExecutionResult> {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve({
          status: 'timeout',
          threadId,
          error: `Execution timeout after ${timeoutMs}ms`,
        });
      }, timeoutMs);
    });
  }
}

// Usage example with full type safety
async function main() {
  try {
    const executor = new AOPExecutor(process.env.ATHENA_API_KEY!);
    
    const result = await executor.executeAOP(
      'asset_comprehensive_analysis',
      {
        company: 'OpenAI',
        analysis_type: 'comprehensive',
        time_period: '2024',
        include_forecasts: 'true',
      },
      {
        timeout: 600000, // 10 minutes
        onProgress: (update: ProgressUpdate) => {
          console.log(`📊 Progress: ${update.status} (${update.attempt}/${update.maxAttempts})`);
        },
        retries: 3,
      }
    );
    
    if (result.status === 'completed') {
      console.log('🎉 Final result:', result.result);
    } else {
      console.error('❌ Execution did not complete:', result.error);
    }
  } catch (error) {
    console.error('💥 Execution failed:', error instanceof Error ? error.message : 'Unknown error');
  }
}

main();
```

### Key Recommendations

1. **Always use `executeAsync`** - It returns immediately with a thread_id for tracking
2. **Poll with `threads.getStatus`** - The thread status shows actual completion
3. **Never use `any` types** - Use proper TypeScript interfaces for type safety
4. **Implement proper error handling** - Handle network failures, timeouts, and API errors
5. **Use retry logic** - Implement exponential backoff for resilient execution
6. **Monitor progress** - Use callbacks to provide real-time updates
7. **Set appropriate timeouts** - Based on expected AOP complexity

**Important**: `executeAsync` only **starts** execution and returns a thread_id. You must poll `threads.getStatus` to monitor actual completion. The execution happens asynchronously in the background.