Agent Loop
The Simple Idea
Section titled “The Simple Idea”The agent loop is Neo’s heartbeat. A user sends a message, the model answers, Neo runs any requested tools, then the model continues until it has a final answer.
Think of it like:
- User asks for work.
- Model decides what to do.
- Neo runs tools the model asked for.
- Tool results go back to the model.
- Repeat until the model is done.
The Problem
Section titled “The Problem”Coding agents need more than one model response. A model might first need to inspect files, then run a command, then edit a file, then run tests. That means one user turn can contain several model/tool steps.
The tricky part is preserving the transcript correctly. If the model asks for a tool, the next provider request must include the matching tool result. Splitting those apart can break provider APIs and confuse the model.
How Neo Solves It
Section titled “How Neo Solves It”The core loop lives in internal/agent. It is deliberately policy-free:
- It stores messages.
- It calls an
llm.Provider. - It emits events for the TUI.
- It runs injected tools.
- It appends tool results before continuing.
It does not know about coding style, AGENTS.md, skills, permissions, or the terminal UI. Those are layered around it.
The Important Invariant
Section titled “The Important Invariant”Every assistant tool_use must be followed by a matching user tool_result before the transcript is sent back to the provider.
Neo builds the assistant message and tool results together before committing them to the transcript. That keeps the conversation valid even when a tool fails.
Oversized tool output is capped at the agent boundary before it enters the transcript or session payload. The capped content includes a visible truncation marker with the original byte size and line count.
How To Extend It
Section titled “How To Extend It”Add behavior around the loop, not inside it, unless the behavior is truly provider/tool-turn mechanics.
Good extensions:
- Add a new tool to
internal/toolsand inject it through the registry. - Add a new provider that implements
llm.Provider. - Add prompt context before constructing the agent.
- Add a compactor through the
compact.Compactorinterface.
Risky extensions:
- Teaching the loop about project files.
- Putting UI behavior in the loop.
- Making the loop decide permissions itself.
Where To Look
Section titled “Where To Look”internal/agent/agent.go: the loop and event model.internal/llm/provider.go: provider-neutral message and tool types.internal/tools/: built-in tools the loop can run.