Language:English VersionChinese Version

Software is entering the age of agents. After years of AI serving as a sophisticated autocomplete — suggesting code, summarizing text, answering questions — we are witnessing a fundamental shift toward systems that can plan, execute, and adapt autonomously. Agentic workflows, where AI agents orchestrate complex multi-step tasks with minimal human intervention, represent the next frontier of applied artificial intelligence.

But building reliable multi-agent systems is dramatically harder than building a chatbot. The challenges of orchestration, error handling, state management, and trust compound in ways that demand new architectural thinking. This article explores the patterns, frameworks, and hard-won lessons emerging from teams building agentic systems in production.

What Makes a Workflow Agentic

The term “agentic” gets thrown around loosely, so precision matters. An agentic workflow has several defining characteristics that distinguish it from traditional automation or simple AI integration:

  • Goal-directed behavior: The agent pursues a high-level objective rather than executing a fixed script
  • Tool use: The agent can invoke external tools, APIs, and services to accomplish its goals
  • Planning and reasoning: The agent decomposes complex tasks into subtasks and determines execution order
  • Adaptation: The agent adjusts its approach based on intermediate results and errors
  • Memory: The agent maintains context across steps and potentially across sessions

A simple chatbot that answers questions is not agentic. A system that receives a bug report, searches the codebase for relevant files, generates a fix, writes tests, opens a pull request, and responds to review comments — that is agentic.

Single-Agent vs. Multi-Agent Architectures

The first architectural decision is whether to use a single agent or multiple cooperating agents. Each approach has distinct tradeoffs.

Single-Agent Systems

A single-agent system uses one LLM instance with access to multiple tools. It plans and executes all steps in a unified loop. This is simpler to build, debug, and reason about. For most use cases, a well-designed single agent outperforms a multi-agent system.

# Single-agent pattern with tool use
from anthropic import Anthropic

client = Anthropic()

tools = [
    {"name": "search_codebase", "description": "Search for code patterns"},
    {"name": "read_file", "description": "Read a file from the repository"},
    {"name": "write_file", "description": "Write content to a file"},
    {"name": "run_tests", "description": "Execute the test suite"},
    {"name": "create_pr", "description": "Create a pull request"},
]

def agent_loop(task: str):
    messages = [{"role": "user", "content": task}]

    while True:
        response = client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=4096,
            tools=tools,
            messages=messages,
        )

        if response.stop_reason == "end_turn":
            return extract_text(response)

        # Process tool calls
        tool_results = []
        for block in response.content:
            if block.type == "tool_use":
                result = execute_tool(block.name, block.input)
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": result,
                })

        messages.append({"role": "assistant", "content": response.content})
        messages.append({"role": "user", "content": tool_results})

Multi-Agent Systems

Multi-agent systems use multiple specialized agents that collaborate on a task. Each agent has a focused role — one might be a researcher, another a coder, another a reviewer. Multi-agent architectures make sense when the task genuinely requires different expertise domains or when you need separation of concerns for safety reasons.

The overhead of multi-agent coordination — message passing, shared state, conflict resolution — is substantial. Do not reach for multi-agent architectures until you have evidence that a single agent cannot handle the task effectively.

Orchestration Patterns

How agents coordinate their work is the central design challenge. Three primary orchestration patterns have emerged.

Sequential (Pipeline)

Agents execute in a fixed order, each receiving the output of the previous agent. This is the simplest pattern and works well when the task has a natural linear flow.

# Sequential orchestration
async def sequential_pipeline(task):
    # Step 1: Research agent gathers context
    context = await research_agent.run(task)

    # Step 2: Planning agent creates implementation plan
    plan = await planning_agent.run(task, context)

    # Step 3: Coding agent implements the plan
    code = await coding_agent.run(plan)

    # Step 4: Review agent validates the output
    review = await review_agent.run(code, plan)

    if review.needs_revision:
        code = await coding_agent.run(review.feedback)

    return code

Parallel (Fan-Out/Fan-In)

Multiple agents work simultaneously on independent subtasks, and their results are aggregated. This is ideal when a task can be decomposed into independent parts — for example, researching multiple topics simultaneously or testing against multiple environments.

# Parallel orchestration
async def parallel_research(topics: list[str]):
    # Fan out: multiple agents research simultaneously
    tasks = [research_agent.run(topic) for topic in topics]
    results = await asyncio.gather(*tasks)

    # Fan in: synthesis agent combines findings
    synthesis = await synthesis_agent.run(results)
    return synthesis

Hierarchical (Manager/Worker)

A manager agent decomposes the task, delegates subtasks to worker agents, evaluates their output, and decides next steps. This is the most flexible pattern but also the most complex. The manager agent needs strong planning capabilities and the judgment to know when worker output is satisfactory.

# Hierarchical orchestration
async def hierarchical_workflow(objective: str):
    manager = ManagerAgent(objective)

    while not manager.is_complete():
        # Manager decides next subtask and assigns it
        assignment = await manager.plan_next_step()

        # Delegate to appropriate worker
        worker = get_worker(assignment.agent_type)
        result = await worker.execute(assignment.task)

        # Manager evaluates and decides next action
        await manager.evaluate_result(result)

    return manager.get_final_output()

Tool Use and Function Calling

Tools are what transform a language model from a text generator into an agent. The design of your tool interface is one of the most consequential decisions in building an agentic system.

Principles of Good Tool Design

Atomic operations: Each tool should do one thing well. A tool that reads a file should not also parse it. Composability comes from combining atomic tools, not from building Swiss Army knives.

Clear contracts: Tool descriptions should be precise about inputs, outputs, and side effects. Ambiguous tool descriptions lead to misuse and errors.

Idempotency where possible: Tools that can be safely retried simplify error handling enormously. When a tool call fails mid-execution, the agent needs to know whether retrying is safe.

Bounded scope: Tools should have guardrails. A file-writing tool should be restricted to specific directories. A database tool should only have access to specific tables. Principle of least privilege applies to agents just as it does to human users.

# Well-designed tool definitions
tools = [
    {
        "name": "query_database",
        "description": "Execute a read-only SQL query against the analytics database. Only SELECT statements are permitted. Results are limited to 1000 rows.",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "SQL SELECT query to execute"
                },
                "timeout_seconds": {
                    "type": "integer",
                    "default": 30,
                    "description": "Query timeout in seconds (max 60)"
                }
            },
            "required": ["query"]
        }
    }
]

Error Handling and Recovery

Error handling is where agentic systems are won or lost. Unlike traditional software where errors follow predictable patterns, agentic systems face a combinatorial explosion of failure modes: tool failures, malformed outputs, hallucinated actions, infinite loops, context window exhaustion, and more.

Essential Error Handling Patterns

Retry with backoff: Transient failures — network timeouts, rate limits, temporary service unavailability — should be retried automatically. Exponential backoff prevents cascade failures.

Fallback strategies: When a primary approach fails, the agent should have alternative strategies. If a code search tool returns no results, the agent might try a broader search or read the directory structure to find relevant files manually.

Circuit breakers: After repeated failures of the same type, the agent should stop retrying and either escalate to a human or try a fundamentally different approach. An agent that retries the same failing API call indefinitely is worse than useless.

Context management: As agents work through complex tasks, they accumulate context. Without active management, they will exhaust their context window. Effective agents summarize intermediate results and prune irrelevant context.

# Error handling with circuit breaker pattern
class AgentExecutor:
    def __init__(self, max_retries=3, max_consecutive_failures=5):
        self.consecutive_failures = 0
        self.max_consecutive_failures = max_consecutive_failures

    async def execute_with_recovery(self, agent, task):
        for attempt in range(self.max_retries):
            try:
                result = await agent.run(task)
                self.consecutive_failures = 0
                return result
            except ToolExecutionError as e:
                self.consecutive_failures += 1
                if self.consecutive_failures >= self.max_consecutive_failures:
                    raise CircuitBreakerOpen(
                        f"Too many consecutive failures: {e}"
                    )
                await asyncio.sleep(2 ** attempt)  # Exponential backoff
            except ContextOverflowError:
                # Summarize and compress context, then retry
                agent.compress_context()
                continue

        raise MaxRetriesExceeded(f"Failed after {self.max_retries} attempts")

Frameworks for Building Agentic Systems

Several frameworks have emerged to simplify the development of agentic workflows. Each makes different tradeoffs.

LangGraph

LangGraph, from the LangChain team, models agentic workflows as directed graphs. Nodes represent processing steps (LLM calls, tool executions, conditional logic), and edges define the flow between them. LangGraph excels at complex workflows with branching, looping, and human-in-the-loop interactions. Its state management is excellent, and it provides built-in support for persistence and streaming.

