# QueryEngine in OpenClaude: The Core of the Agent Loop Explained

> Discover how the QueryEngine orchestrates OpenClaude's agent loop, handling user input to structured output for seamless conversations. Learn its core role in the Gitlawb/openclaude repository.

- Repository: [Gitlawb/openclaude](https://github.com/Gitlawb/openclaude)
- Tags: internals
- Published: 2026-09-05

---

**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`](https://github.com/Gitlawb/openclaude/blob/main/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`](https://github.com/Gitlawb/openclaude/blob/main/src/QueryEngine.ts) at lines 195–197, the lifecycle boundaries are established:

```typescript
// 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:

```typescript
// 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.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/utils/toolSchemaCache.ts) at 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`](https://github.com/Gitlawb/openclaude/blob/main/src/queryEngine.goal.test.ts) at lines 59–61 demonstrates this:

```typescript
// 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

```typescript
// 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`](https://github.com/Gitlawb/openclaude/blob/main/src/entrypoints/sdk/v2.ts) at lines 194–199:

```typescript
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`](https://github.com/Gitlawb/openclaude/blob/main/src/cli/print.ts) at lines 93–95:

```typescript
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`](https://github.com/Gitlawb/openclaude/blob/main/src/grpc/server.ts) at lines 112–150 spins up fresh engines per connection:

```typescript
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`](https://github.com/Gitlawb/openclaude/blob/main/src/utils/messages/systemInit.ts) at lines 47–52:

```typescript
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`](https://github.com/Gitlawb/openclaude/blob/main/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.