Skip to main content
Go from zero to a production AI assistant that answers “why was this built this way?” in four phases. Start with one file and a real recall query. End with a FastAPI backend that ingests GitHub, PRs, Slack, and RFCs, then generates GPT-4o answers grounded in your codebase.
How this guide is structured. Each phase ends with something that works. Phase 0 is a complete minimal system in under 10 minutes. Phases 1–3 are progressive upgrades. You never need to redo what came before.
All code in this cookbook is real. Base URL is https://api.hydradb.com. Get your API key at app.hydradb.com.

Prerequisites

Required knowledge: Python basics, REST APIs, environment variables
Required tools:
  • HydraDB API key
  • Python 3.11 or 3.12 (python --version)
  • OpenAI API key (for generating answers in Phase 3)
  • pip install hydradb-sdk openai

What You’ll Build

By the end of this cookbook, you’ll be able to:
  • Upload any codebase file into HydraDB and get grounded answers to “why was this built this way?”
  • Progressively ingest GitHub files, PRs, Slack threads, and RFCs across four phases
  • Build a FastAPI backend that answers developer questions using full_recall + GPT-4o
  • Add a VS Code extension trigger to query HydraDB from inside your editor

Phase 0 - Minimal Working System · 5–10 minutes

The only goal is to see a real recall response from a real file you uploaded. No multi-source ingestion, no backend server, no VS Code extension - just the four API calls that prove the pipeline works end-to-end. Goal: You will run one recall query and see actual results.
⚠️ Do Phase 0 first, even if you plan to skip ahead. Every later phase assumes the tenant exists and indexing works. Running Phase 0 takes under 10 minutes and eliminates the most common failure modes.

Prerequisites

You need three things before writing any code:
  1. A HydraDB API key. Email [email protected] or book a demo at hydradb.com. You’ll receive a key that starts with hdb_.
  2. Python 3.10+. Run python3 --version to check.
  3. The HydraDB SDK. Run pip install hydradb-sdk python-dotenv.
Create config.py in the project root. This is the canonical configuration used by every script in this guide:
Authorization header must be exact. Authorization: Bearer hdb_abc123 - capital B, one space, no quotes. Anything else returns 401.

Step 1 - Create a Tenant

What: Registers your top-level namespace. Tenant creation is asynchronous - infrastructure provisions in the background. Do not upload files until polling confirms it is ready. Endpoint: POST /tenants/create - Async, poll /tenants/infra/status before ingesting
Expected output:
If it fails: 401 - API key wrong or missing Bearer prefix. Confirm .env has HYDRA_DB_API_KEY=hdb_abc123 with no surrounding quotes.

Step 2 - Upload One File

What: Uploads a single file using multipart form-data to /ingestion/upload_knowledge - the recommended beginner path. HydraDB handles chunking, embedding, and graph-node creation automatically. The returned source_id is what you use in Step 3 to verify indexing.
💡 Two ingestion modes exist. This step uses multipart file upload via /ingestion/upload_knowledge - the tested beginner path. An advanced app-source mode (used in Phases 1–2) supports structured IDs and explicit graph relations. Use file upload here first.
First, create a sample document:
Expected output:
The exact value of source_id depends on HydraDB’s internal file registration. Copy the value returned and use it in Step 3. If it fails:
  • 404 Tenant does not exist - Step 1 not complete, or tenant_id mismatch.
  • 400 / missing files - Confirm the field name is files (not file), and that Content-Type is NOT manually set in headers.

Step 3 - Verify Indexing

What: Polls /ingestion/verify_processing using the source_id returned in Step 2. HydraDB returns a statuses array; you read statuses[0].indexing_status. Querying before this reaches "completed" returns empty results with no error - the most common beginner confusion. Replace YOUR_FILE_ID_HERE with the source_id from Step 2:
Expected output:

Step 4 - Run Your First Recall Query

What: Sends a POST to /recall/full_recall and receives a chunks array. Each chunk has a chunk_content field - this is the text you will later pass to GPT-4o as context. This is the end-to-end proof that tenant, ingestion, indexing, and retrieval all work. Validated first recall - minimal working request:
Expected output:
If it fails: chunks: [] - Indexing is not yet complete. Wait 30 seconds and retry. Re-run Step 3 to confirm indexing_status: completed.
Phase 0 complete. The same /recall/full_recall endpoint - with the same minimal three-field body - is what every later phase uses. You’ll only add parameters, not change the structure. The chunks array and chunk_content field are now your canonical recall response objects.
What you just built: You now have a working retrieval system. HydraDB can store your content, index it, and return the most relevant chunks - each with a chunk_content text field and a relevancy_score - for a question. The missing layer is the reasoning backend that takes those chunks and turns them into a readable, cited answer.

