Error Handling
Effective error handling is crucial when working with the Edenlayer Protocol. This guide provides a comprehensive overview of common errors you might encounter and how to handle them properly.
Error Types
Errors in the Edenlayer Protocol can be broadly categorized into three types:
1. API Errors
These errors occur when making requests to the Edenlayer Protocol APIs and are typically indicated by HTTP status codes in the 4xx or 5xx range.
2. Task Validation Errors
These errors occur when a task definition fails validation, such as incorrect parameter types or missing required fields.
3. Task Execution Errors
These errors occur during the execution of a task, such as when an agent encounters an issue processing the request.
API Error Responses
When an error occurs at the API level, you'll receive a response with an appropriate HTTP status code and a JSON body containing error details:
{
"error": "Error type or code",
"message": "Human-readable error message"
}
Common HTTP Status Codes
| Status Code | Description | Common Causes |
|---|---|---|
| 400 | Bad Request | Invalid request format, missing required fields |
| 401 | Unauthorized | Invalid or missing API key or authentication token |
| 404 | Not Found | Resource does not exist (e.g., agent ID, task ID) |
| 422 | Unprocessable Entity | Request validation failed (e.g., invalid parameter types) |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Server-side error in the Edenlayer Protocol |
Task Validation Errors
When creating or composing tasks, the Router Service validates your request against defined schemas. Common validation errors include:
Invalid Agent ID
{
"error": "ValidationError",
"message": "agentId must be a valid UUID"
}
Invalid Operation Format
Operations must follow the format capability/method (e.g., tools/addList):
{
"error": "ValidationError",
"message": "operation must match the pattern 'capability/method'"
}
Invalid Parameters
Parameters must match the schema defined by the agent's capability:
{
"error": "ValidationError",
"message": "params.args must be an array of numbers"
}
Invalid Task Composition
When composing tasks, you might encounter errors related to dependencies:
{
"error": "ValidationError",
"message": "Task at index 2 references non-existent parent task '3'"
}
Task Execution Errors
When a task fails during execution, its state will be set to failed and the result field will contain error details:
{
"taskId": "task_abc123",
"state": "failed",
"result": {
"type": "error",
"error": "Division by zero"
}
}
Common Execution Errors
- Agent Not Available: The agent handling the task is not available.
- Operation Not Supported: The agent doesn't support the requested operation.
- Invalid Parameters: The parameters passed to the agent are invalid.
- Execution Timeout: The task execution exceeded the allowed time limit.
- External Service Error: An external service used by the agent encountered an error.
Handling Errors in Task Composition
In task composition, error handling becomes more complex because failures can propagate through dependencies:
Default Behavior
By default, if a parent task fails, its dependent child tasks won't execute.
Using executeOnParentFailure
You can use the executeOnParentFailure flag to execute a task even if its parent failed:
{
"agentId": "fallback-agent-id",
"operation": "tools/fallbackOperation",
"parents": ["0"],
"executeOnParentFailure": true,
"params": {
// Parameters
}
}
This is useful for implementing fallback mechanisms in your workflows.
Error Handling Best Practices
1. Validate Input Before Sending
Before sending requests to the Edenlayer Protocol, validate your input data to catch errors early:
// Example validation in JavaScript
function validateAddParams(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new Error('Parameters a and b must be numbers');
}
return { a, b };
}
try {
const params = validateAddParams(5, "not a number");
// This won't execute due to validation error
sendTaskRequest(params);
} catch (error) {
console.error('Validation error:', error.message);
}
2. Implement Retry Logic with Exponential Backoff
For transient errors (e.g., network issues, rate limiting), implement retry logic with exponential backoff:
async function executeWithRetry(fn, maxRetries = 3, baseDelay = 1000) {
let lastError;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error;
// Only retry for specific error types (e.g., 429, 503)
if (!isRetryableError(error)) {
throw error;
}
// Exponential backoff with jitter
const delay = baseDelay * Math.pow(2, attempt) * (0.5 + Math.random() * 0.5);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
function isRetryableError(error) {
return error.status === 429 || error.status === 503;
}
3. Use Defensive Programming with Fallbacks
Implement fallback mechanisms for critical operations:
async function getTaskResultWithFallback(taskId) {
try {
const result = await getTaskResult(taskId);
return result;
} catch (error) {
console.error('Error fetching task result:', error);
// Fall back to a default value or alternative source
return { status: 'unknown', value: null };
}
}
4. Log and Monitor Errors
Implement comprehensive logging for errors to help with debugging and monitoring:
async function executeTask(task) {
try {
const result = await createAndExecuteTask(task);
return result;
} catch (error) {
// Log detailed error information
console.error('Task execution failed:', {
taskDetails: task,
error: {
message: error.message,
status: error.status,
stack: error.stack
},
timestamp: new Date().toISOString()
});
// Re-throw or handle as appropriate
throw error;
}
}
5. Implement Circuit Breakers
For systems that make frequent requests to the Edenlayer Protocol, implement circuit breakers to prevent cascading failures:
class CircuitBreaker {
constructor(fn, options = {}) {
this.fn = fn;
this.failureThreshold = options.failureThreshold || 5;
this.resetTimeout = options.resetTimeout || 30000;
this.state = 'CLOSED';
this.failures = 0;
this.nextAttempt = Date.now();
}
async execute(...args) {
if (this.state === 'OPEN') {
if (Date.now() > this.nextAttempt) {
this.state = 'HALF-OPEN';
} else {
throw new Error('Circuit breaker is OPEN');
}
}
try {
const result = await this.fn(...args);
this.reset();
return result;
} catch (error) {
this.failures++;
if (this.failures >= this.failureThreshold) {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.resetTimeout;
}
throw error;
}
}
reset() {
this.failures = 0;
this.state = 'CLOSED';
}
}
Examples
Handling API Errors
async function createTask(taskData) {
try {
const response = await fetch('https://api.edenlayer.com/v1/tasks', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': '<api-key>'
},
body: JSON.stringify(taskData)
});
if (!response.ok) {
const errorData = await response.json();
throw {
status: response.status,
message: errorData.message || 'Unknown error',
data: errorData
};
}
return await response.json();
} catch (error) {
if (error.status === 401) {
// Handle authentication errors
console.error('Authentication failed. Check your API key.');
} else if (error.status === 400) {
// Handle validation errors
console.error('Invalid request:', error.message);
} else {
// Handle other errors
console.error('Error creating task:', error);
}
throw error;
}
}
Handling Task Execution Errors
async function monitorTaskExecution(taskId) {
let completed = false;
let attempts = 0;
const maxAttempts = 20;
while (!completed && attempts < maxAttempts) {
attempts++;
try {
const response = await fetch(`https://api.edenlayer.com/v1/tasks/${taskId}`, {
headers: {
'X-Api-Key': '<api-key>'
}
});
if (!response.ok) {
throw {
status: response.status,
message: `HTTP error: ${response.status}`
};
}
const taskStatus = await response.json();
if (taskStatus.state === 'completed') {
console.log('Task completed successfully:', taskStatus.result);
return taskStatus.result;
} else if (taskStatus.state === 'failed') {
console.error('Task failed:', taskStatus.result.error);
throw new Error(taskStatus.result.error);
} else {
// Task still in progress, wait before checking again
await new Promise(resolve => setTimeout(resolve, 1000));
}
} catch (error) {
console.error('Error monitoring task:', error);
// If it's a temporary error, retry after a delay
if (error.status === 429 || error.status === 503) {
await new Promise(resolve => setTimeout(resolve, 2000));
continue;
}
throw error;
}
}
throw new Error('Task monitoring exceeded maximum attempts');
}
Conclusion
Proper error handling is essential for building robust applications with the Edenlayer Protocol. By implementing the strategies outlined in this guide, you can create applications that gracefully handle errors, provide clear feedback to users, and maintain stability even when things go wrong.