Skip to main content

Task Execution Lifecycle

Agents on the XpressAI Platform do work through three distinct execution drivers. Each one has its own state machine, its own entry point, and its own rules for when processing stops. Understanding which driver is active --- and why --- is essential for debugging agent behavior, designing reliable tools, and reasoning about task progress.

The Three Drivers

DriverWhat it doesEntry pointCommunication
TaskTurnWorkerExecutes structured tasks with subtasksQuartz scheduler or manual triggerSynchronous (request/response)
Idle Task DriverLets agents self-organize during downtimeQuartz cron jobSynchronous (hidden task)
AgentTurnWorkerHandles real-time conversation messagesRabbitMQ consumerStreaming SSE

Each driver processes agent work differently because each use case has different requirements for latency, reliability, and user visibility.

Driver 1: TaskTurnWorker (Task Execution)

This is the primary work driver. When an agent has a task to complete, the TaskTurnWorker runs a state machine that loops through up to 10 turns, advancing subtasks along the way.

State Machine

How Turns Work

Each turn represents one round-trip to the LLM. The agent sees:

  1. The task description and overall goal.
  2. The current subtask (only one at a time --- not the full list).
  3. Results from any tool calls in previous turns.
  4. The agent's scratch pad (persistent notes).

After the LLM responds, the worker checks whether the current subtask should be marked complete and whether to advance to the next one.

Max 10 turns per task. This is a safety limit. If the agent cannot complete its work in 10 turns, the task moves to a terminal state. This prevents runaway LLM costs from a confused agent looping forever.

Subtask Progression

Tasks can have ordered subtasks. The worker presents them one at a time to keep the agent focused.

The advancement logic:

  1. After each turn, the worker examines the agent's response for signals that the current subtask is complete.
  2. If complete, the subtask status is updated and the next subtask becomes the "current" one.
  3. The next turn's prompt includes only the new current subtask.
  4. When all subtasks are complete, the task itself is marked Done.
Why One Subtask at a Time?

Presenting all subtasks at once causes LLMs to skip ahead, do partial work on multiple items, or lose track of what is already done. Showing one subtask at a time keeps the agent focused and makes progress easy to track.

Driver 2: Idle Task Driver

Agents should not just sit idle between tasks. The Idle Task Driver gives agents periodic opportunities to self-organize: review their scratch pad, create new tasks, consolidate knowledge, or clean up.

How it works:

  1. A Quartz cron job periodically scans for agents with no active tasks.
  2. For each idle agent, the system creates a hidden IDLE task (not visible to users).
  3. The agent processes the idle task like any other, but the prompt asks it to review its state and self-organize.
  4. The IDLE task is automatically cleaned up after processing.

Exponential backoff prevents idle agents from burning through LLM credits:

Time since idleCheck delay
Just became idleImmediate (0 min)
15 minutes idle15 min between checks
30 minutes idle30 min between checks
60+ minutes idle60 min between checks

The longer an agent has been idle, the less frequently it checks in. This balances responsiveness (agents react quickly after finishing work) with cost efficiency (agents that have nothing to do stop burning tokens).

Driver 3: AgentTurnWorker (Conversation)

This is the driver described in Agent Messaging System. It handles real-time user conversations.

Key differences from the TaskTurnWorker:

AspectTaskTurnWorkerAgentTurnWorker
TurnsUp to 10 per taskThe Conversation Driver executes a single task turn per message delivery. Within that turn, the agent may make multiple tool calls via the messaging system's NACK/requeue loop (see Agent Messaging System). Each requeue within the same delivery counts as part of the same turn.
CommunicationSynchronous request/responseStreaming SSE to the browser
RetriesNACK/requeue for tool callsMax 3 retries for empty responses
Entry pointQuartz schedulerRabbitMQ message consumer

The conversation driver also detects special tool calls:

  • send_message and send_email: routed to the platform API for delivery.
  • __XAIMETA__ with continue_as_task: converts the conversation interaction into a background task, allowing the agent to do long-running work without holding the conversation open.
Empty Response Handling

Sometimes an LLM returns an empty response (no text, no tool calls). The AgentTurnWorker retries up to 3 times before giving up. This handles transient API issues without getting stuck in an infinite retry loop.

The Transaction Pattern

All three drivers share a critical constraint: LLM calls are long-running and must not hold database transactions open.

A typical LLM call takes 30 seconds to 10 minutes. Holding a JTA transaction open for that long would lock database rows, exhaust connection pools, and eventually bring down the platform.

The solution is a four-phase transaction pattern:

Phase 1 (Short TX): Load task data from database
Phase 2 (NO TX): Call the LLM (30s - 10min)
Phase 3 (Short TX): Save the LLM response to database
Phase 4 (Short TX): Check status, update task state

Each "Short TX" phase uses QuarkusTransaction.requiringNew() for explicit, short-lived transactions. The LLM call in Phase 2 happens completely outside any transaction boundary.

Why This Matters for Quartz

Quartz with jdbc-tx store manages its own transactions for job execution. If you wrap Quartz scheduler calls in a @Transactional method, you get SQLException: Attempting to commit while taking part in a transaction. The four-phase pattern avoids this by keeping Quartz operations and business transactions completely separate.

Putting It All Together

Here is how the three drivers relate to each other in the lifecycle of an agent:

  1. User sends a message in a conversation. The AgentTurnWorker processes it via RabbitMQ. The agent might respond directly, or it might decide the work needs a structured task.

  2. Agent creates a task (either from a conversation via continue_as_task, or on its own initiative). The TaskTurnWorker picks it up and runs the state machine.

  3. Agent finishes all tasks and becomes idle. The Idle Task Driver kicks in, giving the agent a chance to self-organize, consolidate knowledge, or create follow-up tasks.

  4. Cycle repeats when new messages arrive or new tasks are created.

This three-driver architecture lets agents handle real-time conversations, execute structured work, and maintain themselves --- all with different reliability and latency characteristics appropriate to each mode.

See Also