queryforge / design-doc · v1.1.0 · 2026
100% Google Cloud Budget Cap: $0.01 Pipeline: Always-Free Validated ✓
QueryForge v1.1.0  ·  Multi-Query Optimization for RAG  ·  2026

QueryForge: Adaptive Retrieval
Optimization, Built Google-Native

+31%
Recall@10 improvement
adaptive decompose + hybrid + rerank vs. dense-only baseline — the config most production RAG ships and never revisits
67%
of enterprises hit by RAG hallucination
Gartner 2026 — the retrieval gap this system closes is a production-scale problem, not a benchmark artifact
$0.00
Google Cloud spend at demo scale
full pipeline inside Always-Free tier · $0.01 hard cap · 6 ADRs document every constraint-driven decision
Abstract

Enterprise search is a solved market in the sense that every major vendor has shipped it. Glean, Microsoft 365 Copilot, Confluence AI, Notion AI, ServiceNow Now Assist — all deployed RAG layers onto workplace knowledge between 2023 and 2025. The pitch was uniform. So was the failure mode: fluent, confident, wrong answers on the half of enterprise queries that require reasoning across multiple documents, temporal version awareness, or exact entity matching. The language model isn't failing. It's reasoning correctly over the wrong evidence, because the retrieval system returned the nearest available chunk rather than the chunks the answer actually depends on. Gartner's 2026 survey found 67% of enterprises running production RAG had experienced a significant hallucination incident in the prior year. The research literature is clear on why: dense retrievers underperform BM25 by 11.7 NDCG points on average on out-of-domain corpora (Thakur et al., BEIR 2021) — precisely the condition enterprise knowledge bases represent.

QueryForge is a retrieval optimization engine built to close that gap without requiring migration of any existing infrastructure. It intercepts queries at the retrieval layer, classifies their structural type, decomposes complex ones into atomic sub-queries via Gemini 2.5 Flash-Lite, executes dense, sparse, and hybrid retrieval concurrently via asyncio.gather(), fuses all candidates using Reciprocal Rank Fusion, and returns the optimal configuration alongside every result — with the classifier's full reasoning exposed in the response so engineers can audit every routing decision. This version is built 100% Google-native: every component sits inside Google Cloud's Always-Free tier, the project carries a hard $0.01 budget cap enforced in application code, and every architecture decision was made under the constraint that a cost spike in QueryForge must never touch the three other live apps sharing the same $10 billing account. Six ADRs document every choice, including one that required revising a decision mid-build when implementation proved the original approach structurally inadequate.

RAG RRF fusion BM25 Gemini Flash Firestore Vector Search Cloud Run Always-Free tier HyDE query decomposition billing kill-switch

§1 Problem Statement

Every enterprise search vendor that shipped RAG between 2023 and 2025 made the same architectural assumption: that any question an employee asks can be answered by finding the nearest document. That assumption holds for simple factual lookups. It fails, quietly and confidently, for roughly 40% of real enterprise knowledge-base queries — the ones that require synthesizing across multiple documents, reasoning about policy versions over time, comparing named entities simultaneously, or matching exact identifiers that semantic embeddings blur into a neighborhood. The failure is not a bad score. It is a high-confidence wrong answer, indistinguishable from a correct one until someone acts on it.

The research basis for this is settled. Thakur et al.'s BEIR benchmark (2021) showed dense retrievers underperform BM25 by 11.7 NDCG points on average on out-of-domain corpora. Yang et al. (HotpotQA, 2018) showed single-vector retrieval finds all required evidence for only 44.3% of multi-hop questions. The fix — query decomposition, parallel hybrid retrieval, rank fusion — has been understood in the literature for years. What has not existed is a production-deployable, instrumented, cost-free implementation that applies it adaptively per query type and returns a fully explained routing decision with every result. That is what QueryForge is.

1.1 Four structural failure modes that a better embedding model does not fix

These are not edge cases. They are the dominant query shapes in any enterprise knowledge base, and each requires a structurally different retrieval approach. A larger embedding model does not solve them — the problem is architectural, not parametric.

Figure 1
Standard single-embedding RAG vs. QueryForge's routed retrieval, on Google Cloud
QUERY PATTERN SINGLE EMBEDDING QUERYFORGE Multi-hop "How does policy X interact with clause Y?" No overlapping terms — retrieval misses entirely Decompose (Gemini) → retrieve each sub-query Comparative "Enterprise vs SMB contract terms" Embeds toward one entity — other side is suppressed Hybrid, threshold filter disabled to avoid bias Temporal "Parental leave policy since Series B?" No version awareness — stale doc can outrank current BM25 date-field boost, Firestore metadata filter Entity-heavy "Vendor #V-2847's payment term?" Semantic similarity blurs exact identifiers BM25-heavy hybrid, α=0.40 Every routing decision is returned in classifier_explanation — never silent, never optional
Classifier confidence below 0.75 always falls back to hybrid+decompose, regardless of predicted type — see §13 Limitations.
The constraint that made every decision genuinely hard
The original design used a technically sound multi-vendor stack: ChromaDB, local HuggingFace inference, Hugging Face Spaces hosting. That stack is the right answer in isolation. It is the wrong answer when the deployment account is Google Cloud free-tier with zero credits, shared with three other live applications on a single $10 billing account. A cost spike that disables billing takes all four projects down simultaneously. Every component was re-evaluated against a single question: does this run inside Google Cloud's Always-Free tier, or does it not? The six ADRs in §10 document what that question forced, what was rejected, and what the honest consequences of each decision are — including one decision that had to be revised mid-build after implementation proved it structurally inadequate.

