Skip to content

Knowledge Graphs

Knowledge Graphs let you describe categories, types, attributes, and relationships as validated reference data. SyntheticBrew then gives bound agents predictable tools for listing matching records and looking up exact IDs.

Define the domain in JSON Schema, apply it as a bundle, and bind the bundle to an agent. SyntheticBrew generates list_X, get_X, and optional list_X_ids tools for each exposed entity type.

Knowledge Graphs work for structured, slow-changing domain models. Storage quotas depend on the Cloud plan or Enterprise deployment. They are a good choice for:

  • Taxonomies and ontologies — categories → brands → attributes; conditions → symptoms → treatments; jurisdictions → statutes → topics
  • Catalogs of typed records — product categories with attributes and brands; legal statutes by jurisdiction; controlled medical terminology
  • Known-issue libraries — products → modules → known issues → resolutions
  • Cross-referenced reference data — codes, registries, controlled vocabularies

They are not for:

  • Inventory or transactional data (20,000 SKUs with real-time stock) → use an external MCP server pointing at your existing system
  • Long-form documents and narrative content → use Knowledge / RAG (vector search) instead
  • Conversation memory (what the user told the agent) → use Memory instead
  1. Define entity schemas in JSON Schema (Draft 2020-12) with SyntheticBrew x-* annotations
  2. Bulk-import entity instances matching the schemas into a named bundle
  3. Bind the bundle to one or more agents via the knowledge_graphs capability
  4. SyntheticBrew generates MCP tools per entity type and makes them available to bound agents
my-bundle/
├── manifest.yaml
├── schemas/
│ ├── category.schema.json
│ └── brand.schema.json
└── entities/
├── categories.yaml # array of category entities
└── brands.yaml # array of brand entities
After `brewctl kg apply ./my-bundle`:
list_category(filters={popularity: "high"})
→ returns matching category records plus the total count.
get_brand(ids=["north-aurora", "missing-brand"])
→ returns {entities: [{id, data}], not_found: ["missing-brand"]}.
A missing ID does not fail the rest of the batch.

An entity schema is a standard JSON Schema document with SyntheticBrew-specific x-* extension annotations. These annotations identify the primary ID, filterable fields, and references to other entity types.

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "category",
"title": "Category",
"description": "A product category in the catalog.",
"type": "object",
"x-id-field": "code",
"x-tool-expose": ["list", "get"],
"x-tool-description": "Catalog categories. Use list_category to enumerate top-level categories and get_category to fetch one by code.",
"required": ["code", "name", "tier"],
"additionalProperties": false,
"properties": {
"code": {
"type": "string",
"pattern": "^[a-z][a-z0-9_-]{1,30}$",
"description": "Lowercase short code.",
"x-index": true
},
"name": {
"type": "string",
"minLength": 3,
"maxLength": 60
},
"tier": {
"type": "string",
"enum": ["primary", "secondary"],
"x-index": true
},
"popularity": {
"type": "string",
"enum": ["high", "medium", "low"],
"x-index": true
},
"brand_count": {
"type": "integer",
"minimum": 0,
"x-derived": true
}
}
}

See the Schema annotations reference for the full list of x-* annotations.

For each entity schema you declare, SyntheticBrew generates up to three retrieval tools:

ToolGenerated whenParametersReturns
list_<entity_type>"list"x-tool-expose (default)filters, sort, limit (default 50, max 500), offset{items: [{id, data}], total, limit, offset}
get_<entity_type>"get"x-tool-expose (default)required ids array, max 500{entities: [{id, data}], not_found: [...]} in input order
list_<entity_type>_ids"list_ids"x-tool-expose (opt-in)same query controls as list_*{ids, total} or {items, total} when x-summary-fields is set

Tools are namespaced per tenant — list_category in tenant A’s bundle is invisible to tenant B. Within a tenant, two bundles cannot expose conflicting tool names; the second apply is rejected with tool_name_collision_in_tenant.

Hard limits on list_* parameters:

  • limit must be 1..500. SyntheticBrew rejects an explicit out-of-range value; REST returns 400, while an agent call receives an error tool result.
  • filters keys must reference a field marked x-index: true in the schema. Unknown keys are rejected with the allowed-list.
  • Filter values use JSON types. Tell the agent to send an object rather than a JSON-encoded string and to use record IDs/codes rather than display labels. See Guide the agent to use graph tools.

The x-ref annotation records that a property refers to another entity type. Apply validates that the target entity type exists in the bundle, but it does not verify that every record value matches an entity. Admin shows the record as JSON; add record-level checks to your bundle workflow when you require strict references.

{
"$id": "brand",
"type": "object",
"x-id-field": "code",
"properties": {
"code": {"type": "string"},
"category": {
"type": "string",
"x-ref": "category"
},
"parent_brand": {
"type": "string",
"x-ref": "brand",
"x-ref-field": "code"
}
}
}

Cycles between entity types are allowed (A → B → A); they are detected and logged at apply time as a warning, not rejected.

Enterprise upgrade note. Run the migrations supplied for the target release before binding the capability. See the Enterprise runbook.

