Welcome to the Query project tutorial! In this series, we will build a robust system for managing AI agent interactions.
We start with the foundation: consistency.
Imagine you are playing a video game. You start a mission on "Easy Mode." Halfway through the level, someone secretly flips a switch on the console, and suddenly the game switches to "Hard Mode" while you are still playing. The enemies change behavior instantly, but your character is still geared for Easy Mode. Chaos ensues!
This happens in software too. An AI agent might start a task with one set of settings (like "Fast Mode" enabled), but if we read the settings directly from the environment every time the agent takes a step, a mid-run change could break the logic.
The Solution: We take a "Snapshot" of the settings the moment the mission starts. We seal these settings in an Immutable Query Configuration. The agent looks at this snapshot, not the changing outside world.
Let's say we have a feature flag called fastModeEnabled.
process.env every time it generates a sentence. If the env var changes mid-sentence, the behavior becomes erratic.process.env once at the start. We write down "Fast Mode is ON" in our config object. Even if the server restarts or flags update, this specific agent query will finish its job thinking Fast Mode is ON.Before looking at code, let's define the terms:
In our system, we use a specific TypeScript type called QueryConfig to hold this snapshot.
First, we define what data we want to lock in. We separate standard identifiers (like Session IDs) from "Gates" (feature flags).
// config.ts
import type { SessionId } from '../types/ids.js'
export type QueryConfig = {
// A unique ID for this specific interaction
sessionId: SessionId
// Feature flags (Gates) that change behavior
gates: {
streamingToolExecution: boolean
emitToolUseSummaries: boolean
isAnt: boolean
fastModeEnabled: boolean
}
}
Explanation: This type acts like a form template. It says: "To run a query, you need to fill out this specific form with these specific details."
We create a factory function buildQueryConfig that reads the messy outside world (environment variables, analytics services) and packages them into a clean QueryConfig object.
// config.ts - Part 1: The Setup
import { getSessionId } from '../bootstrap/state.js'
import { isEnvTruthy } from '../utils/envUtils.js'
export function buildQueryConfig(): QueryConfig {
return {
sessionId: getSessionId(), // Grab the ID right now
gates: {
// We will fill these in the next snippet...
streamingToolExecution: false,
emitToolUseSummaries: false,
isAnt: false,
fastModeEnabled: false,
},
}
}
Explanation: This function is the "Camera." When you call buildQueryConfig(), you are pressing the shutter button. It captures the current sessionId.
Now let's fill in the gates. This is where we safely convert volatile environment variables into static booleans.
// config.ts - Part 2: filling the gates
gates: {
// Check Statsig or Env vars ONE time here
streamingToolExecution: checkStatsigFeatureGate('streaming_tool'),
emitToolUseSummaries: isEnvTruthy(
process.env.CLAUDE_CODE_EMIT_TOOL_USE_SUMMARIES,
),
isAnt: process.env.USER_TYPE === 'ant',
// Lock in Fast Mode status
fastModeEnabled: !isEnvTruthy(process.env.DISABLE_FAST_MODE),
},
Explanation: We call functions like isEnvTruthy. If the environment variable is "true" right now, fastModeEnabled becomes true forever for this object.
What actually happens when a user asks a question?
buildQueryConfig.
You might wonder why we don't just put fastModeEnabled inside the Agent's state (memory).
The Agent's State changes constantlyβit adds messages, updates token counts, and tracks tool outputs. If we mix static settings with changing state, debugging becomes a nightmare.
By keeping QueryConfig separate, we can easily write tests:
"Given Config X and State Y, the agent should do Z."
This approach makes the function "Pure" (predictable), which is essential for stable AI behavior.
In this chapter, we learned:
QueryConfig object.Now that we have our "Mission Parameters" set, we need to make sure the agent doesn't talk too much and run out of resources.
Next Chapter: Token Budget Control
Generated by Code IQ