Knowledge Graphs — Quickstart
This tutorial walks you through creating a working Knowledge Graph, binding it to an agent, and observing the agent use auto-generated MCP tools to answer questions deterministically. End-to-end in about 15 minutes.
You will need:
- A running SyntheticBrew engine (CE, EE, or Cloud — same workflow). Engine 1.3.0 or later is required: it ships migration
011_capabilities_kg_constraint.yaml, which enables theknowledge_graphscapability type. brewctl0.3.0 or later — installation guide- An admin API token
What you will build
Section titled “What you will build”A small Knowledge Graph called quickstart-catalog with two entity types — category and brand — and a single agent bound to it. By the end, the agent will answer the question “what premium-tier brands carry footwear?” by calling list_brand with a filter, returning structured results.
Step 1: Create the bundle directory
Section titled “Step 1: Create the bundle directory”mkdir -p quickstart-catalog/{schemas,entities}cd quickstart-catalogCreate manifest.yaml:
bundle_name: quickstart-catalogversion: 1.0.0entity_types: - name: category schema_file: schemas/category.schema.json entities_file: entities/categories.yaml - name: brand schema_file: schemas/brand.schema.json entities_file: entities/brands.yamlStep 2: Define the entity schemas
Section titled “Step 2: Define the entity schemas”Create schemas/category.schema.json:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "category", "title": "Category", "description": "A product category in the quickstart catalog.", "type": "object", "x-id-field": "code", "x-tool-expose": ["list", "get"], "required": ["code", "name"], "additionalProperties": false, "properties": { "code": { "type": "string", "pattern": "^[a-z][a-z0-9_-]{1,30}$", "x-index": true }, "name": { "type": "string", "minLength": 3, "maxLength": 60 }, "popularity": { "type": "string", "enum": ["high", "medium", "low"], "x-index": true } }}Create schemas/brand.schema.json:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "brand", "title": "Brand", "description": "A brand carried in the catalog under exactly one category.", "type": "object", "x-id-field": "code", "x-tool-expose": ["list", "get"], "required": ["code", "name", "category", "tier"], "additionalProperties": false, "properties": { "code": { "type": "string", "x-index": true }, "name": { "type": "string" }, "category": { "type": "string", "x-ref": "category", "x-index": true }, "tier": { "type": "string", "enum": ["budget", "mid", "premium"], "x-index": true }, "headquarters": { "type": "string", "x-content-type": "text" } }}Note the x-ref: "category" annotation on brand.category — the engine will validate at apply time that every category value matches an existing category entity’s code.
Step 3: Populate the entities
Section titled “Step 3: Populate the entities”Create entities/categories.yaml:
- code: footwear name: Footwear popularity: high- code: apparel name: Apparel popularity: high- code: home_goods name: Home Goods popularity: mediumCreate entities/brands.yaml:
- code: north-aurora name: North Aurora category: footwear tier: premium headquarters: Vancouver, Canada
- code: stride-co name: Stride Co. category: footwear tier: mid headquarters: Portland, USA
- code: harborline name: Harborline Apparel category: apparel tier: mid headquarters: Boston, USA
- code: oakwood-home name: Oakwood Home category: home_goods tier: mid headquarters: Stockholm, Sweden
- code: budget-basics name: Budget Basics category: apparel tier: budgetStep 4: Apply the bundle
Section titled “Step 4: Apply the bundle”brewctl kg apply . \ --endpoint http://localhost:18082 \ --token $BREWCTL_TOKENYou should see output like:
Bundle 'quickstart-catalog' v1.0.0: + category (3 entities) + brand (5 entities)Atomic apply: OKGenerated tools: list_category, get_category, list_brand, get_brandIf something is wrong (broken x-ref, schema validation error, tool name collision), the apply rejects with a structured error message — no partial state is persisted.
Step 5: Create an agent bound to the bundle
Section titled “Step 5: Create an agent bound to the bundle”In the admin UI, create an agent (or via API):
curl -X POST http://localhost:18082/api/v1/agents \ -H "Authorization: Bearer $BREWCTL_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "quickstart-assistant", "model_name": "glm-5", "system_prompt": "You are bound to the quickstart-catalog knowledge graph.\nYou have read-only tools: list_category, get_category, list_brand, get_brand.\n\nMANDATORY workflow on every user question:\n 1. Identify which entity_type the question is about.\n 2. Use list_/get_ tools — NEVER invent entity codes or attribute values.\n 3. If a tool returns 0 results, say so explicitly. Suggest the closest existing entities by querying a related type.\n 4. Prefer popularity=high categories first when not specified.\n 5. Cite the entity code of every recommendation.\n\nFilter values must be ENTITY CODES (lowercase snake_case or kebab-case), not display names.\nFilters is an object, not a JSON-encoded string. Example:\n list_brand(filters={\"category\": \"footwear\", \"tier\": \"premium\"})", "capabilities": [{ "type": "knowledge_graphs", "config": {"bundles": ["quickstart-catalog"]} }] }'Step 6: Chat with the agent
Section titled “Step 6: Chat with the agent”Open the admin UI chat tab and send:
What premium-tier brands carry footwear?
Watch the agent’s reasoning trace:
Tool call: list_brand(filters={category: "footwear", tier: "premium"})Tool result: { items: [ {code: "north-aurora", name: "North Aurora", category: "footwear", tier: "premium", headquarters: "Vancouver, Canada"} ], total: 1}
Agent response:There is 1 premium-tier brand carrying footwear:
• north-aurora — North Aurora (HQ: Vancouver, Canada)
Want me to list mid-tier footwear brands too?Notice three things:
- The agent did not hallucinate codes.
north-aurorais exactly what you put in the YAML. - The agent filtered by both
categoryandtierin a single tool call. Vector RAG cannot do this. - The
totalfield tells the agent there is exactly 1 result — no chance of missing one.
Step 7: Verify the schema
Section titled “Step 7: Verify the schema”Try a follow-up question:
What about budget-tier apparel brands?
Expected tool call:
list_brand(filters={category: "apparel", tier: "budget"})Result: exactly budget-basics. If the agent had only seen premium-tier brands at training time, it might have hallucinated; with the Knowledge Graph, the response is grounded in the live bundle.
What you just built
Section titled “What you just built”In 15 minutes you declared a domain ontology (2 schemas, 8 entities), pushed it as an atomic bundle, bound it to an agent via the knowledge_graphs capability, and observed deterministic retrieval through auto-generated MCP tools. No agent code was written; the tools were generated automatically from the schemas.
Step 7 (engine 1.4.0+): try the five query patterns
Section titled “Step 7 (engine 1.4.0+): try the five query patterns”If you are on engine 1.4.0 or later, the same list_brand tool plus its new siblings unlock a much richer query surface. Each example below is a single tool call your agent can make — paste any of them into the admin chat tab to see the response.
1. Batch get — fetch multiple entities in one round-trip. Response shape is {entities, not_found}; misses do not fail the call.
get_brand(ids=["north-aurora", "stride-co", "no-such-brand"])2. Range filter on a numeric x-index field. (Requires a numeric or format: date-time property — add one to the brand schema if you want to try this on the quickstart bundle.)
list_brand(filters={founded_year: {gte: 2010, lte: 2020}})3. Multi-value [in] filter on any x-index field.
list_brand(filters={tier: {in: ["premium", "luxury"]}})4. Summary projection on list_brand_ids — cheap preview without full payloads. Requires the brand schema to declare x-summary-fields AND expose the list_ids tool. Update brand.schema.json:
"x-tool-expose": ["list", "get"],"x-tool-expose": ["list", "get", "list_ids"],"x-summary-fields": ["name", "tier", "category"],Re-apply (brewctl kg apply ./quickstart-catalog), then call:
list_brand_ids(filters={category: "footwear"})Response shape is {items: [{id, name, tier, category}], total} instead of bare ids. Note the id key — the engine normalises the x-id-field’s value (which the schema declares as code) into a generic id key in the projection payload, so the agent can iterate items without knowing your id field name.
5. Server-side sort with enum declaration order. Your brand schema declares tier: enum: [budget, mid, premium] (the quickstart’s order — low to high). With this declaration, sort directions read as:
sort=tier:descreturns[budget, mid, premium]— declaration head first (“desc” = top of the array).sort=tier:ascreturns[premium, mid, budget]— declaration tail first.
If you want desc to mean “premium-first” semantically (highest tier on top), flip the schema declaration to [premium, mid, budget]. Then desc returns [premium, mid, budget] — natural “best first” ordering.
list_brand_ids( filters={category: "footwear"}, sort=[{field: "tier", order: "asc"}, {field: "code", order: "asc"}], limit=5)(In the quickstart’s [budget, mid, premium] schema, tier:asc puts premium first — the high tier. If you flipped the declaration to [premium, mid, budget], the same result needs tier:desc.)
These five together cover the typical “find candidates → fetch the chosen few” pattern that compresses agent retrieval token cost by roughly 12× versus a single list_brand over the same matches.
Step 8 (engine 1.4.0+, optional): split your entities by category
Section titled “Step 8 (engine 1.4.0+, optional): split your entities by category”For larger catalogs (≥100 entities per type) the canonical single-file entities/brands.yaml becomes hard to PR-review. Migrate to the split layout:
- name: brand schema_file: schemas/brand.schema.json entities_file: entities/brands.yaml- name: brand schema_file: schemas/brand.schema.json entities_path: entities/brand/Create the directory and split entities into multiple files by any axis (tier, category, region):
entities/brand/├── premium.yaml ← array of premium-tier brands├── mid.yaml ← array of mid-tier brands└── budget-basics.yaml ← single entity, one-document formbrewctl globs *.yaml flat, merges, validates uniqueness across files, and applies as one atomic bundle. See Bundles & layouts guide for the full discussion.
Next steps
Section titled “Next steps”- Read the Knowledge Graphs concept for the full picture
- See the Schema annotations reference for every
x-*annotation - Learn the hybrid pattern — combine Knowledge Graphs with an external MCP server for inventory
- Walk the Migration 1.3 → 1.4 guide — what changed in the agent tool surface
- Bring it to production: admin UI guide and GitOps with Helm