QueryEngine in OpenClaude: The Core of the Agent Loop Explained
The QueryEngine is the central orchestrator of OpenClaude's agent loop, managing the complete lifecycle of a conversation turn from user input to structured output.
In OpenClaude, the agent loop represents the iterative cycle where user messages flow through processing, model inference, tool execution, and response generation. The QueryEngine class—implemented in src/QueryEngine.ts—serves as the single point of coordination for this entire pipeline. Understanding its role is essential for anyone extending OpenClaude's SDK, building on its gRPC interface, or debugging conversational state.
Core Responsibilities of the QueryEngine
The QueryEngine's duties span state management, prompt construction, model communication, and cross-component integration. Below is a complete breakdown of its functions with source references.
Lifecycle and Session Ownership
Each conversation receives a dedicated QueryEngine instance. This design ensures complete isolation between sessions.
- Per-conversation instantiation: One engine per conversation, created fresh for each new dialogue
- Turn initiation: The
submitMessage()method kicks off a new agent loop iteration - State persistence: The engine maintains accumulated context across multiple turns
In src/QueryEngine.ts at lines 195–197, the lifecycle boundaries are established:
// Each QueryEngine owns its query lifecycle and session state
// submitMessage() initiates a new turn in the agent loop
Prompt Assembly and Message Construction
Before any model call occurs, the QueryEngine constructs the complete prompt payload. This assembly phase integrates multiple data sources:
| Component | Purpose | Source Location |
|---|---|---|
| System prompt | Core behavioral instructions | src/QueryEngine.ts:81 |
| Injected messages | Context from prior turns or external sources | src/QueryEngine.ts:81–84 |
| Tool schemas | Function definitions for the model | src/utils/toolSchemaCache.ts:30–31 |
| Usage tracking | Token budgets and pricing data | Embedded in engine state |
The assembly logic at lines 81–84 ensures the model receives a coherent, policy-compliant instruction set:
// Builds system prompt with injected messages, tool schemas, and usage tracking
const prompt = this.buildPrompt({
system: this.systemPrompt,
messages: this.injectContext(rawMessages),
tools: this.toolSchemaCache.getSchemas(),
usage: this.totalUsage
});
Tool Integration and Schema Management
The QueryEngine maintains dynamic tool availability through the updateTools() mechanism. This allows:
- Runtime tool registration: New capabilities added mid-conversation
- Schema caching: Efficient lookup via
src/utils/toolSchemaCache.tsat lines 30–31 - Budget enforcement: Token and cost tracking through
totalUsage
Tool calls returned by the model are dispatched, executed, and their results folded back into the conversation context—all coordinated by the engine.
Goal and State Propagation
For higher-level agent systems, the QueryEngine surfaces progress information through goal status tracking. The test file src/queryEngine.goal.test.ts at lines 59–61 demonstrates this:
// Goal status forwarded to Assistant output for agent observation
expect(output.goalStatus).toEqual({
completed: false,
progress: 0.5,
description: 'Gathering requirements'
});
This enables supervisory agents to monitor subordinate conversations without parsing raw message streams.
Resilience: Interruption and Retry Handling
Production deployments require graceful failure modes. The QueryEngine implements:
- Interruption tracing: Capture and log loop termination causes (
src/QueryEngine.interruptionTrace.test.ts:31–38) - API retry logic: Exponential backoff for transient LLM failures
- Graceful shutdown:
interrupt()method for clean session termination
// Interruption tracing captures why a turn ended prematurely
const trace = engine.getInterruptionTrace();
// [{ reason: 'timeout', timestamp: 1699500000, recoverable: true }]
Integration Points: How Other Components Use QueryEngine
The QueryEngine is not invoked directly by end users. Instead, OpenClaude's various entry points wrap it with appropriate abstractions.
SDK Session Wrapping
The v2 SDK creates persistent sessions that maintain engine state across turns. In src/entrypoints/sdk/v2.ts at lines 194–199:
class SDKSession {
private engine: QueryEngine;
constructor(config: SessionConfig) {
this.engine = new QueryEngine(config);
// Engine persists for multi-turn conversations
}
async send(message: string): Promise<SDKMessage> {
return this.engine.submitMessage(message);
}
}
This wrapping allows application developers to manage long-running conversations without handling engine lifecycle directly.
CLI and REPL Single-Shot Queries
For command-line usage, the ask() function provides a thin convenience wrapper. The print command in src/cli/print.ts at lines 93–95:
import { ask } from '../QueryEngine';
export async function printCommand(query: string): Promise<void> {
const response = await ask(query, { singleShot: true });
console.log(response.content);
}
The REPL bridge uses the same pattern, creating ephemeral engines for each independent query.
gRPC Server Session Isolation
Remote clients receive complete session isolation. The gRPC server in src/grpc/server.ts at lines 112–150 spins up fresh engines per connection:
server.addService(ClaudeService, {
async createSession(call) {
const engine = new QueryEngine({
sessionId: uuid(),
isolation: 'strict' // No shared state between clients
});
// ... session management
}
});
This architecture prevents cross-tenant data leakage while allowing horizontal scaling of engine instances.
Message Emission and Observable Behavior
The final output of each agent loop iteration is an SDKMessage—a structured object consumed by downstream components. The emission occurs in src/utils/messages/systemInit.ts at lines 47–52:
export interface SDKMessage {
role: 'assistant' | 'tool';
content: ContentBlock[];
usage: TokenUsage;
goalStatus?: GoalStatus;
trace?: InterruptionTrace;
}
// Engine emits this for each completed turn
this.emit('message', sdkMessage);
UI layers, logging systems, and agent supervisors subscribe to these events to observe or record conversational state.
Summary
- QueryEngine is the sole owner of the agent loop lifecycle in OpenClaude, instantiated per conversation
- Prompt assembly integrates system instructions, context, tools, and usage data before model calls
- Tool and goal management enables dynamic capabilities and observable progress for hierarchical agents
- Resilience features include interruption tracing, retry logic, and graceful shutdown
- Multiple integration patterns allow the same core engine to serve SDK sessions, CLI commands, REPL interactions, and gRPC clients
- Structured message emission provides clean observability for downstream consumers
Frequently Asked Questions
What is the difference between QueryEngine and SDKSession?
SDKSession wraps a QueryEngine instance to provide a higher-level API. While the QueryEngine handles a single conversation's raw mechanics, SDKSession manages persistence, reconnection, and batching across multiple turns. The session abstraction in src/entrypoints/sdk/v2.ts keeps application code decoupled from engine lifecycle details.
Can multiple conversations share one QueryEngine?
No—each conversation requires its own QueryEngine instance. The source code at src/QueryEngine.ts:195-197 creates fresh engines per session. Sharing would risk state contamination between conversations. For resource pooling, spawn multiple engines and route requests externally rather than reusing instances.
How does QueryEngine handle long-running tool executions?
Through interruption tracing and async yielding. When tools exceed timeout thresholds, the engine captures the interruption cause in src/QueryEngine.interruptionTrace.test.ts:31-38, persists partial results, and allows resumption. The SDK layer can poll or stream progress via the goalStatus field rather than blocking on completion.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →