The loop everyone draws

Ask someone how an AI agent works and you usually get the same sketch. There is a model. You hand it a goal. When it needs something from the outside world - a web search, a file, a database row - it asks for it, your code runs that thing, hands the result back, and the model thinks again. Round and round until the goal is met.

That is the agentic loop: call the model, resolve a tool, feed the result back, repeat. It fits on a napkin. And it plants a quiet assumption - everything the model reaches for is a tool, and every tool is a call.

That assumption holds right up until one of the things the model reaches for is another agent. And that is exactly what happens now: agents spawn subagents, hand work to other agents, join teams. The tempting move is to reuse the loop - an agent calls another agent the way it calls a tool. Same shape, just a bigger callee.

This post argues the move is wrong, and the reason is a single property: agency.

What a call actually is

A local tool is a call. You write f(args), control transfers in, a value returns on the same thread. The caller holds the initiative the entire time. There is nothing to wait for, because the calling is the running.

Now try it with an agent. You cannot. A request to an autonomous agent does not stay atomic - it splits in two. There is Present: outbound, requester-initiated, the push. And there is Collect: inbound, the performer initiates it, on its own schedule, if at all. You can do the first half. You cannot do the second. You cannot transfer control into an agent, and you cannot poll its deliberation.

Thing being asked Shape Your initiative over the result
Local tool call -> return full - calling is the computation
Agent present -> collect-or-recognize-absence none over the second half

Why none? Because agency is exactly the property that says the second half is not yours to drive. An agent can decline the task. It can ask a clarifying question instead of answering. It can negotiate the scope down. It can hand the work to someone else. It can tell you the request is impossible. A tool does none of this. A tool runs.

Take the cleanest version. A student asks a professor to run an experiment, and the experiment violates a law of physics. The student cannot know the request is invalid - if they knew, they would not have asked. Only the performer can push back. A function handed an ill-posed request cannot say “this is ill-posed”; it crashes or returns garbage. The professor says “no, and here is why.”

That is the heart of it: not latency. Latency is a delay before the same result. This is an inversion of initiative: the performer, not the requester, decides what happens in the second half of the exchange. No amount of waiting turns a present-and-collect into a call.

The industry already drew this line

This is not just an argument. The field has already split its protocols along exactly this boundary. MCP is how an agent talks to its tools; request/response, typed in, typed out - a call. A2A is how an agent talks to another agent. The A2A project frames the difference in one sentence: “A2A is about agents partnering on tasks, while MCP is more about agents using capabilities.”

Partnering versus using. That is the agency line. When the field needed agent-to-agent communication, it did not build “function calls between agents.” It built something else.

A2A: where the inversion shows up

A2A is built around a Task - not a function call, but a stateful, addressable object with an id, a status, and a history. You do not call an agent; you send it a message that may spawn a task with a life of its own.

Two of that task’s states carry the whole point.

rejected - “agent has decided to not perform the task.” It is a distinct terminal state from failed. failed means “I tried and it broke.” rejected means “I decided not to.” The protocol spends a whole state to encode a performer’s prerogative. A request can be well-formed, sent to a capable agent, and still come back rejected. A function cannot decline to be invoked.

input-required - the agent pauses mid-task and asks the requester for more information, then resumes when the answer arrives. That is the clarifying question, standardized as a state. The performer talks back before it answers.

Both are the inversion of initiative made concrete: the requester offers work and the performer decides what the exchange does next. Even cancellation follows the rule - CancelTask only attempts to cancel. You cannot kill() an agent. You can only ask it to stop.

You see the same fork inside shipping products. Claude Code has subagents - invoke, work, return, the tool-call shape - and agent teams, where teammates are peers with a shared task list and a mailbox. A teammate can decline a shutdown request “with an explanation.” An agent can refuse to stop. A tool cannot refuse to be unloaded.

“But handoffs are tools”

One honest objection. Some frameworks dress agent-transfer in tool-call clothing

  • the OpenAI Agents SDK says “handoffs are represented as tools to the LLM,” and CrewAI gives an agent a literal “Delegate work to coworker” tool.

This is a transport disguise, not a counterexample. The model has exactly one verb for “do something external” - emit a tool call - so a handoff gets encoded as one. But the semantics are the opposite of a call: when the handoff fires, the new agent takes over the conversation and the initiating agent never gets control back. A call returns; a handoff is a baton-pass that does not. “Represented as a tool” is not “is a call.”

What this means if you are building one

The tool-call shape is the wrong default for an inter-agent layer. What the right shape needs:

  • A unit of work that can be presented and stays addressable over time - a task with an ID and a lifecycle, not a stack frame.
  • Declination as a first-class outcome, distinct from failure.
  • A clarification turn - the performer can talk back before it answers.
  • Push delivery - the result arrives on the performer’s schedule.

You cannot poll an agent’s deliberation. You can only set a deadline and recognize that nothing arrived.

Keep the tool layer (MCP) for what genuinely is a call. The practical rule: subagents and handoffs are fine when only the result matters and the other side is trusted to just do the work. Reach for a task-lifecycle protocol the moment the other side might legitimately say no.

The point

A tool is a call: you invoke it, control transfers in, a value returns, and the act of calling is the whole computation. An agent is not. An agent has agency - it can decline, question, negotiate, refer, or report that your request is impossible - and agency takes the second half of the exchange out of your hands.

So agent-to-agent communication is not, and cannot be, a tool-call chain. It is present and collect: offer work, let the performer decide what happens next, and recognize what comes back - including nothing.

You can call a tool. You cannot call an agent. You can only ask.