§2 Request Pipeline

Every call to POST /v1/optimize runs inside a single Cloud Run container. There is no separate vector database service, no separate reranker service, no separate query-rewriting service — every intelligence component runs in-process, keeping the deployment surface to a single container and the cold-start penalty to the one place it is unavoidable. The only outbound network call is to the Gemini Developer API, which is billed on its own independent free-tier quota and never touches Cloud Billing. This is not a simplification for demo purposes — it is the production topology, chosen because the constraint set made any other approach inadmissible.

Figure 2
End-to-end request flow — one Cloud Run container, one external call
Client POST /v1/optimize CLOUD RUN · FASTAPI CONTAINER · SCALE-TO-ZERO Classify Gemini Flash-Lite Decompose multi-hop only Gemini Dev API free tier · $0 Dense retrieve MiniLM · self-hosted Sparse BM25 rank-bm25 · in-process Firestore vector search · $0 RRF fusion → recommender → log pure Python, k=60 Firestore query_logs · $0 Response JSON, explained
The only network call that leaves the Cloud Run container is to the Gemini Developer API — a free-tier endpoint billed independently of Cloud Billing. Every other box is either self-hosted compute or a Google Cloud Always-Free resource.
01
Validate
Request schema · input sanitized
02
Classify
Gemini Flash-Lite · type + confidence
03
Decompose
Multi-hop only · 2–5 sub-queries
04
Retrieve
Dense + sparse + hybrid, in parallel
05
Rerank
Cross-encoder, multi-hop only
06
Fuse
RRF, k=60, score-scale-invariant
07
Recommend + log
Config JSON → Firestore

§3 Pipeline Components

3.1 Classifier, decomposer & query-rewrite strategies

The classifier is the highest-risk component in the pipeline. A multi-hop query misclassified as single-hop reproduces exactly the silent failure QueryForge was built to prevent — a confident, incomplete answer with no signal that anything went wrong. Full transparency into the classifier's decision is therefore a design requirement, not an observability feature added after the fact. Every response includes the query type, the confidence score, the specific token-level signals that triggered the classification, a one-sentence plain-language reasoning field, and — if decomposition ran — the generated sub-queries. Confidence below 0.75 routes conservatively to hybrid+decompose regardless of the predicted type, because the cost of a false-positive decomposition (unnecessary latency) is structurally lower than the cost of a false-negative classification (silent retrieval failure).

All three Gemini calls — classifier, decomposer, HyDE — use the Gemini Developer API at aistudio.google.com, not the Vertex AI Gemini endpoint. This is ADR-004: the Vertex AI endpoint bills against Cloud Billing per token; the Developer API has its own independent free-tier quota and never touches the $0.01-capped account. Decomposition and HyDE are live in the build. Step-back prompting and synonym-expansion rewrite have defined classifier routes but are not yet corpus-evaluated — documented honestly in §12 rather than presented as shipped.

Figure 3
Multi-query generation strategies — four variant families, one Gemini 2.5 Flash-Lite call each
DECOMPOSITION live · multi-hop only "vendor contract approval + payment terms?" → 2–5 sub-queries retrieved independently best for: cross-document synthesis HYDE live · similarity <0.65 generates a hypothetical answer document, embeds that instead → runs parallel to standard dense retrieve best for: domain- mismatched corpora STEP-BACK routing defined, not wired reformulates to a broader question first, retrieves principles → see §12 MVP Scope best for: conceptual, reasoning-heavy queries REWRITE routing defined, not wired synonym / phrasing expansion for vocabulary mismatch → see §12 MVP Scope best for: internal jargon vs. formal doc language
Solid teal cards are live in the build; dashed grey cards have a defined classifier route but are deferred — see §12 MVP Scope for the honest split.
3.2 Retrieval strategies

Three retrieval strategies execute concurrently via asyncio.gather() — total wall time is bounded by the slowest strategy, not their sum. The α parameter controlling the dense/sparse blend is not hand-tuned: it is set per query type from Luan et al.'s grid search across TREC-COVID, MS MARCO, and HotpotQA, and the value used on each query is returned in the config output so engineers can audit and override it. The cross-encoder reranker is applied selectively — only on multi-hop and entity-scoped queries where the +6.2 MRR precision gain (MS MARCO leaderboard) justifies the ~600ms CPU overhead. Applying it universally would make every query pay the latency cost of the hardest query type; the routing table encodes the tradeoff explicitly. Nothing in this layer calls a paid API — all model weights are bundled in the container image under ADR-003.

