In one line
An agent’s bottleneck is not model capability. It’s context management.
Before I built one, I assumed the hard part was orchestrating tool calls. After shipping it I realized: you decide what the model sees at every step. Give it too much and it loses the thread, give it too little and it makes things up.
Three principles that keep proving out
- Each step sees only what it needs — don’t feed search results back raw, extract the key points first.
- History is compressible — once a tool result from the previous turn has been used, collapse it to one sentence for the next one.
- Make failure explicit — when a model repeats a mistake it’s usually because it never registered that the last attempt failed. Write the error into the context.
// Bad: push the raw tool JSON straight back
messages.push({ role: 'tool', content: JSON.stringify(rawResult) })
// Good: compress it down to what the agent actually needs
messages.push({
role: 'tool',
content: summarize(rawResult, { keep: ['id', 'title', 'status'] })
})
One counterintuitive finding
Showing an agent its own reasoning works far worse than showing it the result of that reasoning.
Having the model emit its thinking at every step is a popular move, but leaving that whole chain in the context lets later steps get hijacked by the model’s earlier self-persuasion. What I do: throw the thinking away once it’s been used, keep only the final decision.
Writing a good agent is mostly being a good context designer.