Skip to main content
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

The generator yields 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 toolsisReadOnly() 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:
  1. Calls getMessagesAfterCompactBoundary() to split history into old and recent
  2. Sends the older messages to a separate Claude API call for summarization
  3. Replaces them with [summary] + [compact_boundary] + [recent messages]
The compact boundary is persisted in the JSONL session log as a system/compact_boundary entry, so it survives session resume.

runTools()

runTools() handles tool orchestration after StreamingToolExecutor returns results. It:
  • Appends each tool_result block to messages[]
  • Calls recordTranscript() for each result (fire-and-forget for assistant messages)
  • Handles SDKPermissionDenial for denied tools
  • Detects the SYNTHETIC_OUTPUT_TOOL_NAME sentinel 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 streams content_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.