What Are AI Agents? Moving Beyond Single-Turn Chat

    What Are AI Agents? Moving Beyond Single-Turn Chat

    Agents wrap a model in a loop so it can act, observe, and decide the next move, unlike single-turn chat that only writes text. Autonomy is a dial, not a binary, most products sit lower than their teams think. The article covers cost, latency, and compounding errors across steps.

    default profile

    Shreyash Gurav

    August 29, 2026

    9 min read

    What Are AI Agents? Moving Beyond Single-Turn Chat

    Ask a chatbot "book me the cheapest flight next Tuesday" and it will happily write you an essay about booking flights. Ask an agent the same thing and it checks your calendar, queries three airline APIs, compares prices, holds a seat, and asks before paying. The difference is not a smarter model. It is architecture: agents wrap a model in a loop that lets it act on the world across multiple steps, observe results, and decide what to do next.

    That definition sounds simple because the concept is simple, which is exactly why the hype obscures it. This article pins down what makes software agentic, builds a working agent in about sixty lines, places agents honestly on the spectrum from deterministic pipelines to autonomous swarms, and covers the operational realities (cost, latency, compounding errors) that separate demos from systems people trust.

    Single Turns Versus Loops#

    Ordinary LLM applications are functions: request in, one model call, response out. Deterministic control flow means your code decides every step before runtime. An agent inverts this: the model itself chooses the sequence of actions at runtime, calling tools, inspecting outcomes, and revising plans until it judges the task complete or hits a guardrail.

    Fixed chain versus agent loop

    Three properties follow from that inversion. Control flow becomes dynamic: the number and order of steps is unknown until execution. Behavior becomes emergent rather than authored: nobody wrote "if the API returns 404, try searching by email," the model improvises it from context. And reliability becomes statistical rather than binary: each step has a failure probability, and loops multiply them.

    The Four Components Every Agent Has#

    Strip away frameworks and every agent is the same four parts. A model with tool access provides reasoning and decision-making. Tools are typed capabilities your code exposes, functions that touch databases, APIs, calculators, filesystems. State is whatever persists across iterations of the loop: message history, scratch notes, intermediate results; without it, each turn amnesia-resets the plan. And a termination policy decides when to stop: success detection, iteration caps, token budgets, or a human pulling the plug.

    The termination policy deserves more respect than it gets, because it is the only thing standing between an agent and an infinitely expensive while loop. Production agents always carry hard ceilings regardless of how smart their stopping logic seems.

    Runtime behavior across those components settles into a small, well-defined state machine, and drawing it explicitly keeps implementations honest:

    Agent runtime states

    The explicit Failed state is the part most hobby implementations omit. An agent that exhausts its budget should return a structured failure with partial findings attached, not hang, and not fabricate a confident answer from incomplete evidence.

    A Minimal Agent From Scratch#

    Here is a complete agent using the OpenAI SDK. It answers questions over a small knowledge base by deciding whether to search documents, run calculations, or answer directly:

    import json from openai import OpenAI client = OpenAI() def search_docs(query: str) -> dict: corpus = { "refund": "Refunds process within 5 business days after approval.", "vacation": "Employees may carry over up to 5 vacation days.", } key = next((k for k in corpus if k in query.lower()), None) return {"result": corpus.get(key, "no match")} def calculate(expression: str) -> dict: allowed = set("0123456789+-*/(). ") if not set(expression) <= allowed: return {"error": "expression rejected"} return {"value": eval(expression)} # sandboxed input space TOOLS_SPEC = [ {"type": "function", "function": { "name": "search_docs", "description": "Search internal policy documents.", "parameters": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]}}}, {"type": "function", "function": { "name": "calculate", "description": "Evaluate arithmetic, e.g. refund totals.", "parameters": {"type": "object", "properties": {"expression": {"type": "string"}}, "required": ["expression"]}}}, ] DISPATCH = {"search_docs": search_docs, "calculate": calculate} def run_agent(task: str, max_steps: int = 8) -> str: messages = [{"role": "system", "content": "Use tools as needed, then answer concisely."}, {"role": "user", "content": task}] trace = [] for step in range(max_steps): resp = client.chat.completions.create( model="gpt-4o-mini", tools=TOOLS_SPEC, messages=messages) msg = resp.choices[0].message if not msg.tool_calls: trace.append({"step": step, "event": "final_answer"}) print(json.dumps(trace)) # full audit trail return msg.content or "" messages.append(msg) for tc in msg.tool_calls: args = json.loads(tc.function.arguments) outcome = DISPATCH[tc.function.name](**args) trace.append({"step": step, "tool": tc.function.name, "args": args, "outcome": outcome}) messages.append({"role": "tool", "tool_call_id": tc.id, "content": json.dumps(outcome)}) return "Agent stopped: step limit reached." print(run_agent("An employee carried over 5 days and used 2. " "Per policy, how many remain?"))

    Watch what the loop does with that task: one search_docs call to learn the carry-over rule, one calculate call computing five minus two, then a grounded final answer. Nobody encoded that plan. The model composed it from tool descriptions and the observation stream, and the trace list gives you a complete replay for debugging. Framework-free agents are entirely practical at this scale; adopt orchestration libraries when state machines grow legs, not before.

    The conversation with the world, seen as messages, reads like this:

    Two tool hops inside one agent turn

    For teams that do outgrow hand-rolled loops, LangGraph formalizes exactly this pattern with explicit graph structure:

    from langgraph.graph import StateGraph, END graph = StateGraph(dict) graph.add_node("decide", decide_node) graph.add_node("act", execute_tool_node) graph.add_conditional_edges("decide", route_fn, {"tool": "act", "done": END}) graph.add_edge("act", "decide") app = graph.compile()

    The library earns its keep on conditional branching, checkpointing, and human-in-the-loop interrupts; the concepts above are still everything it does.

    The Autonomy Dial#

    "Agent" is not binary; it is a dial, and most products should live lower than they think:

    Four levels of autonomy

    Level 1 covers extraction, classification, summarization: no agency needed. Level 2 routes requests to deterministic handlers, an agent-shaped front door with none of the runtime unpredictability. Level 3 is where genuine agents start and where support copilots, research assistants, and ops automations belong. Level 4, multiple specialized agents delegating among themselves, is justified by genuinely decomposable problems with independent subgoals; shipped examples exist, but they are rarer than conference talks imply. My position after building at all four levels: reach for level 3 only after level 2 measurably fails, and treat level 4 as a last resort requiring dedicated evaluation infrastructure, because debugging inter-agent conversations is debugging two stochastic systems blaming each other.

    When a Loop Beats a Chain#

    Choose chains when steps are known and inputs predictable: ETL-style document processing, fixed summarization flows, anything a flowchart already describes. Choose an agent loop when the path depends on what you find: research questions whose depth varies, triage tasks where the right action emerges mid-flight, operations work against inconsistent external systems. The test I use: if you can enumerate the branches ahead of time, write the flowchart; if discovering branches is the point, spend the loop budget. Ambiguity is not automatically a reason for agency either, since ambiguous tasks mostly produce confident wrongness at higher cost.

    Concrete examples from shipped systems make the line visible. Invoice ingestion is a chain: extract fields, validate against schema, route by vendor, every branch enumerable in advance. Investigating why a customer's pipeline failed is an agent task: you cannot know whether the answer lives in logs, config diffs, or a third-party status page until you look. Refund processing sits uncomfortably between; teams that start with an agent loop usually settle into a router that classifies the request and deterministic handlers that execute it, keeping the model for judgment calls like "is this damage claim plausible" rather than control flow.

    Cost, Latency, and Compounding Errors#

    Agent economics differ from chat economics in kind, not degree. Each hop re-sends growing context plus tool results, so a ten-step agent can consume fifty times the tokens of a single well-prompted call. Latency stacks serially: five seconds per hop times eight hops is a minute-long UX problem unless steps parallelize.

    Worse, errors compound multiplicatively. If any single step lands correctly ninety-five percent of the time, ten dependent steps succeed only about sixty percent of the time:

    steps = [0.95] * 10 joint = 1.0 for s in steps: joint *= s # 0.95^10 ~= 0.60

    That arithmetic, illustrative rather than benchmarked, explains the field's central tension: agents shine on long-horizon tasks precisely where long horizons punish per-step imperfection hardest. The decay is unforgiving to look at:

    Joint success probability across dependent steps

    Mitigations are structural: fewer, fatter tools instead of many tiny ones (fewer decision points), checkpoints that persist progress so retries resume rather than restart, and verification steps where the model critiques its own draft before acting on it.

    Guardrails That Make Agents Shippable#

    Non-negotiables from shipping these into production: hard step and token budgets on every loop, no exceptions for demo days. Allowlists on tool side effects, with irreversible actions (payments, deletions, sends) behind human confirmation gates rather than model judgment. Full trace logging of every thought-adjacent decision, argument set, and observation, because postmortems without traces are fiction. Sandboxed execution for any generated code or untrusted content, since tool outputs are attacker-writable surfaces. And graded evaluations on recorded task suites run before every prompt or tool change, because agent behavior drifts with upstream models whether you touch anything or not.

    Agency Is a Budget. An agent buys adaptability with unpredictability, and the exchange rate is brutal if you spend carelessly. The engineers who ship successful agents are the ones who buy the minimum autonomy that solves the task: a router where a branch suffices, a capped loop where routing fails, delegation only when decomposition is proven. Treat autonomy like cloud spend, instrumented, budgeted, reviewed monthly, and agents stop being a gamble and become what they actually are: a new control flow primitive for problems where the control flow cannot be known in advance.

    Want to Master Spring Boot and Land Your Dream Job?

    Struggling with coding interviews? Learn Data Structures & Algorithms (DSA) with our expert-led course. Build strong problem-solving skills, write optimized code, and crack top tech interviews with ease

    Learn more
    AI Engineering
    LLM
    AI Agents
    Was it helpful?

    Subscribe to our newsletter

    Read articles from Coding Shuttle directly inside your inbox. Subscribe to the newsletter, and don't miss out.

    More articles