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)
LifecycleEntire conversation (multi-turn)Single task (one turn)
StatePersistent (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.