QueryEngine.ts is the SDK/headless mode lifecycle engine. While REPL.tsx drives interactive terminal sessions with React/Ink, QueryEngine.ts exposes the same agent loop as a clean programmatic API: submitMessage(prompt) returns an AsyncGenerator<SDKMessage> that streams results back to the consumer.
QueryEngine.ts is ~1,295 lines and orchestrates the entire lifecycle from prompt ingestion through system prompt assembly, slash command parsing, agent loop execution, and JSONL persistence.submitMessage Interface
SDKMessage values incrementally as the agent loop progresses. Consumers can for await over it to receive streaming output.
Internal Flow
StreamingToolExecutor
StreamingToolExecutor (in src/services/tools/StreamingToolExecutor.ts) manages concurrent tool execution within a single agent turn. It partitions the tools requested by the model into two buckets:
- Concurrent-safe tools —
isReadOnly()or otherwise side-effect-free; run in parallel - Serial tools — write operations like
FileEditTool,BashTool; run sequentially to avoid conflicts
autoCompact()
autoCompact() triggers when the accumulated token count in messages[] exceeds the configured threshold. It:
- Calls
getMessagesAfterCompactBoundary()to split history into old and recent - Sends the older messages to a separate Claude API call for summarization
- Replaces them with
[summary] + [compact_boundary] + [recent messages]
system/compact_boundary entry, so it survives session resume.
runTools()
runTools() handles tool orchestration after StreamingToolExecutor returns results. It:
- Appends each
tool_resultblock tomessages[] - Calls
recordTranscript()for each result (fire-and-forget for assistant messages) - Handles
SDKPermissionDenialfor denied tools - Detects the
SYNTHETIC_OUTPUT_TOOL_NAMEsentinel for structured output
SDKMessage Types
submitMessage yields five distinct message shapes, all discriminated by type:
AsyncGenerator Streaming Chain
The streaming chain is end-to-end: the Claude API streamscontent_block_delta events, query.ts yields them as SDKMessage values, and QueryEngine.ts re-yields them to the consumer. No buffering occurs at the engine layer.
Using QueryEngine in Headless/SDK Mode
In headless/SDK mode there is no interactive permission prompt. Tool permission decisions are driven entirely by the
permissionMode option and any alwaysAllow/alwaysDeny rules configured in settings.json.