Zenaique

How do LlamaIndex Workflow-based agents add structure over a bare ReAct loop?

Flashcard·Easy·4.0 · 0·~30s·Asked atBanana DevSnowflake
Attempt it
TL;DR

AgentWorker decided one step. AgentRunner owned the Task and the loop, enabling pause, inspect and review. Both were removed in 0.13.0. Workflow-based agents keep the same split.

Memory aid
Sign in to see the mnemonic that makes this stick.
Easy to grasp

Imagine a chess engine and a chess clock. The engine knows how to pick the next move given the current board. The clock knows whose turn it is, when to pause, and how to resume a game from a saved position. If you smash both into one program, you have a chess bot that always plays start to finish in one go. You cannot stop it mid-game, you cannot review the moves before the next one, and you cannot swap in a different engine without rewriting the clock. LlamaIndex first split its agents this way: AgentWorker was the engine, and AgentRunner was the clock plus the saved-game folder. Its current agents keep the same idea. The agent picks each move, and a separate workflow runtime runs the game and can save it.

Concept explanation~2 min read

Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.

Early agent frameworks shipped one abstraction: an agent class with a run(query) method that internally looped until done. That abstraction worked for demos and failed for production. You could not pause it, inspect it mid-run, or resume after a crash.

LlamaIndex's first response was the runner/worker split: AgentRunner orchestrated Tasks, and AgentWorker decided one step. LlamaIndex has since removed both classes (in llama-index-core 0.13.0) and built its agents on Workflows, which keep the same split. The same response shows up across mature agent runtimes: LangGraph splits executor and node, OpenAI Agents SDK separates the loop and handoffs, Mastra typed workflows split runtime and step handlers.

AgentWorker. The per-step decision

An AgentWorker had one job. Given current state and message history, return the next action: think, call tool, or finalise. Built-in implementations included ReActAgentWorker (reasoning then action loop) and FunctionCallingAgentWorker (native tool calls on supported models).

The key surface was run_step(step, task): advance the task by exactly one decision. The worker did not loop. The runner decided when to call it again.

This narrowness was the point. A worker mapped state in to a decision out, with no role in the rest of the loop. That made workers swappable and testable in isolation. In current LlamaIndex, the agent class (FunctionAgent, ReActAgent) plays this role inside a Workflow.

AgentRunner. The Task orchestrator
Four capabilities the split unlocks
Parallel with LangGraph and other runtimes
LlamaIndex Workflows: the current form
Sign in to unlock the full deep dive.

Situations where this technique stops working.

Sign in to see when this approach fails.

2–4 min · Everything important, quickly.

Sign in to see the quick scan of the deep dive.
python
import asyncio

from llama_index.core.agent.workflow import FunctionAgent, ToolCallResult
from llama_index.core.workflow import Context, JsonSerializer
from llama_index.llms.openai import OpenAI


def search(query: str) -> str:
    """Search internal notes for a query."""
    return f"Notes about {query}"


agent = FunctionAgent(tools=[search], llm=OpenAI(model="gpt-5.5"))


async def main():
    # The Workflow runtime drives the steps; the agent decides each one
    ctx = Context(agent)
    handler = agent.run("Research X, draft a one-page brief.", ctx=ctx)

    # Inspect each tool step as it happens
    async for ev in handler.stream_events():
        if isinstance(ev, ToolCallResult):
            print("tool:", ev.tool_name, "->", ev.tool_output)

    response = await handler
    print(response)

    # Save the run state; restore it later, even in another process
    ctx_dict = ctx.to_dict(serializer=JsonSerializer())
    restored = Context.from_dict(agent, ctx_dict, serializer=JsonSerializer())
    followup = await agent.run("Now shorten it to 200 words.", ctx=restored)
    print(followup)


asyncio.run(main())

Real products, models, and research that use this idea.

  • LlamaIndex's agent docs build FunctionAgent and ReActAgent on Workflows, with the runtime driving steps and the agent deciding each one
  • Document-QA agents built on FunctionAgent add human-approval gates with InputRequiredEvent and HumanResponseEvent
Sign in to see more production examples.

What an interviewer would ask next. Try answering before peeking at the approach.

QHow does LlamaIndex Workflows generalise the runner/worker pattern?
A

Walk through the event-driven Step decorator model, typed StartEvent/StopEvent flow, and how Steps replace AgentWorker as the per-decision unit while the Workflow runtime replaces AgentRunner.

2 more follow-ups an interviewer would ask next. Sign in to reveal them.

Red flags & common mistakes

The phrases that signal junior thinking. Click to expand.

Most common mistake

Writing new code against AgentRunner or AgentWorker. Both were removed. Workflow-based agents such as FunctionAgent now provide the stepped loop with inspectable, saveable state.

Sign in to see all red flags and common mistakes.

60 second bullets to scan on the way to the call.

  • Role of AgentWorker. Per-step decision

  • Role of AgentRunner. Task orchestration and state between steps

Sign in to unlock the revision sheet.

Primary sources. Browse if you want the original framing.

Similar questions

Same topic, related formats. Practice these next.

4 curated
Next question
When is LlamaParse the right ingestion tool?
MCQ·Medium