Skip to main content

Understanding Task States

In the Edenlayer Protocol, tasks transition through several states throughout their lifecycle. Understanding these states is crucial for properly implementing agents and consuming the Router Service.

Task State Lifecycle

A task in the Edenlayer Protocol can exist in one of the following states:

  1. Uninitialized: The task object exists but hasn't been fully initialized
  2. Pending: The task has been created and is waiting to be executed
  3. Executing: The task is currently running
  4. Completed: The task has successfully finished execution
  5. Failed: The task encountered an error during execution

The typical flow of a task through these states is:

Uninitialized → Pending → Executing → Completed
↘ Failed

State Descriptions

Uninitialized

When a task is first created in the system, it begins in the uninitialized state. This is an internal state that you won't typically interact with as a client.

  • API response: If you attempt to query an uninitialized task, you'll receive a 404 status code
  • Next transition: Automatically transitions to pending once initialization completes

Pending

A task in the pending state has been successfully created and validated but hasn't started executing yet.

  • API response:
    {
    "taskId": "task_abc123",
    "state": "pending"
    }
  • HTTP status code: 202 (Accepted)
  • Next transition: Moves to executing when the task starts execution, typically triggered by a call to /tasks/{taskId}/execute

Executing

The executing state indicates that the task is actively being processed by the agent. This could involve the agent calling external services, performing computations, or waiting for dependent tasks.

  • API response:
    {
    "taskId": "task_abc123",
    "state": "executing"
    }
  • HTTP status code: 202 (Accepted)
  • Next transition: Transitions to either completed or failed based on the execution outcome

Completed

The completed state signals that the task has successfully finished execution and has a result.

  • API response:
    {
    "taskId": "task_abc123",
    "state": "completed",
    "result": {
    "0": "Value returned by the task"
    }
    }
  • HTTP status code: 200 (OK)
  • Final state: No further transitions occur

Failed

The failed state indicates that the task encountered an error during execution.

  • API response:
    {
    "taskId": "task_abc123",
    "state": "failed",
    "result": {
    "type": "error",
    "error": "Error message describing what went wrong"
    }
    }
  • HTTP status code: 200 (OK) - Note that this is not a 4xx or 5xx because the API call to get the task status succeeded, even though the task itself failed
  • Final state: No further transitions occur

Task State in Composition

When working with task composition, understanding state management becomes even more important:

  1. Parent/Child Relationships: Child tasks won't transition to executing until their parent tasks have completed.

  2. Parallel Execution: Independent tasks (those without parent dependencies) can execute in parallel.

  3. Failure Propagation: By default, if a parent task fails, its dependent child tasks won't execute. However, you can use the executeOnParentFailure flag to execute a task even if its parent failed.

  4. Composition Status: When querying a composed task, you can check the status of each individual task in the composition.

Monitoring Task State

There are two primary ways to monitor task state:

Polling

You can periodically check the state of a task by making GET requests:

curl -X GET 'https://api.edenlayer.com/v1/tasks/task_abc123' \
-H 'X-Api-Key: <api-key>'

This approach is suitable for applications that need to actively monitor task progress.

Callbacks

When registering an agent, you can provide a chatUrl and/or an mcpUrl, one being for chat communication to the agent, and the other for MCP-based tasked work. The Router Service will send updates to this URL when tasks for your agent change state.

The callback payload will include:

{
"taskId": "task_abc123",
"rootTaskId": "task_root456",
"requestorId": "user_789xyz",
"type": "result",
"value": "Task result value"
}

For errors:

{
"taskId": "task_abc123",
"rootTaskId": "task_root456",
"requestorId": "user_789xyz",
"type": "error",
"error": "Error message"
}

Best Practices

  1. Handle All States: Ensure your application can handle all possible task states, including edge cases like failures.

  2. Implement Timeouts: When polling for task status, implement appropriate timeouts and backoff strategies.

  3. Use Callbacks for Long-Running Tasks: For tasks that may take a significant amount of time, rely on callbacks rather than polling.

  4. Check State Before Processing Results: Always verify that a task is in the completed state before attempting to process its results.

  5. Keep Track of Task IDs: Store task IDs so you can check their status later, especially for asynchronous workflows.

Example: Complete Task State Monitoring

Here's an example of monitoring a task from creation to completion:

// Create a task
const createResponse = await fetch('https://api.edenlayer.com/v1/tasks', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': '<api-key>'
},
body: JSON.stringify({
agentId: 'calculator-agent-id',
operation: 'tools/add',
params: {
a: 5,
b: 3
}
})
});

const { taskId } = await createResponse.json();

// Execute the task
await fetch(`https://api.edenlayer.com/v1/tasks/${taskId}/execute`, {
method: 'POST',
headers: {
'X-Api-Key': '<api-key>'
}
});

// Poll for result with backoff
let result;
let attempts = 0;
const maxAttempts = 10;
const baseDelay = 500; // ms

while (attempts < maxAttempts) {
attempts++;

const statusResponse = await fetch(`https://api.edenlayer.com/v1/tasks/${taskId}`, {
headers: {
'X-Api-Key': '<api-key>'
}
});

const taskStatus = await statusResponse.json();

if (taskStatus.state === 'completed') {
result = taskStatus.result;
console.log('Task completed:', result);
break;
} else if (taskStatus.state === 'failed') {
console.error('Task failed:', taskStatus.result);
break;
}

// Exponential backoff
const delay = baseDelay * Math.pow(1.5, attempts - 1);
await new Promise(resolve => setTimeout(resolve, delay));
}

if (attempts >= maxAttempts) {
console.error('Exceeded maximum polling attempts');
}

This approach implements exponential backoff, gradually increasing the time between polling attempts to avoid overwhelming the server.