A bundle becomes visible to an agent through the knowledge_graphs capability. In a brewctl agent resource, declare the binding like this:

apiVersion: syntheticbrew/v1
kind: Agent
name: catalog-assistant
model: glm-5
capabilities:
- type: knowledge_graphs
enabled: true
config:
bundles: [ecommerce-catalog-example]
system_prompt: |
You are bound to the "ecommerce-catalog-example" knowledge graph.
You have read-only tools: list_category, get_category, list_brand,
get_brand, list_product_attribute, get_product_attribute.
MANDATORY workflow on every user question:
1. Identify which entity_type the question is about.
2. Use list_/get_ tools — NEVER invent entity codes or attribute values.
3. If a tool returns 0 results, say so explicitly. Suggest the closest
existing entities by querying a related type.
4. Prefer popularity=high categories first when not specified.
5. Cite the entity code of every recommendation.
Filter values must be ENTITY CODES (lowercase snake_case or kebab-case),
not display names. Filters is an object, not a JSON-encoded string.

For direct REST calls, create the agent first, then attach the binding with POST /api/v1/agents/{name}/capabilities. Configuration import does not accept a nested capabilities field on an agent.

See Guide the agent to use graph tools below. Clear instructions help the model choose graph data instead of answering from general knowledge.

Agents not bound to a bundle do not see its tools. Two agents in the same tenant can be bound to different bundles and see different tools.

Auto-generated tools are available to the model, but the system prompt still needs to say when they are required. A general instruction such as "You help users navigate the product catalog" does not require the agent to check the graph before answering.

Add an explicit workflow to the agent’s system prompt.

Use the following template (adapt the entity-type names to your bundle):

You are bound to the "{bundle_name}" knowledge graph. You have access to these
read-only tools for navigating it deterministically:
list_<entity_type>(filters?, limit?, offset?) → enumerates entities
get_<entity_type>(ids=[id, ...]) → batch fetches up to 500 ids
MANDATORY workflow on every user question:
1. Identify which entity_types the question is about. If unclear, call
list_<entity_type> on the most general type first to discover the domain.
2. Use the list_/get_ tools to retrieve the actual entities. NEVER invent
entity ids, codes, or attribute values from general knowledge.
3. If a tool returns 0 results, say so explicitly. Suggest the closest
existing entities by querying a related type (do not fabricate).
4. Prefer popularity=high entities first when the user has not specified.
5. Cite the entity id of every record you recommend, e.g. "north-aurora",
so the user can verify.
Filter argument format:
- Filter values must be ENTITY CODES (lowercase snake_case or kebab-case
identifiers, e.g. category="footwear"), NOT display names ("Footwear").
- `filters` is an object, not a JSON-encoded string. Example call:
list_brand(filters={"category": "footwear", "tier": "premium"}, limit=50).
Weak promptStrong prompt (template above)
Agent gives generic catalog advice from training data.Agent calls list_category first, then drills with list_brand(filters={category: ...}).
Hallucinated entity codes (e.g. "brand-xyz" that does not exist).Cited entity codes resolve via get_* to real records.
User asks about a niche the bundle does not cover → agent invents one.Agent says “no premium footwear brands in the catalog yet, closest is mid-tier stride-co” + cites real codes.
Filter args with display labels — 0 results, agent spirals through variants.Filter args use canonical codes — first call returns correct items.

Test the workflow with known records, missing records, and filters that return no matches. The expected result is an answer based on returned entity IDs, with a clear statement when the graph contains no matching record.

Knowledge Graphs and the vector Knowledge / RAG primitive are complementary, not competing. The right pattern is to use both together:

Use casePrimitive
”What categories exist?” (full recall)Knowledge Graphs (list_category)
“Show only premium-tier brands” (filtered)Knowledge Graphs (list_brand with filters={tier: "premium"})
“How do I care for wool?” (narrative search)Knowledge / RAG (knowledge_search)
“What does the brand north-aurora carry?” (deterministic ID lookup)Knowledge Graphs (get_brand(ids=["north-aurora"]))

Both capabilities can be enabled on the same agent.

For domains with large transactional data (real-time stock, customer orders, live pricing), put structure in a Knowledge Graph and inventory behind an external MCP server. See the Hybrid pattern guide for full examples.

  • Bound agent tools are read-only — generated list_ and get_ tools retrieve graph data. Authorized operators and coding agents mutate graphs through the management MCP tools, REST API, or brewctl; Admin browses graph content.
  • Schema changes require matching records — after a breaking schema change, re-import entities that conform to the new schema.
  • Imports do not prune omitted schemas — a full import replaces the bundle’s entities and updates the schemas it contains, but an older omitted schema remains. Delete and recreate the bundle when retiring an entity type.
  • No cross-bundle refs — the entity type named by x-ref must exist within the same bundle. Record-level reference values are not foreign-key enforced.
  • Start a new session after tool changes — new graph tools do not appear in a chat that is already in progress.
  • Manage access through the agent capability — add or remove bundles in the agent’s Knowledge Graphs capability.