Solution: A Shell on the Outside, a Machine Within
Before diving into the Loop's internals, it helps to understand where it sits. There are two things in the code that are easy to confuse:
You send a message
↓
QueryEngine.submitMessage() ← [Outer] conversation manager, maintains entire session state
↓
query() ← [Inner] Agent Loop, executes this turn's task
↓
queryLoop() inner loop
├── Calls Claude API
├── Executes tools
└── Loops until task is complete
↓
Returns result to QueryEngine
QueryEngine updates message history, waits for next message
| QueryEngine (outer) | query() (inner) | |
|---|---|---|
| Lifecycle | Entire conversation (multi-turn) | Single task (one turn) |
| State | Persistent (message history, file cache, usage) | Ephemeral within the turn |
QueryEngine's own comment:
"One QueryEngine per conversation. Each submitMessage() call starts a new turn within the same conversation. State (messages, file cache, usage…) persists across turns."
With the outer layer clear, let's go inside — the five steps of the Agent Loop.