Phase 1 - Improve Retrieval · 15–20 minutes

Ingest multiple files with structured app-source metadata and tune recall parameters. At the end of this phase, recall queries return more relevant chunks across many documents. Goal: 50+ files indexed with metadata, scoped recall working.

How Retrieval Works

Before writing more ingestion code, it’s worth understanding what HydraDB actually returns and how to interpret it. This mental model applies to every phase. Chunks vs. sources: When HydraDB indexes a document, it splits the content into overlapping chunks. Each chunk is embedded independently and stored as a node in the context graph. When you call /recall/full_recall, you get back a chunks array. Iterate this array and read chunk_content from each item - that is the text you pass directly to your LLM as context. The response also includes a sources array - a deduplicated list of the original documents that contributed chunks. Use sources for citation labels; use chunks for the actual LLM context. Anatomy of a recall chunk - field reference: How to interpret the relevancy_score: Scores are relative within a single response - they indicate ranked relevance for your specific query, not absolute confidence. How graph context changes recall: When you add "graph_context": true and "search_apps": true to your recall request, HydraDB can use explicit relations[], provider IDs, parent links, threads, and actors around high-scoring chunks. A source file chunk can pull in the PR that last changed it. That PR can pull in the RFC or Slack thread it referenced. This multi-hop traversal is what makes “why” questions answerable.
💡 Always safe to add to Phase 0. "graph_context": true has no downside with a single file and no relations - it returns the same result. Turn it on now.

P1 · Step 1 - Batch Upload with Explicit IDs (App-Source Ingestion)

What: Switches to multipart app-source ingestion via /ingestion/upload_knowledge with app_knowledge as a JSON-stringified form field. This gives you full control over stable IDs, provider namespaces, structured fields, metadata, and (in Phase 2) explicit relations[]. The {repo_name}/{relative_path} external ID convention is required for Phase 2 linking.
⚠️ Advanced path - stricter validation. App-source ingestion requires well-formed payloads with id, tenant_id, sub_tenant_id, kind, provider, external_id, and fields on every item. Malformed requests are rejected outright. Use the file upload path (Phase 0 Step 2) if you just want to get content indexed quickly. Use app-source ingestion when you need stable IDs and graph relations.
Expected output:

P1 · Step 2 - Metadata for Scoping and Citations

What: App-source identity belongs in provider and external_id; searchable structure belongs in fields; stable scoping values belong in metadata. Use metadata for repo, channel, space, project, doc type, or other filters you want available at recall time.

P1 · Step 3 - Tuned Recall with Graph Context


Phase 2 - Multi-source Context · 20–30 minutes

Connect GitHub source files to their pull requests, Slack decision threads, and internal wikis. Use explicit relations[] with provider-scoped external_id targets to guarantee graph edges. When a developer asks “why?”, the answer now travels across all four source types in a single query. Goal: A “why” question returns code + PR + Slack + RFC in one response. Pipeline: Source files → Pull requests → Slack threads → Wikis & RFCs → Graph built
💡 Explicit vs. automatic graph edges. Setting explicit relations[] guarantees a graph link every time. HydraDB may also try to extract relationships from content, but this is best-effort. Always use explicit relations for links that matter.

Why Each Source Type Matters

A developer asking “why does the auth middleware skip token validation for internal IPs?” cannot be answered by any single source. Here is what each source type contributes: A normal code search tool answers what a function does. It is weak at answering why it exists because the rationale almost never lives in the code file alone. HydraDB becomes powerful when you index all the places where engineering intent is recorded:
  • Code tells you what is running now.
  • Pull requests tell you why the code changed and what alternatives were discussed.
  • Slack threads capture informal trade-offs, approvals, and urgency.
  • RFCs and ADRs record the formal decision and long-term architectural reasoning.
When those sources share stable IDs and explicit relations, the assistant can move from a source file to the PR that introduced it, to the Slack thread that approved it, to the ADR that documented it.
💡 Adaptation checklist. Pick a stable ID scheme first, choose the minimum set of high-signal sources second, then expand. Good IDs and a small number of trustworthy sources beat a huge but messy index every time.

P2 · Step 1 - Ingest GitHub Source Files with Consistent IDs

What: Ingests source files using the {repo_name}/{relative_path} ID convention. These external IDs are what PR ingestion will reference in relations[].target.external_id. The IDs must match exactly - myrepo/auth/middleware.py, not auth/middleware.py. Use ingest_directory() from Phase 1 Step 1. If you already ran Phase 1, your files are already indexed.

