An agent is a model that can take actions and decide what to do next. That second part is what makes it useful and what makes it dangerous, because a system that chooses its own next step can choose badly many times in a row without anyone noticing until the bill arrives.
Most of the engineering is constraint.
Give it the smallest possible set of tools
Every tool you expose is a decision the model now has to get right. Ten tools means ten chances to pick the wrong one, and the failure rate does not scale kindly.
Tools should be few, sharply named, and hard to misuse. A tool called
search_orders_by_customer_id produces better behaviour than one called
query with a free text parameter, because the first one cannot be pointed at
something unintended.
If two tools do almost the same thing, the model will pick between them arbitrarily. Merge them.
Bound everything that can run away
An agent loop has three quantities that must have a ceiling: steps, time and money.
Without a step limit, an agent that gets confused will retry variations of the same failing action indefinitely. Without a time limit, a user watches a spinner forever. Without a cost ceiling, a loop that would have been caught in testing becomes a genuinely expensive incident in production.
These limits are not error handling. They are the design. Decide them before building, and decide what the agent does when it hits them, because hitting them is a normal outcome rather than an exception.
Separate reading from writing
The single most useful structural decision: tools that only read are safe to let the agent call freely. Tools that change something are not.
Anything that writes, sends, charges, deletes or publishes should either require explicit confirmation, or be restricted to a narrow, reversible, well tested path. The blast radius of a confused agent is exactly the set of write actions you handed it.
Make every step observable
When an agent produces a bad outcome, the question is always which step went wrong, and you cannot answer it from the final output alone.
Log the whole trace: what it decided, which tool it called, what came back, and what it concluded. Without that, debugging an agent is guesswork, and with it, most problems are obvious within a minute.
This is also what makes evaluation possible, because you can score individual decisions rather than only the end result.
Prefer a fixed sequence when a fixed sequence works
This is the advice people like least. If a task has a known order of operations, write the order of operations. A model deciding each step is only worth its unpredictability when the path genuinely varies.
A great deal of what gets built as an agent is a workflow with a model in two or three of the boxes. That version is cheaper, faster, easier to test and much easier to reason about when it breaks. Reach for autonomy when the branching is real, not because the architecture sounds more advanced.
Decide what failure looks like
An agent that cannot complete a task should stop and say so. That behaviour has to be designed and tested, because the default tendency is to keep going and produce something.
The most damaging agent failure is not the one that gives up. It is the one that confidently finishes, having done the wrong thing, in a system where nobody was checking.