CrewAI

CrewAI focuses specifically on multi-agent collaboration. You define agents with specific roles, backstories, and goals, then assemble them into crews that work together on tasks. CrewAI handles the coordination and communication between agents. It is particularly good for workflows that map naturally to team metaphors — a research team, a content creation team, a code review team.

Anthropic Agent SDK

The Anthropic Agent SDK takes a deliberately minimal approach. Rather than providing a heavy framework, it offers lightweight primitives for building agents: tool registration, agent loops, handoffs between agents, and guardrails. The philosophy is that most of the complexity in agentic systems is domain-specific, and a framework should provide building blocks rather than opinions.

# Anthropic Agent SDK example
from agents import Agent, Runner, function_tool

@function_tool
def search_web(query: str) -> str:
    """Search the web for information."""
    return web_search_api(query)

@function_tool
def write_report(title: str, content: str) -> str:
    """Write a structured report."""
    return save_report(title, content)

researcher = Agent(
    name="Researcher",
    instructions="You research topics thoroughly using web search.",
    tools=[search_web],
)

writer = Agent(
    name="Writer",
    instructions="You write clear, well-structured reports.",
    tools=[write_report],
    handoffs=[researcher],  # Can delegate back to researcher
)

result = Runner.run(writer, "Write a report on WASM adoption in 2026")

Production Considerations

Moving agentic systems from prototype to production surfaces challenges that are easy to underestimate.

Observability

You cannot operate what you cannot observe. Every agent action — every LLM call, tool invocation, decision point, and error — needs to be logged and traceable. Without comprehensive observability, debugging production issues in multi-step agentic workflows is nearly impossible. Tools like LangSmith, Braintrust, and Arize Phoenix provide specialized observability for agentic systems.

Cost Management

Agentic workflows can be expensive. A single task might involve dozens of LLM calls, each consuming tokens. Cost scales with complexity, and runaway agents can burn through API budgets quickly. Implement hard limits on iterations, token consumption, and tool calls per task.

Latency

Each step in an agentic workflow adds latency. A five-step agent loop with two-second LLM calls and one-second tool executions takes at minimum 15 seconds. For user-facing applications, this latency is often unacceptable. Strategies include parallelizing independent steps, caching common tool results, and using faster models for simple decisions.

Safety and Guardrails

Agents that can take real-world actions need guardrails. At minimum, implement approval gates for high-impact actions (sending emails, making purchases, modifying production data), output validation to catch hallucinated or malformed actions, rate limiting on tool calls, and sandboxing for code execution. The most dangerous failure mode is an agent that confidently takes the wrong action at scale.

When Agentic Workflows Make Sense

Not every problem needs an agentic solution. Agentic workflows add complexity, cost, latency, and unpredictability. They make sense when the task genuinely requires multi-step reasoning with intermediate decisions, when the workflow cannot be fully specified in advance, when the task requires interacting with multiple external systems, and when human-level judgment (not just pattern matching) is needed at decision points.

If a task can be solved with a single LLM call, a simple chain, or traditional automation, those simpler approaches are almost always preferable.

Looking Forward

The agentic paradigm is still in its early days. The frameworks are maturing rapidly, the models are getting better at planning and tool use, and the engineering patterns for reliability are solidifying. But we are far from the point where you can hand an agent a vague objective and expect reliable results.

The teams building the most successful agentic systems share a common trait: they are deeply pragmatic. They start with narrow, well-defined tasks. They invest heavily in observability and testing. They build trust incrementally, expanding agent authority only as reliability is demonstrated. And they never forget that the agent is a tool in service of a human goal — not an end in itself.

The rise of agentic workflows is not about replacing human judgment. It is about extending it — giving engineers and organizations the ability to automate complex cognitive tasks that were previously impossible to delegate to software. The organizations that learn to build reliable agentic systems will have a significant competitive advantage. The key word is reliable.

By Michael Sun

Founder and Editor-in-Chief of NovVista. Software engineer with hands-on experience in cloud infrastructure, full-stack development, and DevOps. Writes about AI tools, developer workflows, server architecture, and the practical side of technology. Based in China.

Leave a Reply

Your email address will not be published. Required fields are marked *