P2 · Step 2 - Ingest Pull Requests with Explicit Graph Relations

What: Fetches merged PRs and turns each into one document: title + description + review comments + changed-file list. The relations[] list guarantees graph edges form between the PR and every source file it changed. Before: A GitHub token with repo scope. Create at github.com/settings/tokens → New classic token → check repo → add to .env as GITHUB_TOKEN, GITHUB_OWNER, GITHUB_REPO.

P2 · Step 3 - Ingest Slack Threads

What: Ingests each Slack message as a typed message app source. Messages share thread_id, and replies set parent_id to the root thread timestamp. Architecture channels, post-mortems, and decision threads are the highest-value sources - these capture approvals that never get written up anywhere else. Before: A Slack export ZIP. Go to your-workspace.slack.com/services/export (requires admin), export, and unzip. Structure: {export_dir}/{channel_name}/{YYYY-MM-DD}.json.

P2 · Step 4 - Ingest Wikis & RFCs

What: Ingests Architecture Decision Records, RFCs, Confluence pages, and Notion wikis. Tag each with doc_type so recall can be scoped to decisions-only when that is what a question requires.

P2 · Step 5 - Run a Multi-source Graph Query

Expected output:
Phase 2 complete. A “why” question now returns multi-hop retrieved context. Phase 3 wraps this in a production FastAPI server with your own GPT-4o answer generation layer.

Phase 3 - Backend & Answer Generation · 25–35 minutes

Build a production FastAPI server. HydraDB is the memory layer - it retrieves ranked chunks via full_recall(). GPT-4o is the reasoning layer - it reads those chunks and writes a grounded, cited answer. The two layers are kept strictly separate so they can be debugged and improved independently. Goal: POST /chat and POST /ask → HydraDB chunks → GPT-4o → answer.

Backend Architecture

The flow through the backend is:
  1. Question - POST /chat or /ask
  2. Recall - full_recall() → chunks
  3. Context block - chunk_content assembled
  4. GPT-4o - writes grounded answer
  5. Response - answer + citations
Why split retrieval and reasoning? Because they fail differently. Weak recall means you improve ingestion, metadata, or graph links. Weak answers mean you improve prompt construction or model behavior. Keeping the layers separate means you can isolate which side is the problem. File structure:
Architecture summary:
  • config.py - Loads .env, exposes HYDRA_DB_API_KEY, TENANT_ID, client (HydraDB SDK instance), OPENAI_API_KEY, and recall defaults. Every other file imports from here only.
  • hydra_client.py - full_recall() is the HydraDB retrieval path. It returns chunks, sources, and graph context; prompt formatting and OpenAI calls stay in answer.py. Imported by → recall.py, app.py.
  • recall.py - recall_context() calls full_recall() and returns the raw payload. build_context_block() extracts chunk_content from each chunk and assembles a formatted context string for the LLM. Imported by → app.py.
  • answer.py - Takes the context block from recall.py, formats the system + user prompt, and calls the OpenAI streaming API. Contains all anti-hallucination rules. Never calls HydraDB. Imported by → app.py.
  • app.py - Two endpoints: POST /chat streams NDJSON token-by-token - ideal for a web or IDE frontend. POST /ask returns a complete JSON response with answer, sources, and chunks - easier for Postman testing. Both use the same recall_contextbuild_context_blockstream_answer pipeline.
Install dependencies for Phase 3:
Add to .env:
Create the empty package init file:

backend/config.py

Centralises all credentials. Use HYDRA_DB_API_KEY as the variable name for your HydraDB key. All other backend files import from here.

backend/hydra_client.py

full_recall() is the HydraDB retrieval path. It returns ranked chunks and source metadata; it does not format prompts or decide routing.

backend/recall.py

recall_context() calls full_recall() and returns the raw HydraDB payload. build_context_block() reads the chunks array, extracts each chunk_content field, and assembles them into a single formatted string that is passed to the LLM. The sources array from the payload is used separately by app.py for citation output.
💡 Production upgrade: score filtering. build_context_block is intentionally simple for the first backend. For production, add a relevancy_score filter to drop low-quality chunks before building the context string - the same pattern used in Phase 1’s build_context() helper.

backend/answer.py

Takes the context block string from recall.py and turns it into a grounded GPT-4o answer. This is the only file that touches OpenAI. If the context string is empty, it returns a safe fallback without calling the model at all - this is the primary hallucination guard.

backend/app.py

The FastAPI server exposes two endpoints. POST /chat streams NDJSON - best for a web frontend or VS Code extension. POST /ask returns a complete JSON response - easier for Postman testing and best for API consumers that don’t stream. Both use the same recall_contextbuild_context_blockstream_answer pipeline.
💡 Want WHY_SIGNALS routing? Once this basic backend is working, add a question classifier that adjusts full_recall() parameters for “why” questions, such as lower alpha, higher max_results, and graph_context: true. Keep final answer generation in your app layer.

Answer Generation Flow

Understanding the exact data path prevents debugging confusion. build_context_block() does the following in order:
  1. Read the chunks array from the recall payload. /recall/full_recall returns {"chunks": [...], "sources": [...]} - always iterate chunks, never results.
  2. Extract chunk_content from each chunk object. This is the raw text of the chunk - a few hundred tokens of the original document. It is the only required field. Skip any chunk where chunk_content is absent or empty.
  3. Join all chunk_content values with double newlines to form the context body.
  4. Append the sources list from recall_payload.sources in - title (id) format so the model can cite them.
  5. Return the assembled string to stream_answer(). This becomes the user message body inside the OpenAI prompt.
The assembled context block that GPT-4o receives looks like this:

Avoiding Hallucination

LLMs will confidently invent details about your codebase if you don’t actively prevent it. The system prompt in answer.py contains specific rules for each failure mode:
⚠️ Do not raise temperature above 0.2 for this use case. Higher temperature increases creativity, which is the opposite of what you want for accurate, cited answers about specific code.

Running the Server

Expected startup output:
Test the streaming /chat endpoint:
Test the synchronous /ask endpoint (easier for Postman):
Phase 3 complete. You have a production FastAPI backend. HydraDB is the memory layer - full_recall() retrieves ranked chunks. GPT-4o is the reasoning layer - it reads those chunks via stream_answer() and writes a grounded answer. The two layers are intentionally separate so you can debug and improve each independently.

Phase 4 - Productization · Coming next

Ship the assistant to your team. Build the VS Code extension sidebar, set up a daily incremental sync cron job, wire a GitHub Actions workflow for on-push indexing, and monitor recall quality over time. Goal: Team using it daily with a fresh index on every merge. Detailed implementation coming in the next revision. Everything in Phase 4 is packaging around a working Phase 3 server. Planned components: VS Code extension (TypeScript), Webview chat UI, Active-file context injection, Daily cron sync, GitHub Actions on push to main, Incremental re-index, Recall quality monitoring.

If Your Retrieval Quality Is Weak

  • Fix your IDs and metadata. Weak retrieval often starts upstream. Use stable IDs, clear titles, accurate timestamps, and useful metadata like doc_type, repo, and channel.
  • Add explicit relations. If “why” answers feel shallow, it usually means the assistant sees the code but not the documents around it. Add explicit relations[] between code, PRs, Slack, and RFCs.
  • Improve source formatting. A PR with only a title is weak. A PR with title, description, changed files, reviews, and inline comments is strong. Rich documents produce better chunks.
  • Add relevancy_score filtering. The production upgrade to build_context_block is to drop chunks where relevancy_score is below your threshold before assembling the context string.

How to Extend Beyond the First Version

A good progression: Phase 0 recall in the terminal → FastAPI /ask in Postman → FastAPI /chat with a simple web frontend → VS Code sidebar → streaming, incremental sync, caching, usage analytics.

Troubleshooting

401 - Not Authenticated

Cause: The Authorization header is missing or malformed. Most common: the Bearer prefix is absent, quotes surround the key in .env, or there is a leading/trailing space in the key value. Fix: Run python3 -c "from config import HYDRA_DB_API_KEY; print(repr(HYDRA_DB_API_KEY))". The output should be 'hdb_abc123' - no surrounding spaces. Your header must be exactly Authorization: Bearer hdb_abc123.

404 - Tenant Does Not Exist

Cause: You are attempting to upload or query before the tenant was created, or the tenant_id in your request does not match the one you created. Tenant IDs are case-sensitive. Fix: Run python3 phase0/create_tenant.py and wait for ”✓ Tenant ready.” before proceeding. Confirm TENANT_ID in config.py exactly matches what you passed to create_tenant.

404 - Not Found (on recall or ingestion endpoint)

Cause: The endpoint path has a typo, extra slash, or missing segment. Also occurs when querying a sub_tenant_id that has never had data written to it. Fix: Correct paths: /ingestion/upload_knowledge (file upload or app-source batch with app_knowledge), /recall/full_recall, /ingestion/verify_processing. If using sub_tenant_id, confirm at least one batch was successfully uploaded to that sub-tenant first.

400 - Provide at least one of: files or app_knowledge

Cause: For file upload: the field name is wrong (file instead of files), or Content-Type: application/json was manually set (which breaks multipart). For app-source batch: the array is empty or objects are missing required fields. Fix: For file upload: use files={"files": (filename, f, "text/markdown")} and data={"tenant_id": TENANT_ID}. Do NOT set Content-Type manually. For app-source batch: confirm each item has id, tenant_id, sub_tenant_id, kind, provider, external_id, and fields.

Empty Recall - chunks: []

Cause: Indexing is still in progress, or relevancy_score filtering in build_context_block is too aggressive, or the query does not semantically match any ingested content. Fix: Step 1 - Run verify_file() and confirm "indexing_status": "completed" in the statuses array. Step 2 - Run phase0/recall.py directly and print the raw response - check that the chunks key is present and non-empty before any filtering. Step 3 - Check the relevancy_score values on returned chunks.

429 - OpenAI insufficient_quota

Cause: Your OpenAI account has run out of credits or hit its usage limit. This is an OpenAI-side error - HydraDB retrieval may be working perfectly. The error appears in stream_answer() inside answer.py, not in the HydraDB recall step. Fix: Add billing or upgrade your OpenAI plan at platform.openai.com. While debugging, replace stream_answer in answer.py with this temporary debug fallback - it returns the raw retrieved context without calling OpenAI:
💡 General debugging pattern. Add print("STATUS CODE:", resp.status_code) and print("RESPONSE:", resp.text[:500]) immediately after every requests.post() call. See the full error body before doing anything else.

Production Notes

  • Batch size and rate limits. The app-source batch upload path accepts a maximum of 20 source objects per request. Always sleep 1 second between batches. For large repos (1,000+ files), expect ingestion to take 10–30 minutes total.
  • Indexing delays. Indexing is async. Never rely on a fixed sleep; always poll verify_processing until the statuses array shows "indexing_status": "completed".
  • Consistency and upserts. HydraDB upserts by id - re-uploading replaces the existing document. There is a brief window where recall may return stale chunks. Run verify on new IDs before marking a deployment complete.
  • LLM context window. The basic build_context_block assembles all chunk text. For production, add a relevancy_score filter and a character cap (e.g. 12,000 chars) to prevent exceeding GPT-4o’s context window.
  • Graph edge consistency. Graph edges only form if both sides exist and are indexed. Always ingest source files first, then PRs. Re-ingest affected PRs if you add new files after initial ingestion.
  • API key security. The HydraDB API key grants full access to all tenant data. Never commit it to git. Use environment secrets in production. Rotate immediately if you suspect exposure.
  • OpenAI token costs. Each question sends context + system prompt + question to GPT-4o. For a 50-engineer team asking 200 questions/day, budget $2–4/day. Use gpt-4o-mini for factual lookups to reduce cost.
  • Recall quality monitoring. Log the relevancy_score value of each chunk included in a response. If median scores are falling over time, new content may be diluting the index - re-ingest with better metadata and app-source fields. Also confirm the chunks array is non-empty before sending context to GPT-4o.

API Reference

All endpoints used in this cookbook. Base URL: https://api.hydradb.com. Header: Authorization: Bearer YOUR_API_KEY

Create Tenant

POST /tenants/create - Async, poll /tenants/infra/status before ingesting

Upload File - Beginner Path

POST /ingestion/upload_knowledge - Multipart form-data · field name "files" · tenant_id as form field

Upload App Sources - Advanced

POST /ingestion/upload_knowledge - Max 20 items · multipart field app_knowledge (JSON string) · supports typed fields and relations

Verify Indexing

POST /ingestion/verify_processing - Returns statuses[] array · poll until "completed"

Recall - Validated Minimal Request

POST /recall/full_recall - Primary verified path · sub_tenant_id optional · returns chunks[] and sources[]

Multi-hop Recall

POST /recall/full_recall - Use for multi-hop retrieval, then generate the answer in your app layer

Benchmarks

1,200 developer questions across three engineering teams (18–80 engineers, codebases 150k–2.2M lines) compared against naive vector search using identical ingested content.
ℹ️ The 22% accuracy on “why” questions isn’t a tuning problem - it’s structural. Embedding a source file and the RFC that motivated it produces similar-but-unlinked vectors. Without explicit graph edges, the RFC never surfaces when the source file matches. HydraDB makes the connection explicit: every node linked to your query gets traversed, not just nodes that look semantically similar.