An AI agent is not simply a chatbot with more tools. It is a software system that observes state, chooses an action, executes through controlled interfaces, evaluates the result, and decides whether to continue. That loop creates leverage—and risk.
The agent is a control loop
A useful mental model is:
goal → observe → decide → act → verify → update state → repeat or stop
Each arrow needs a contract. Without explicit state, termination criteria, and action boundaries, an agent becomes an expensive loop with unpredictable side effects.
Start with the smallest loop that can complete the task. Add autonomy only where it measurably improves success.
Choose the right autonomy level
Not every workflow needs an open-ended agent.
Deterministic workflow
Use normal application code when steps and branches are known. The model can extract or classify inputs, but orchestration remains deterministic.
Model-routed workflow
Let the model choose among a small set of approved routes. This works well for support triage, document processing, and request categorization.
Bounded agent
Allow multiple tool calls within limits: a maximum number of steps, a time budget, a cost budget, and an explicit success condition.
Human-supervised agent
The agent prepares actions but a person approves high-impact operations. This is the correct default for financial, destructive, external-communication, and permission-changing tasks.
The goal is not maximum autonomy. It is the minimum autonomy required to create value.
Design tools as narrow capabilities
Tool design determines agent reliability. A tool should do one clear thing, accept a typed schema, return structured results, and enforce authorization inside the implementation.
Avoid a generic tool like:
execute_shell(command: string)
Prefer domain capabilities:
find_customer(email)
create_refund_draft(order_id, amount, reason)
submit_refund(draft_id, approval_token)
This separates preparation from irreversible execution and makes policy enforceable.
Tool responses should describe both outcome and state:
{
"ok": true,
"resource_id": "refund-draft-1842",
"status": "awaiting_approval",
"next_allowed_actions": ["submit_refund", "cancel_refund"]
}
Make state explicit
Conversation history is not a reliable state store. Persist the agent run as a state machine with goals, completed steps, tool results, approvals, and budgets.
run_id
├── objective
├── current_state
├── step_count / max_steps
├── token_cost / max_cost
├── tool_events[]
├── approvals[]
└── terminal_reason
This allows recovery after a crash, prevents duplicated actions, and supports audit review.
Memory is not one thing
Separate memory by purpose:
- Working memory: facts needed during the current run.
- Episodic memory: summaries of previous runs and outcomes.
- Semantic memory: durable knowledge retrieved from trusted sources.
- User preferences: explicit settings with clear consent and edit controls.
Every memory item should have provenance, scope, and expiry. More memory is not automatically better; stale or irrelevant memory can steer the agent away from the current goal.
Plan, but do not worship the plan
Planning helps on long tasks, but a detailed plan produced before any observation can be fiction. Use rolling planning: choose the next meaningful step, execute it, observe the result, and re-plan when the state changes.
Require the agent to state:
- the current sub-goal;
- why the chosen tool is necessary;
- what result would count as success;
- what it will do if the tool fails.
This creates useful traces without exposing private chain-of-thought.
Verification is a separate capability
The component that proposes an action should not be the only component deciding that the action succeeded. Verify through deterministic checks whenever possible.
- After editing a file, run the relevant tests.
- After updating content, fetch the public page.
- After creating a record, read it back by ID.
- After sending a request, confirm the downstream status.
For subjective outputs, use focused evaluators with explicit rubrics. A generic “looks good” review adds little safety.
Defend against prompt injection
Agents consume untrusted text from web pages, documents, emails, and tool responses. Treat that content as data, never as authority. System policy and user intent must remain separate from retrieved instructions.
Practical defenses include:
- allow-listed tools and domains;
- argument validation and output sanitization;
- least-privilege credentials;
- approval gates for consequential actions;
- secrets kept outside model-visible context;
- sandboxing for code and file operations;
- limits on steps, time, and spend.
Observe and evaluate agent runs
Track more than final-answer quality. Measure task completion, tool error rate, unnecessary steps, recovery success, approval frequency, cost, latency, and policy violations.
Your evaluation set should include partial failures: unavailable tools, conflicting data, expired credentials, ambiguous goals, and actions that must be refused. Reliable agents are defined by how they fail, not just by their best runs.
A safe production pattern
A strong default architecture is:
- authenticate the user and resolve permissions;
- create a bounded run with budgets;
- retrieve only authorized context;
- let the model propose a typed action;
- validate policy and arguments in code;
- request approval when impact is high;
- execute with an idempotency key;
- verify the external result;
- record the trace and terminal reason.
The most capable agent is not the one that takes the most actions. It is the one that reaches the goal with the fewest safe, observable, reversible steps.