Follow this flowchart to create the whole thing yourself in the Azure & Microsoft Foundry portals:
5 Knowledge Sources → 3 Knowledge Bases → agentic retrieval → ONE Foundry agent that attaches all 3 KBs.
Every exact resource name is given. No web app needed — this is the grounding stack the app just calls.
This is the shape from the screenshot: one question → ONE Foundry agent that has three Knowledge Bases
attached under its Knowledge panel. The agent's router-model picks which KB tool(s) to call; each KB internally
runs the agentic-retrieval pipeline (plan → parallel hybrid retrieve → semantic rerank → cited synthesis) across its Knowledge Sources.
flowchart LR
U["HR user asks ONE question"] --> AG
subgraph FDRY["Microsoft Foundry — ONE hosted agent (hriq-hr-agent, model-router)"]
AG["Foundry agent"]
AG --> T1["MCP tool knowledge_base_hrbp"]
AG --> T2["MCP tool knowledge_base_manager"]
AG --> T3["MCP tool knowledge_base_hrops"]
end
subgraph KBS["3 Foundry IQ Knowledge Bases (each: plan → retrieve → rerank → synthesize)"]
KB1["hriq-hrbp-kb"]
KB2["hriq-manager-kb"]
KB3["hriq-hrops-kb"]
end
subgraph SRC["5 Knowledge Sources (Azure AI Search · BM25 + HNSW vector + semantic)"]
S1["hriq-employment-law"]
S2["hriq-hr-policies"]
S3["hriq-job-architecture"]
S4["hriq-learning-development"]
S5["hriq-onboarding-offboarding"]
end
T1 --> KB1
T2 --> KB2
T3 --> KB3
KB1 -->|hybrid queries| S1 & S2 & S3
KB2 -->|hybrid queries| S3 & S4 & S5
KB3 -->|hybrid queries| S1 & S2 & S3 & S4 & S5
AG --> A["ONE cited answer jurisdiction-accurate"]
classDef src fill:#c2410c,stroke:#7c2d12,color:#fff;
classDef kb fill:#0f766e,stroke:#08403b,color:#fff;
classDef fdry fill:#0369a1,stroke:#083344,color:#fff;
class S1,S2,S3,S4,S5 src; class KB1,KB2,KB3 kb; class AG,T1,T2,T3 fdry;
The build order (do it in this sequence)
Bottom-up: you can't make a Knowledge Base before its sources, or a source before its index.
Follow the arrows.
flowchart TB
A["Step 0 Prereqs + sign in + RBAC"] --> B["Step 1 Deploy 2 models chat + embedding"]
B --> C["Step 2 Create 5 indexes HNSW + semantic"]
C --> D["Step 3 Ingest + chunk from sources Blob · SharePoint · SQL"]
D --> E["Step 4 Create 5 Knowledge Sources"]
E --> F["Step 5 Create 3 Knowledge Bases"]
F --> G["Step 6 Create 1 Foundry agent hriq-hr-agent"]
G --> H["Step 7 Attach the 3 KBs (Knowledge panel)"]
H --> I["Step 8 Grant runtime RBAC"]
I --> SEC["Step 8b Country access control security trimming"]
SEC --> J["Step 9 Test in the Playground"]
J --> K["Step 10 (optional) Single-KB variant hriq-kb over all 5"]
classDef s fill:#141d3b,stroke:#5b8cff,color:#eaf0ff;
class A,B,C,D,E,F,G,H,I,SEC,J,K s;
Sign in to the right tenant and give yourself the roles needed to author indexes, sources,
KBs and agents. (The running agent gets its own read-only role in Step 8.)
Sign in (correct tenant matters — HRIQ lives in the microsoft.com tenant)
az login --tenant 16b3c013-d300-468d-ac64-7eda0820b6d3
az account set --subscription 4a751d0a-a8ef-44f2-b08f-05b8d87d959a
⚠️ Do not use "Azure AI Developer" — it targets ML/Foundry hubs, not Foundry projects. Use Foundry User.
✅ Done when: az account show shows tenant 16b3c013… and your user, and the 3 roles appear in Access control (IAM).
1 Deploy two models
Foundry IQ needs a chat model (query planning + answer synthesis) and an embedding model
(to build the HNSW vectors + vectorize queries).
Foundry portal → project agentfond-create-a-develop-1 → left nav Models → + Deploy model
Purpose
Model
Deployment name (use exactly)
Chat / synthesis (agent)
model-router
model-router
Chat for the KB reasoning
gpt-5
gpt-5
Embeddings (3072-dim)
text-embedding-3-large
text-embedding-3-large-2
The Knowledge Base requires a concrete model (gpt-5) — model-router is only allowed on the agent. The embedding deployment name here is text-embedding-3-large-2.
✅ Done when: all three deployments show Succeeded under Models.
2 Create the 5 search indexes (HNSW + semantic)
One index per HR domain. Each holds the chunked text, plus filterable metadata, plus a
vector field indexed with the HNSW algorithm and an integrated vectorizer → this is what makes
retrieval hybrid (keyword BM25 + vector) with a semantic reranker.
Azure portal → Search service foundry-iq-search5600 → Search management → Indexes → + Add index
Prefer code? The whole schema is in the repo at
services/index/schemas/_common.py (Python) — run python -m hriq_foundry_iq.scripts.bootstrap_indexes to create all 5 at once.
✅ Done when: 5 hriq-* indexes appear under Indexes, each with a content_vector field and a semantic configuration.
3 Ingest & chunk the HR documents (from real source systems)
Cut each source document into citable pieces and load them — with embeddings + metadata — into the matching index. There are two ways to do this; use whichever fits the source.
Model
How it works
Best for
A · Push (manual / SDK)
You read the file, chunk it in code, embed each chunk, and uploadDocuments into the index.
Local files, PDFs, ad-hoc loads, the in-app "Load your own documents" button.
B · Pull (indexer + skillset)
Azure AI Search connects to a data source (Blob / SharePoint / SQL), an indexer crawls it, and a skillset chunks + embeds automatically.
SharePoint libraries, Blob containers, SQL tables that change over time.
Chunking rule (identical in both models)
Split on markdown ## headings first (keeps ideas together) → then a sliding window of
1,100 chars with 150 char overlap → each chunk inherits its parent's jurisdiction / content_type / grade / job_family / revision.
A · Push model — chunk + embed + upload yourself
Run the repo uploader (chunks + embeds + upserts to all 5 indexes):
python -m hriq_foundry_iq.scripts.ingest_all. Or use the web app's Data → Load your own documents panel, or the standalone load_documents.py loader. All three do the same three steps: chunk → embed (text-embedding-3-large-2) → uploadDocuments.
Worked example — ingest the 5 employment-law files by hand (with RBAC).
Folder: 05-employment-law → index hriq-employment-law. Do these exactly once per file.
Extract text from the PDF (any PDF-to-text; or the app's PdfPig extractor).
Chunk: split on ## headings, then a 1,100-char window advancing by 950 (= 150 overlap). Give each chunk id <stem>__<n>, e.g. LAW-DE-BEEG-PARENTAL-2025__0.
Embed each chunk (input "<title>\n\n<chunk>") → content_vector (3072 floats).
Tag each chunk with the file's jurisdiction + groupIds from the table below.
Upload (see the REST call further down; mergeOrUpload is idempotent).
File
jurisdiction
groupIds (who may see it)
LAW-DE-BEEG-PARENTAL-2025.pdf
Germany
["HR-DE"]
LAW-DE-PROMO-PARENTAL-2025.pdf
Germany
["HR-DE"]
LAW-GDPR-HR-DATA-2025.pdf
EU
["HR-EU","HR-Global"]
LAW-UK-MGR-EMPLOYMENT-2025.pdf
United Kingdom
["HR-UK"]
LAW-UK-SPL-2025.pdf
United Kingdom
["HR-UK"]
One finished chunk-document looks like this (upload one per chunk):
Prefer to skip the hand-work? The app's Data → Load your own documents button does steps 1–3 + upload for you; add the two RBAC tags in code as shown here.
B · Pull model — Blob & SharePoint data sources (valid URLs)
One-time wiring: a data source (where the files live) → a skillset (Split + embedding) → an indexer (schedule) → index projections that write one search document per chunk. This is exactly what the portal's Import and vectorize data wizard builds for you.
Source
Data-source type · valid URL / connection
Azure Blob
type azureblob · container URL https://hriqstorage5600.blob.core.windows.net/meridian-hr-blobs · auth = managed identity (ResourceId of the search service granted Storage Blob Data Reader)
SharePoint Online
type sharepoint (preview) · connection SharePointOnlineEndpoint=https://<tenant>.sharepoint.com/sites/HR;ApplicationId=<appId> · Graph scope Sites.Selected on just the HR site
Azure SQL
type azuresql · server hriq-sql-server.database.windows.net · db HRIQMeridianDB (high-watermark change tracking for incremental pulls)
⚠️ Blob hriqstorage5600 is publicNetworkAccess=Disabled (AAD-only, private). The indexer reaches it over a shared private link / trusted-service exception with the search service's managed identity — not a laptop. Grant the search MI Storage Blob Data Reader.
✅ Done when: each index's Document count is > 0 (e.g. employment-law ≈ 51, learning-development ≈ 142) and the indexer's last run shows Success.
4 Create the 5 Knowledge Sources
A Knowledge Source wraps one index and tells Foundry IQ which fields to return for citations.
Five indexes → five sources.
All three KBs will be attached to the same single agent in Steps 6–7 — the agent's model decides which KB to query per question. (The KB chat-completion model must be a concrete model like gpt-5; model-router is only allowed on the agent.)
✅ Done when: 3 hriq-*-kb entries appear under Knowledge bases, each listing its sources + a gpt-5 model.
6 Create ONE Foundry agent
Instead of one agent per KB, you build a single unified agent. It runs on model-router (token-efficient — the router picks the cheapest capable model per turn) and will hold all three KBs.
Foundry portal (New Foundry toggle ON) → project agentfond-create-a-develop-1 → Build → Agents → + New agent
Setting
Value
Agent name
hriq-hr-agent
Model
model-router (Global Standard deployment)
Knowledge (added in Step 7)
hriq-hrbp-kb · hriq-manager-kb · hriq-hrops-kb
Instructions (paste into the agent — the "cite or say I don't know" guardrail)
You are HRIQ, Meridian Group's unified HR assistant, grounded on THREE Foundry IQ
knowledge bases: hriq-hrbp-kb (employment law, HR policies, job architecture),
hriq-manager-kb (job architecture, learning & development, onboarding/offboarding),
and hriq-hrops-kb (all five domains). For every question, call the
knowledge_base_retrieve tool on the most relevant knowledge base (or more than one
when the question spans audiences), then answer ONLY from what the tools return.
Cite the exact source for every claim with a numbered citation, and state which
jurisdiction (Germany, UK, or Global) each statutory or policy claim applies to,
always preferring the latest revision. If the knowledge bases do not contain the
answer, reply exactly "I don't know" and recommend escalating to Legal or HR Operations.
✅ Done when: hriq-hr-agent appears under Build → Agents (no knowledge attached yet — that's Step 7).
7 Attach the 3 Knowledge Bases to the agent
This is the key change: one agent, three KBs. In New Foundry you attach a Knowledge Base straight from the agent's Knowledge panel — Foundry auto-creates the MCP tool + connection for you (no manual connection step needed).
Foundry portal → open hriq-hr-agent → Playground → scroll to Knowledge → Add → Knowledge base, and add all three:
Add this KB
Becomes MCP tool
Covers
hriq-hrbp-kb
knowledge_base_hrbp
employment law · HR policies · job architecture
hriq-manager-kb
knowledge_base_manager
job architecture · L&D · onboarding/offboarding
hriq-hrops-kb
knowledge_base_hrops
all 5 domains
MCP tool setting on each: require approval = never, allowed tools = knowledge_base_retrieve. After adding, Save the agent version.
Advanced (code / manual): the exact per-KB MCP endpoints
If you wire it in code (PromptAgentDefinition with three MCPTools) or create the RemoteTool connections by hand, each tool's server_url must be the exact per-KB MCP URL:
⚠️ The target must be the exact per-KB MCP URL including ?api-version=. A generic search-root target returns HTTP 400 "missing api-version" when the agent lists tools. Give each tool a distinct server_label (e.g. knowledge_base_hrbp / _manager / _hrops) — labels must be unique within one agent.
✅ Done when: hriq-hr-agent lists all 3 knowledge bases under Knowledge, and a test question fires one or more knowledge_base_* tools with citations.
8 Grant the runtime identity read access
The agent's managed identity must be able to read the Search data at answer time, or the MCP call returns 401/403.
Role
On
Assign to
Search Index Data Reader
Search foundry-iq-search5600
the Foundry project / agent managed identity
Foundry User (or Agent Consumer)
Foundry account
same identity
The project's system-assigned managed identity is what the MCP connection (Step 6) authenticates as. Grant it Search Index Data Reader under the Search service's Access control (IAM).
✅ Done when: the identity lists Search Index Data Reader + Foundry User.
8b Country-aware access control (a Germany user must not see US policies)
The RBAC in Steps 0/8 controls who can build/run the system. This step controls what each end user sees inside an answer: a German HRBP asking a question must never get a US-only policy in the retrieved sources — even though both live in the same index. There are three ways to enforce it; use A (native, tamper-proof) for production.
A · Document-level security trimming (recommended)
Azure AI Search can filter results by the caller's own identity, enforced by the service — the app can't accidentally leak. You tag each document with the Entra group(s) allowed to see it, then pass the user's token at query time.
Do this
Detail
Add ACL fields to each index
a field groupIds (Collection(String)) with permissionFilter: groupIds — and set the index's permissionFilterOption: enabled
Tag every chunk at ingest
US-only policy → groupIds:["HR-US"]; German law → ["HR-DE"]; company-wide → ["HR-Global"]
Create the Entra groups
HR-DE, HR-US, HR-UK, HR-Global — a German HRBP is a member of HR-DE + HR-Global, never HR-US
Query with the user token
send header x-ms-query-source-authorization: <user's Entra token>; the service returns only docs whose groupIds intersect the user's groups
This is why Foundry IQ advertises "permission-aware responses" — the KB passes the caller's identity down to the search layer, so a Germany user's retrieval simply never contains US-only rows. No filter for the app to forget.
Worked example — prove it in Search Explorer (portal). After you've uploaded the 5 employment-law docs tagged as in Step 3, open
Azure portal → your search service → Indexes → hriq-employment-law → Search explorer and switch the view to JSON. Paste each query and Search → run:
1) As a German HR user (member of HR-DE, HR-EU, HR-Global):
→ returns the UK laws + GDPR; the Germany-only laws are absent. That contrast is the RBAC proof you can screen-record: same index, same query, different group membership → different visible documents.
(Search Explorer runs as you, so it doesn't auto-apply the caller's groups — you supply the group filter to simulate each user. In production, enable native permissionFilter below so the service applies the caller's real groups from their token and no app-side filter is needed.)
B · Jurisdiction metadata filter (simple, app-enforced)
Every chunk already carries a jurisdiction field (Germany / UK / US / Global). The app adds an OData filter based on the signed-in user's country before it retrieves:
// German user → sees Germany-specific + company-wide, never US/UK-only
$filter = "jurisdiction eq 'Germany' or jurisdiction eq 'Global'"
⚠️ Trust boundary: option B is only as safe as the app that adds the filter. Good for a demo and for relevance; combine with A for a real security guarantee.
C · Separate KBs / indexes per region
Coarsest option: build region-scoped indexes (hriq-*-de, hriq-*-us) and region KBs (hriq-de-kb, hriq-us-kb), then RBAC the agents so a German HRBP can only invoke the DE agent. Simple mentally, but multiplies resources and duplicates Global docs — prefer A unless you need hard physical isolation.
Recommended design: one set of 5 indexes → tag chunks with both jurisdiction (for relevance) andgroupIds (for security) → keep the 3 audience KBs → enforce access with A (security trimming). Same architecture as the rest of this guide, just two extra fields at ingest.
✅ Done when: signed in as a German HRBP, the north-star answer cites German + Global sources and zero US-only documents — verifiable in the retrieval trace (no groupIds:["HR-US"] rows).
9 Test in the Agent Playground
Prove the whole chain end-to-end — plan → parallel hybrid retrieve → rerank → cited answer.
Foundry portal → Build → Agents → open hriq-hr-agent → Playground / Try in playground
Ask the hardest (3-source) question
A Senior Analyst in Germany asks about promotion eligibility while on parental leave:
what does job architecture say about competencies, what does German law say about
promotion rights during leave, and what does our global policy say about development
conversations during leave?
What "good" looks like
The answer calls a knowledge_base_* tool, contains numbered citations, states the
jurisdiction (Germany / Global), and pulls from more than one KB / source. Expand the run's tool call to see the sub-queries.
✅ Done when: one cited, jurisdiction-accurate answer comes back in under ~2 minutes.
10 (Optional) Single-KB variant — one KB over all 5 sources
Want the simplest possible shape instead of 3 audience KBs? Make one KB that composes all 5 sources, and attach just that one KB to an agent. No audience separation, but one tool covers everything.
Both shapes already exist live in this project: the 3-KBs-in-one-agent build is hriq-hr-agent (Steps 5–7); the single-KB variant is hriq-kb-agent + hriq-kb. Per-audience single-KB agents (hriq-hrbp-kb-agent etc.) also exist if you want one agent per audience instead.
✅ Done when: hriq-kb-agent answers using citations spanning any of the 5 domains.