Function Calls vs. Stateful Agents: A Practical Comparison

Tutorial

Function Calls vs. Stateful Agents: A Practical Comparison

One of the most common questions we get from engineers evaluating Korelos is some version of: “Why do I need an agent runtime? I can just call function_call on the model and handle the rest myself.”

It’s a fair question. For about thirty percent of use cases, the answer is “you’re right, you don’t.” For the other seventy percent, what looks like a single function call on day one becomes a fragile reimplementation of an agent runtime by month three. This post is about how to tell which side of that line you’re on.

What a function call gives you

The model returns a structured request: “call get_order_status with order_id=42.” You execute it, you put the result back in the conversation, you call the model again. That’s it. One round trip per tool use, fully under your control, easy to debug.

For single-step, single-tool tasks — “look up this order, summarize it” — this is genuinely all you need. Don’t add an agent runtime if you don’t need one.

Where it breaks down

The cracks start to appear when any of the following become true:

  • Multi-step planning. The agent has to decide what to do next based on what just happened, possibly with a goal that takes 4–10 tool calls to achieve.
  • Conditional branching. “If the order status is shipped, do X; if it’s pending, do Y.” Encoding this in code makes the model’s planning behavior brittle.
  • Memory across turns. The user mentioned their preferences yesterday. You need to surface them today without re-asking.
  • Error recovery. The tool returned a 503. The agent needs to retry, or back off, or pick a different tool, or ask the user to clarify.
  • Cost and time budgets. “Don’t spend more than $0.20 or 30 seconds on this conversation.”

Every team I’ve seen try to bolt these onto a raw function-call loop ends up writing the same five hundred lines of orchestration code. It’s not that hard, but it’s not that easy either, and it’s the same code in every project.

What a stateful agent gives you

An agent runtime makes the control loop the primitive. You give it a goal, tools, and constraints; it does the rest. In Korelos, that looks like:

const run = await korelos.runs.create({
  agent_id: 'support-agent-v1',
  input: 'Where is my order #42 and why was the last refund delayed?',
  budgets: { steps: 8, tokens: 4000, ms: 30000 },
});

Behind that one call: planning, tool dispatch, retries on transient failures, memory retrieval, structured output, observability traces, and budget enforcement. None of that is in your code, because none of it is logic that’s specific to your problem.

The decision rule

If your interaction is one round trip, use a function call. If it’s a conversation or a workflow with conditional steps, use an agent runtime. The cost of being wrong in the first direction is “a little bit of overhead.” The cost of being wrong in the second direction is six weeks of yak-shaving.

The right primitive at the right layer of abstraction saves more time than any single feature.

That’s the bet behind Korelos: that more teams need an agent runtime than realize it, and that the ones who try to skip it end up rebuilding it badly.