Function calling gives a model an interface to software. An agent appears when you put that capability inside a control loop.
The model receives a goal and some state, proposes an action, the application executes a tool, the result returns as an observation, and the model decides what happens next. That may mean another call, a final answer, or a decision to stop.
The interesting engineering is mostly around the model.
Tool schemas should make invalid actions difficult to express. Arguments still need validation. Calls need timeouts and bounded retries. Operations with side effects need idempotency or explicit confirmation. Authentication and authorization belong to the application, not to the model. Tool results should be normalized before being pushed back into context. And the loop needs an explicit termination policy.
Otherwise the failures are predictable: invented arguments, repeated side effects, circular tool calls, enormous observations consuming the context window, or one bad tool response steering every decision afterwards.
This surrounding machinery is what I think of as the agent harness. The model provides uncertain decision-making. The harness provides state, tools, permissions, execution, validation, recovery, observability and stopping.
The model may be the most sophisticated component in the system. It should still not be the component deciding what it is allowed to do.
