Lessons from shipping autonomous AI agents, covering deterministic tool calling, structured output validation, context compaction, and sandbox safety.
By Kibet Brian
Demos of AI agents are easy. Production agents are a systems problem: bounded tools, validated outputs, managed context, and hard safety rails. Here is the architecture that survived real users.
Every tool gets a strict schema, an idempotency key, and a timeout. The agent never calls the world directly, it calls your tools, and your tools enforce the contract. Retries use the same key so a flaky network cannot double-charge or double-book.
1const tools = {2 searchOrders: {3 schema: z.object({ query: z.string(), limit: z.number().max(20) }),4 timeoutMs: 8000,5 idempotent: true,6 handler: withTimeout(searchOrders),7 },8};
Free-form model output is a liability at system boundaries. Parse every response against a schema before it touches your database or another agent. On validation failure, re-prompt once with the error, then fail loud to a human instead of guessing.
Long agent runs blow past context windows and latency budgets. Summarize completed steps into rolling state, keep only the current goal and fresh tool results verbatim, and archive the rest to retrievable storage.
An agent without a budget is a denial-of-wallet waiting to happen. Set a max step count per run, a token budget per task, and a circuit breaker that halts on repeated tool failures. The agent should ask for more budget, never assume it.
1const run = await agent.run(goal, {2 maxSteps: 12,3 maxTokens: 20000,4 stopOnConsecutiveFailures: 3,5 onBudgetExceeded: 'ask-human',6});
Keep a golden set of tasks with expected tool-call sequences and final states, and replay it on every prompt or model change. Score tool choice, output validity, and latency separately, because a fast agent that calls the wrong tool is worse than a slow correct one.