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
| Driver | What it does | Entry point | Communication |
|---|---|---|---|
| TaskTurnWorker | Executes structured tasks with subtasks | Quartz scheduler or manual trigger | Synchronous (request/response) |
| Idle Task Driver | Lets agents self-organize during downtime | Quartz cron job | Synchronous (hidden task) |
| AgentTurnWorker | Handles real-time conversation messages | RabbitMQ consumer | Streaming 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:
- The task description and overall goal.
- The current subtask (only one at a time --- not the full list).
- Results from any tool calls in previous turns.
- 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:
- After each turn, the worker examines the agent's response for signals that the current subtask is complete.
- If complete, the subtask status is updated and the next subtask becomes the "current" one.
- The next turn's prompt includes only the new current subtask.
- When all subtasks are complete, the task itself is marked Done.
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:
- A Quartz cron job periodically scans for agents with no active tasks.
- For each idle agent, the system creates a hidden IDLE task (not visible to users).
- The agent processes the idle task like any other, but the prompt asks it to review its state and self-organize.
- The IDLE task is automatically cleaned up after processing.
Exponential backoff prevents idle agents from burning through LLM credits:
| Time since idle | Check delay |
|---|---|
| Just became idle | Immediate (0 min) |
| 15 minutes idle | 15 min between checks |
| 30 minutes idle | 30 min between checks |
| 60+ minutes idle | 60 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:
| Aspect | TaskTurnWorker | AgentTurnWorker |
|---|---|---|
| Turns | Up to 10 per task | The 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. |
| Communication | Synchronous request/response | Streaming SSE to the browser |
| Retries | NACK/requeue for tool calls | Max 3 retries for empty responses |
| Entry point | Quartz scheduler | RabbitMQ message consumer |
The conversation driver also detects special tool calls:
send_messageandsend_email: routed to the platform API for delivery.__XAIMETA__withcontinue_as_task: converts the conversation interaction into a background task, allowing the agent to do long-running work without holding the conversation open.
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.
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:
-
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.
-
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. -
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.
-
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
- Task Statuses -- reference for all task status values and transitions
- Track Task Progress -- how to monitor and manage task execution
- Agent Messaging System -- the NACK/requeue mechanism that drives the tool-call loop