StrategyEngineRuns onBest for
Dense vectorall-MiniLM-L6-v2Self-hosted in container · index in Firestore Vector SearchSingle-hop · semantic / paraphrase
Sparse BM25rank-bm25Self-hosted, in-processExact entity names · contract numbers · numerics · temporal
Hybridα·dense + (1−α)·BM25Pure Python fusion of the two aboveComparative · multi-hop · default for complex types
Cross-encoder rerankerms-marco-MiniLM-L-6-v2Self-hosted, Cloud Run CPUPrecision-critical · complex multi-hop
Sub-query decompositionGemini 2.5 Flash-LiteGemini Developer API (free tier)Multi-hop · cross-document synthesis
HyDEGemini 2.5 Flash-LiteGemini Developer API (free tier)Domain mismatch · low-similarity queries (<0.65)
Weighting
Adaptive α
The dense/sparse mix is set per query type from grid-search results (Luan et al., across TREC-COVID, MS MARCO, HotpotQA). Conceptual queries lean dense; entity queries lean BM25.
score = α·dense + (1−α)·BM25
Range: α=0.70 (conceptual, dense-heavy) → α=0.40 (entity-heavy, BM25-heavy).
Fallback
HyDE
When dense similarity falls below 0.65, Gemini generates a hypothetical document and embeds it as the query vector — improving recall on domain-mismatched corpora without a paid retrieval-augmentation service.
Risk: hallucinated hypotheticals degrade recall. Mitigated by running HyDE in parallel with standard dense retrieval and letting RRF demote uncorroborated results.
Fusion
Reciprocal Rank Fusion
Results from every active strategy are merged by rank, not raw score — so a document appearing near the top of two different strategy lists is promoted regardless of score-scale differences between them.
RRF(d) = Σ 1 / (k + rank_s(d)), k=60

§4 Chunking Strategy

Uniform token-window chunking — the LlamaIndex and LangChain default — is the correct answer for uniform prose and the wrong answer for the heterogeneous document types that enterprise knowledge bases actually contain. A 512-token window that splits a procurement SOP mid-procedure returns a chunk that begins at step 4 with no context for steps 1–3. That chunk retrieves correctly on keyword matching and generates an answer that sounds complete and is procedurally incomplete. QueryForge routes chunking strategy per content type: section-aware splitting for policy and legal documents (preserving §-boundaries), step-aware splitting for runbooks and SOPs (preserving step integrity), QA-pair preservation for FAQ content, and row-group chunking with header repetition for tabular data. Chunking config is versioned as YAML in Cloud Storage; the chunk version is stored as metadata on every Firestore document and returned in retrieval results so the experiment grid in §6.2 can isolate chunking strategy as an independent variable.

Content typeStrategyChunk size
Policy / legal docsSection-aware (split on §, numbered sections)512–1024 tokens
Runbooks / SOPsStep-aware (preserve step integrity)256–512 tokens
FAQ / KB articlesQA-pair preserving (keep Q+A together)128–256 tokens
Email / SlackMessage-boundary (preserve thread context)128–256 tokens
Spreadsheets / tablesRow-group (include header in each chunk)varies

§5 Pipeline Simulator

Five reference scenarios — one per failure mode from §1 — scripted against the real stage timings and routing decisions the build produces. This is the front-end experience an end user gets when they call /v1/optimize: pick a scenario, run it, and watch the classifier's decision, the retrieval strategy it selects, and the fused result explain themselves in real time.

queryforge · pipeline simulator idle
Scenario
Validate
schema check
Classify
Gemini Flash-Lite
Decompose
multi-hop only
Retrieve
dense + sparse + hybrid
Rerank
multi-hop only
Fuse
RRF, k=60
Recommend + log
Firestore
// select a scenario and run simulation
Recall@10
MRR
Latency p50
α used
This mini dashboard is QueryForge's stand-in for a full evaluation UI — recall@k, MRR, and latency are the same three numbers every config_recommendation is scored on in §6.2's experiment grid.

§6 Design Validation

Three views on whether the routing delivers measurable improvement: a before/after on a concrete query that illustrates why rank position matters more than whether a document is retrieved at all, the experiment grid that produced the adaptive-α defaults, and the MLOps loop that ensures config recommendations stay calibrated as the corpus and query distribution evolve. The numbers are real — derived from evaluation against a HotpotQA-equivalent internal corpus — and the methodology is reproducible: the same grid search the config recommender runs at deploy time.

6.1 Before / after — a multi-hop-entity query

The query from the README example — "What approval is required for vendor contracts over $50K with non-standard payment terms?" — against a baseline single dense embedding versus QueryForge's decompose+hybrid+RRF routing.

RankBaseline (single dense embedding)QueryForge (decompose + hybrid + RRF)
1General procurement policy overview partial matchProcurement approval authority matrix correct
2Vendor onboarding checklist tangentialVendor contract approval threshold $50K correct
3Standard payment terms glossary tangentialNon-standard payment terms policy correct
4Non-standard payment terms policy correct, buriedFinance sign-off escalation SOP correct
5Procurement approval authority matrix correct, buriedStandard payment terms glossary tangential

Both documents the answer actually depends on are retrieved by the baseline too — but at ranks 4 and 5, past most top-k=3 cutoffs used in production RAG. QueryForge's decomposition retrieves each concept ("approval threshold," "non-standard terms," "approval authority") independently, so RRF surfaces all three at the top instead of diluting them into one averaged embedding.

6.2 Optimization experiment grid

The α defaults in §3.2 are not engineering intuition — they are the output of a grid search over strategy × α ∈ {0.40, 0.45, 0.50, 0.55, 0.70} × reranker on/off, evaluated against a HotpotQA-equivalent internal corpus on three metrics: Recall@10, MRR, and p50 latency. This is the same grid QueryForge's config recommender runs at deploy time for a new corpus. The results below are what the recommender is trained to replicate: route conservatively on latency, aggressively on recall, and never pay the reranker tax for query types where the precision gain does not justify it.

ConfigαRerankerRecall@10MRRLatency p50
Dense only1.00off0.610.540.9s
BM25 only0.00off0.580.510.4s
Hybrid (fixed)0.55off0.740.661.1s
Hybrid + decompose0.40 (adaptive)off0.810.722.1s
Hybrid + decompose + rerank0.40 (adaptive)on0.920.792.5s

Dense-only retrieval — the default that most enterprise RAG systems ship at launch and never revisit — achieves Recall@10 of 0.61. QueryForge's full pipeline reaches 0.92: a 31-percentage-point improvement that represents the difference between a system that misses 39% of required evidence on multi-hop queries and one that misses 8%. The reranker adds 400ms of latency for a 7-point recall gain over decompose+hybrid alone — the routing table applies it only to multi-hop and entity-scoped queries, where that tradeoff is justified. For single-hop queries, the system routes to dense-only in under 700ms and never pays costs that the query type doesn't warrant.

6.3 MLOps lifecycle for RAG optimization
Figure 6
Data → experiment → evaluate → recommend → deploy, with drift feeding back to data
Data corpus + chunk config Experiment strategy × α grid Evaluate recall@k · MRR · p50 Recommend config_recommendation Deploy Cloud Run, versioned drift detected in Firestore query_logs → re-run the grid
The loop closes through Firestore, not a separate MLOps platform: query_logs is both the audit trail and the drift-detection input for the next experiment cycle.

§7 Google Cloud Architecture

The architecture is Google-native by deliberate constraint, not default. Every component was selected because it fits inside the Always-Free monthly allotment of a shared billing account — and because the Always-Free boundary is hard: anything outside it risks a bill that could propagate to three other applications this project must never affect. The service map below is the production topology. The same container, the same configuration, and the same cost envelope that runs the demo is what production scale-out looks like — the graduation path replaces Firestore Vector Search with Vertex AI Vector Search and adds the paid Gemini tier, but the application code does not change.

7.1 Service map
Interface
REST API (POST /v1/optimize)
Python SDK
OpenAPI schema
Cloud Monitoring dashboards
Intelligence
Gemini 2.5 Flash-Lite (Dev API)
all-MiniLM-L6-v2 (self-hosted)
rank-bm25 (self-hosted)
ms-marco-MiniLM-L-6-v2 (self-hosted)
Orchestration
FastAPI · asyncio · Python 3.11
Cloud Run (scale-to-zero)
Cloud Build
Artifact Registry
Data & governance
Firestore (vector search + logs)
Cloud Storage (corpus + weights)
Secret Manager (API key)
Cloud Billing Budgets
7.2 IAM & security

The Cloud Run service account is scoped to least privilege: roles/datastore.user (Firestore read/write), roles/storage.objectViewer (corpus bucket, read-only), roles/secretmanager.secretAccessor (Gemini API key retrieval), and roles/run.invoker for authenticated callers. No third-party vector database, SaaS tool, or external hosting provider is in the request path — the only outbound network call from the container is to the Gemini Developer API. This matters for enterprise deployability: a system whose data path stays within a single vendor boundary is a fundamentally different compliance conversation than one that routes through multiple external services. It is worth being direct about the one genuine trade-off: on the Gemini Developer API free tier, Google may use inputs and outputs to improve its models. Enabling billing on the API opts out of that data use. For a $0.01-capped portfolio project, that is an accepted limitation. For an enterprise deployment handling confidential documents, it is the first thing to change — and ADR-004 says so plainly rather than burying it.

§8 Cost & Adoption Case

Two cost models, both grounded in cited sources: the business cost of the retrieval gap QueryForge closes, and the running cost of the Google Cloud solution itself. Every figure below is sourced — where a number is a rough estimate rather than a primary figure, that's stated plainly rather than dressed up as precision.

8.1 Problem cost — why "good enough" retrieval is expensive
MetricValueDetailSource
Lost productivity, 1,000 knowledge workers$5.7M/yrWorkers find needed information only ~56% of the timeIDC via Coveo, 2014
Time spent searching2.5 hrs/day≈30% of the workday, $80K/yr knowledge-worker cost baselineIDC, "The High Cost of Not Finding Information"
Time spent searching (recent)1.8 hrs/day≈23% of productive hours, 2025 remeasurementMcKinsey via Copernic, 2025
Global cost of AI hallucinations$67.4B (2024)Projected ~$112B for 2025 as enterprise AI adoption scalesAllAboutAI 2025, via Holm Intelligence Partners
AI-output verification tax~$14,200/employee/yr4.3 hrs/week per employee spent checking AI outputForrester, "Enterprise AI Cost Analysis," 2025
Manual RAG tuning cost$4,500–$10,500Chunking strategy + hybrid search + metadata filtering, one-time, per corpusStratagem Systems, 89 production RAG deployments, 2026
Enterprises with ≥1 RAG hallucination incident67%Of enterprises running production RAG, in the past year — RAG narrows the hallucination problem, it doesn't close itGartner 2026 survey, via NeuralWired
Named incident — why retrieval quality is the fix, not a bigger model
In October 2025, Deloitte refunded part of an AU$440K (~$290K USD) contract with the Australian government after a delivered report was found to contain AI-fabricated citations. (AP, October 2025, via Medium) The most-cited finding across 2025–2026 RAG research is that this class of failure is "overwhelmingly a retrieval problem, not a generation problem" — the model reasons correctly over the wrong chunk. (Seekr, "The Hallucination Tax," 2026) That is precisely the failure mode §1 names and §3 routes around — QueryForge's bet is that better retrieval selection is cheaper than better verification after the fact.
8.2 Solution cost — the Always-Free ledger
ComponentAlways-Free allowanceQueryForge projected usageCostSource
Cloud Run2M requests · 360K GiB-sec · 180K vCPU-sec per billing account/moDemo traffic, scale-to-zero when idle$0.00Cloud Run pricing
Firestore1 GiB storage · 50K reads / 20K writes / 20K deletes per day, per projectSmall demo corpus + query log, well under daily caps; KNN vector search billed 1 read per 100 index entries scanned$0.00Firestore pricing
Cloud Storage5 GB (US regions), 5K Class A / 50K Class B opsCorpus files + self-hosted model weights$0.00Cloud Storage pricing
Cloud Build120 build-minutes/dayOne container build per deploy$0.00Cloud Build pricing
Artifact Registry0.5 GB storageSingle container image$0.00Artifact Registry pricing
Gemini Developer API~15 RPM / ~1,500 RPD / 1M TPM, per project, for Flash-LiteClassifier + decomposer + HyDE calls$0.00 — not billed through Cloud Billing at allGemini API pricing · rate limits, TokenMix 2026
Total actual spend$0.00, capped at $0.01
Cloud Run's Always-Free allotment pools per billing account, not per project — QueryForge's share is kept conservative by design so the other three apps on the same account keep theirs. Firestore's free quota is per project, which is the one allotment QueryForge does not have to share.
8.3 The budget guard

A hard cap only means something if it enforces itself before a bill is generated. The first design for this used a reactive Cloud Billing Budget → Pub/Sub → Cloud Function kill switch. Building it surfaced a problem: Google's own billing data lags by at least 24 hours, which makes any billing-data-driven trigger structurally too slow to catch a $0.01 overspend before it happens — by the time it fires, the overspend already occurred.

Enforcement path — revised
Every Gemini call now passes through a Firestore-transactional spend guard before it's made: a running monthly total is checked against the $0.01 cap, using published per-token pricing, with zero dependency on GCP's billing pipeline or its lag. The original Cloud Billing Budget ($0.01, project-scoped) is kept as an independent secondary tripwire — defense in depth in case the guard itself has a bug, not the primary safeguard. See ADR-006 (revised) and build/service/budget_guard.py in the repository.
8.4 QueryForge vs. alternative approaches
ApproachAnnual costRetrieval routingExplainabilityNotes
QueryForge (this build)~$0/yr✓ 5-way classifier, adaptive α✓ classifier_explanation on every callBounded by Always-Free ceilings — see §12 MVP Scope
Manual RAG tuning (in-house)$4,500–$10,500 one-time (Stratagem 2026)~ Fixed config, hand-tuned per corpus✗ No routing rationale returnedRe-tuning needed whenever the corpus shifts
Glean / managed enterprise search$8K–$30K/yr (est., per-seat)~ Proprietary, vendor-controlled~ Partial, product-dependentStrong UX, but retrieval logic is not inspectable or self-hostable
Google Vertex AI Search (managed)$8K–$30K/yr (est.)~ Managed, multi-tenant~ PartialA different product from what QueryForge does — Vertex AI Search is a managed, multi-tenant enterprise search product; QueryForge calls the raw Gemini API from a backend we control. Worth being precise about — the two get conflated often.
Do nothing (single dense embedding)$0/yr direct, but see §8.1✗ None✗ NoneThe baseline row in §6.1 — cheapest to run, most expensive in downstream errors
Limitations of this cost model
The problem-cost figures in §8.1 are industry averages, not measurements of any specific deployment — actual exposure depends on corpus size, query volume, and how much of an organization's error rate is attributable to retrieval versus other causes, which is not separable from public data. The alternative-approach costs marked "est." are directional, built from public per-seat pricing ranges, not vendor quotes. What is not an estimate: QueryForge's own Google Cloud spend, which is measured against real, cited pricing pages and is $0.00 at demo scale.

§9 Deployment

Single-command deploy to Cloud Run, followed immediately by the budget cap — the cap is treated as part of the deployment, not an optional afterthought.

deploy.sh
gcloud · single project · Always-Free
# build + deploy the container, capped at 3 instances, 1Gi memory
gcloud run deploy queryforge \
  --source . \
  --region us-central1 \
  --max-instances 3 \
  --memory 1Gi \
  --allow-unauthenticated \
  --set-secrets GEMINI_API_KEY=gemini-api-key:latest

# hard budget cap — scoped to THIS project only
# does not touch the shared billing account or the other 3 apps on it
gcloud billing budgets create \
  --billing-account=$BILLING_ACCOUNT_ID \
  --display-name="queryforge-hard-cap" \
  --budget-amount=0.01USD \
  --threshold-rule=percent=1.0 \
  --filter-projects=projects/$QUERYFORGE_PROJECT_ID

§10 Architecture Decision Records

Architecture Decision Records are the unit of engineering judgment in this document. Each ADR below records a decision that had real consequences: what was chosen, what was seriously considered and rejected, the reasoning that separated them, and the consequences that were accepted. They are not post-hoc justifications for decisions already made on intuition. They are the working record of a design process that started with a technically sound multi-vendor stack, encountered two hard constraints — 100% Google Cloud, $0.01 ceiling on a shared billing account — and had to rebuild every layer from first principles. ADR-006 is included with its revision history intact because the original decision was implemented, found to be structurally inadequate, and replaced. That revision is not a failure to document. It is the point of the format.

ADR-001
Google Cloud-only architecture over open-source, multi-vendor stack
Accepted
Date
2026-07-09
Context
The original design used ChromaDB, local HuggingFace inference for embeddings and reranking, and Hugging Face Spaces for hosting. This deployment runs on a Google Cloud free-tier account with zero credits, sharing a billing account with three other apps. A multi-vendor stack adds operational surface area with no benefit on this constraint set.
Decision
Rebuild on Google Cloud only: Cloud Run for compute, Firestore for persistence and vector search, Cloud Storage for corpus/weights, and the Gemini Developer API for the one LLM dependency.
Considered
chosen
Google Cloud-only — single vendor, single IAM boundary, every managed service has an Always-Free tier that covers demo scale.
rejected
Keep original multi-vendor stack — Hugging Face Spaces hosting and ChromaDB have no committed zero-cost guarantee compatible with a $0.01 ceiling.
Consequences
Self-hosted open-weight models remain in the design (ADR-003) — "Google Cloud-only" means the infrastructure vendor, not that every model call must be a managed API.
ADR-002
Firestore Vector Search over ChromaDB or Vertex AI Vector Search
Accepted
Date
2026-07-09
Context
The retrieval layer needs a persistent vector index. Cloud Run's local disk is ephemeral, so self-hosted ChromaDB would lose its index on every cold start. Vertex AI Vector Search is Google-native but has no Always-Free tier — its cheapest deployed index runs continuously and accrues an hourly charge regardless of query volume.
Decision
Use Firestore (Native mode) with vector search for both the dense index and the query/config store. Persists across cold starts, and its free daily quota comfortably covers a portfolio-scale demo corpus.
Considered
chosen
Firestore Vector Search — native GCP, Always-Free tier, persists across Cloud Run scale-to-zero.
rejected
Self-hosted ChromaDB — no free persistent disk on Cloud Run; needs a separate VM or Filestore volume, both outside Always-Free.
rejected
Vertex AI Vector Search — the deployed index has an always-on hourly cost with no free tier; incompatible with a $0.01 cap. Documented as the production upgrade path in §12.
Consequences
Corpus size for the zero-cost demo is bounded by Firestore's Always-Free storage — see §13 Limitations.
ADR-003
Self-hosted open-weight models over Vertex AI Embeddings / Prediction API
Accepted
Date
2026-07-09
Context
Dense embedding and cross-encoder reranking need a model somewhere in the request path. Vertex AI's Text Embeddings API and hosted Prediction endpoints are Google-native, but both are metered per call with no meaningful free tier.
Decision
Bundle all-MiniLM-L6-v2 and ms-marco-MiniLM-L-6-v2 as open weights inside the Cloud Run container image and run inference on the container's own CPU — compute time is Always-Free up to 180K vCPU-sec/month instead of a per-call API fee.
Considered
chosen
Self-hosted, in-container inference — zero marginal cost per query.
rejected
Vertex AI Text Embeddings API — billed per 1K characters with no free tier; incompatible with a $0.01 cap at any real query volume.
Consequences
Reranker adds ~600ms latency on Cloud Run's free-tier CPU — see §13 Limitations.
ADR-004
Gemini Developer API over the Vertex AI Gemini endpoint
Accepted
Date
2026-07-09
Context
Gemini is reachable two ways: through Vertex AI (billed to the GCP project's Cloud Billing account) or through the Gemini Developer API at aistudio.google.com (billed on a separate, independently free-tiered quota tied to the API key). QueryForge's classifier, decomposer, and HyDE fallback all need to stay off the $0.01-capped account.
Decision
Call Gemini exclusively through the Gemini Developer API using an API key stored in Secret Manager. The Vertex AI Gemini endpoint is not used anywhere in this design.
Considered
chosen
Gemini Developer API — free tier billed independently of Cloud Billing.
rejected
Vertex AI Gemini endpoint — same model family, but every token generated is metered against Cloud Billing.
Consequences
Throughput is capped at the Developer API's free-tier limits — the primary scaling bottleneck in §13. Free-tier calls may also be used by Google to improve its models; production deployments that need to opt out enable billing on the API, which is a different account decision from the $0.01 Cloud Billing cap this ADR is about. Also worth noting: the model originally specified here (Gemini 2.0 Flash-Lite) was deprecated and shut down June 1, 2026 — this document was updated to Gemini 2.5 Flash-Lite, and future revisions should expect the same churn.
ADR-005
Cloud Run over GKE Autopilot
Accepted
Date
2026-07-09
Context
QueryForge needs a container runtime. GKE Autopilot is the Google-native alternative, but even a minimal cluster bills for a base management fee and any always-on node, regardless of traffic.
Decision
Deploy to Cloud Run with max-instances capped and scale-to-zero enabled. No traffic, no running instance, no charge.
Considered
chosen
Cloud Run — scale-to-zero, 2M requests/month Always-Free, no cost floor.
rejected
GKE Autopilot — cluster management fee and node cost accrue even at zero traffic.
Consequences
Cold starts add latency after idle periods — an accepted trade-off, documented rather than hidden.
ADR-006
Project-scoped, self-enforcing budget guard over a billing-data-reactive kill switch
Revised
Date
2026-07-09 (revised from the original same-day decision, after implementation)
Context
The common pattern for a hard cost cap disables billing (or throttles a service) once a Cloud Billing Budget threshold is crossed. QueryForge shares its $10 billing account with three other apps, so the original decision scoped that pattern to the project only. Building it surfaced a bigger problem: Google's own billing data lags by at least 24 hours. A billing-data-reactive trigger — Pub/Sub push or scheduled poll, project-scoped or not — cannot enforce a $0.01 cap in real time. By the time it fires, the overspend already happened.
Decision
Self-enforce the cap in application code. Every Gemini call now passes through build/service/budget_guard.py first — a Firestore-transactional check against a running monthly total, using published per-token pricing, with zero dependency on GCP's billing pipeline. The original project-scoped Cloud Billing Budget is retained as an independent secondary tripwire, not the primary safeguard.
Considered
chosen
Self-enforcing Firestore-transactional guard — sub-second enforcement, no dependency on billing-data latency; Cloud Billing Budget kept as defense-in-depth only.
superseded
Project-scoped Pub/Sub → Cloud Function kill switch (original decision) — correct in scope, but too slow to be the primary guard given the billing-data lag.
rejected
Account-level "disable billing" kill switch — a QueryForge cost spike would take down three unrelated apps as collateral damage, on top of still being too slow.
Consequences
One extra Firestore transaction per Gemini call — inside the Always-Free daily write quota at demo volume. The cost estimate is derived from token-usage metadata against published pricing, not GCP's actual invoice; treated as a hard-stop guard, not a billing record.

§11 Data Schema

Firestore holds three collections. The schema is not incidental: corpus_chunks stores chunk version and effective date as indexed fields, which is what makes temporal queries retrievable via BM25 date-field boost rather than requiring a separate metadata store. query_logs is the drift-detection input for the MLOps loop in §6.3 — every routing decision, alpha value, sub-query set, and latency measurement is logged here, and this log is what feeds the next experiment cycle when query distribution shifts. All three collections fit inside the Always-Free daily read/write quota at demo scale; the graduation path to production replaces the Firestore vector index with Vertex AI Vector Search (see ADR-002) but keeps the same log schema.

corpus_chunks/{doc_id}
Firestore · vector-indexed
// One document per chunk. `embedding` is indexed for Firestore Vector Search.
{
  "doc_id":          "policy_travel_v3_chunk_014",
  "corpus_id":       "acme-hr-corpus",
  "content_type":    "policy_docs",
  "chunk_strategy":  "section-aware",
  "chunk_version":   "v3",
  "text":            "§4.2 Non-standard payment terms require...",
  "embedding":       [0.0123, -0.0871, ...],   // all-MiniLM-L6-v2, 384-dim
  "metadata": {
    "effective_date": "2026-02-01",
    "source_uri":     "gs://acme-corpus/policy_travel_v3.pdf"
  }
}
query_logs/{doc_id}
Firestore · Always-Free: 20K writes/day
// One document per /v1/optimize call.
{
  "query_id":        "qlog_00482",
  "query_text":      "What approval is required for vendor contracts over $50K...",
  "classifier_type": "multi-hop-entity",
  "confidence":      0.91,
  "sub_queries":     ["vendor contract approval threshold $50K", "..."],
  "alpha":           0.40,
  "reranked":        true,
  "latency_ms":      2340,
  "timestamp":       "2026-07-09T14:02:11Z"
}

§12 MVP Scope & Build Boundaries

Everything below runs inside the Always-Free tier and the $0.01 cap. Nothing here required a paid tier to demonstrate.

Fully implemented
Query classifier + confidence fallbackGemini 2.5 Flash-Lite · Dev API free tier
Sub-query decomposition + HyDEmulti-hop / low-similarity, live routing
Dense + sparse + hybrid retrievalFirestore Vector Search + self-hosted BM25
RRF fusion + config recommenderpure Python · Firestore-logged
Budget guardFirestore-transactional, self-enforcing · project-scoped Cloud Billing Budget as secondary tripwire
Demo-scoped
Cross-encoder rerankerlive, but only on a small cached demo corpus to stay inside free CPU-seconds
Firestore Vector Search corpuscapped at ≤50MB to stay comfortably inside free storage
Front-end simulator (§5)scripted stage timings against real pipeline benchmarks, not a live backend call — same pattern used across this portfolio's other design docs
Deferred to production
Step-back & rewrite variantsclassifier route defined (§3.1, Fig. 3) but not corpus-evaluated yet
Vertex AI Vector Searchfor corpora beyond Firestore's free ceiling — see ADR-002
Paid Gemini tierabove ~5K queries/day, once free-tier RPM is the bottleneck

§13 Limitations & Known Issues

These are real limitations, not caveats added for completeness. Each one has a mitigation where one exists, and a clear statement of what the consequence is where it does not. An enterprise system that cannot articulate its own failure modes is a more dangerous system than one that can. The graduation path to production for each limitation is documented in §12.

HyDE hallucination riskAn incorrect hypothetical document degrades recall. Mitigated by running HyDE alongside standard dense retrieval and letting RRF demote uncorroborated results; only activates below 0.65 similarity.
Classifier miscategorizationA multi-hop query misclassified as single-hop reproduces the exact failure QueryForge exists to prevent. Confidence below 0.75 always falls back to hybrid+decompose.
Reranker latencyThe cross-encoder adds ~600ms on Cloud Run's free-tier CPU. Only applied to multi-hop queries where the precision gain justifies it.
Gemini free-tier rate limits~15 RPM / ~1,500 requests per day is the scaling bottleneck. Production deployments above ~5K queries/day need the paid Gemini tier (ADR-004).
Free-tier data useGoogle may use Gemini Developer API free-tier inputs/outputs to improve its models. Enabling billing opts out — a real trade-off for confidentiality-sensitive corpora that a $0.01-capped project can't casually take.
Model deprecation churnGemini 2.0 Flash-Lite, the model this design originally targeted, was deprecated and shut down June 1, 2026. This document now targets 2.5 Flash-Lite; free-tier model names should be expected to change again.
Firestore Always-Free ceilings1 GiB storage and 20K writes/day cap the demo corpus size and query-log volume (ADR-002).
Shared billing accountThe $0.01 cap is enforced at the project level specifically so a cost spike here cannot cascade into the three other apps on the same account (ADR-006).

§14 Glossary

RAG — Retrieval-Augmented Generation
Retrieving relevant context from a corpus and passing it to an LLM alongside the user's query, so the model's answer is grounded in that context rather than parametric memory alone.
RRF — Reciprocal Rank Fusion
A rank-based method for merging results from multiple retrieval strategies. Scores each document by the sum of 1/(k+rank) across every list it appears in, so it never has to reconcile incompatible score scales.
BM25
A sparse, term-frequency-based ranking function. Strong on exact matches — entity names, contract numbers — where dense embeddings tend to blur precise identifiers into a general semantic neighborhood.
HyDE — Hypothetical Document Embeddings
Generating a plausible hypothetical answer document with an LLM, then embedding that document (instead of the raw query) as the search vector.
α-weighting
The blend factor between dense and sparse scores in hybrid retrieval: score = α·dense + (1−α)·BM25. QueryForge sets α per query type based on published grid-search results.
Cross-encoder reranker
A model that scores a (query, document) pair jointly, rather than comparing independently-embedded vectors. Used selectively on multi-hop queries only.
Firestore Vector Search
Google Cloud's native vector-similarity search over Firestore documents. Used here as the dense index because it persists across Cloud Run's scale-to-zero cycles and fits inside the Always-Free daily quota at demo scale.
Always-Free tier
The set of Google Cloud service allotments available every month at no charge, independent of any free-trial credit. Some pool per billing account (Cloud Run); others are per project (Firestore) — see §8.2.
Gemini Developer API vs. Vertex AI Gemini
Two ways to call the same Gemini models. The Developer API (aistudio.google.com, API-key auth) has an independent free-tier quota, billed separately from Cloud Billing. The Vertex AI endpoint is billed through Cloud Billing per token. QueryForge uses the Developer API exclusively (ADR-004).
Budget guard
QueryForge's self-enforcing spend cap: a Firestore-transactional check before every Gemini call, using published per-token pricing. Chosen over a reactive Cloud Billing Budget alert because Google's billing data lags too much to enforce a $0.01 cap in real time (§8.3, ADR-006).