Support
Log In

Discovery Metadata

Document your models for humans and AI discovery

Your model now defines what your data means. This page is about the next stage: optimizing it for AI retrieval. Metadata tags document your model for humans and — through the Credible AI Analytics Engine — make it discoverable to AI agents: when you publish, your tags are indexed and become searchable by people browsing models and by agents answering natural-language questions.

Two tags do the work: #(doc) describes what a field means, and #(index) makes a dimension's values searchable. A third lever, discovery curation, narrows which models and sources agents see in the first place. This page covers what to tag, index, and curate; for how indexes are built, kept fresh, and paid for, see Performance & Cost.

You don't have to write tags by hand: the agent documents as it builds — every field it defines gets its #(doc) and #(index) tags as part of the modeling workflow — and you can ask it to document an existing model the same way. Review its work with this page in mind; good documentation directly improves the quality of chat-based analysis downstream.

Documentation Tags

Use #(doc) tags to add human-readable descriptions to fields. Place the tag on the line before the field definition.

source: orders is conn.table('sales.orders') extend {
  dimension:
    #(doc) The unique identifier for each order
    order_id is id

    #(doc) The date when the order was placed
    order_date is created_at::date

    #(doc) Customer segment based on lifetime value: High (>$10k), Medium (>$1k), Low
    customer_segment is case
      when lifetime_value > 10000 then 'High'
      when lifetime_value > 1000 then 'Medium'
      else 'Low'
    end

  measure:
    #(doc) Total revenue from all orders
    total_revenue is sum(amount)

    #(doc) Number of orders that have shipped
    shipped_orders is count() { where: status = 'shipped' }
}

The best #(doc) descriptions carry meaning the code can't: units, business rules, thresholds, and caveats — like the segment cutoffs above — not restatements of the field name.

Value Indexing

Use #(index) to index all distinct values from a column. This enables AI agents to find fields by searching for data values, not just field names or descriptions.

source: products is conn.table('catalog.products') extend {
  dimension:
    #(index)
    #(doc) The product name
    product_name is name

    #(index)
    #(doc) The product category (e.g., Electronics, Clothing, Home & Garden)
    category is product_category

    #(index)
    #(doc) The brand name
    brand is brand_name
}

Why Value Indexing Matters

Consider a product catalog where the column is named product_category but contains values like "Running Shoes" and "Athletic Apparel". When a user asks "What are our top-selling sports gear products?", field names and documentation alone won't surface those products — nothing is named "sports gear".

With value indexing, the Credible AI Analytics Engine searches the actual values: it matches the question to fields whose values fit, then helps the agent construct a query with the correct filter — finding "Running Shoes" and "Athletic Apparel" without exact string matching.

When to Use Value Indexing

Use #(index) on:

  • Names and titles - Product names, customer names, program titles
  • Categorical values - Status codes, types, categories
  • Lookup values - Region names, department names, brand names

Skip value indexing for:

  • Numeric fields (amounts, counts, IDs)
  • Timestamps and dates

Example: Well-Documented Model

source: orders is conn.table('sales.orders') extend {
  primary_key: order_id

  join_one: customers is conn.table('sales.customers') on customer_id = customers.id
  join_one: products is conn.table('catalog.products') on product_id = products.id

  dimension:
    #(doc) Date the order was placed
    order_date is created_at::date

    #(doc) Order status: pending, processing, shipped, delivered, cancelled
    #(index)
    status is order_status

    #(doc) Product category from the catalog
    #(index)
    category is products.product_category

    #(doc) Customer's geographic region
    #(index)
    region is customers.region

  measure:
    #(doc) Total number of orders
    order_count is count()

    #(doc) Total revenue in USD
    total_revenue is sum(amount)

    #(doc) Average revenue per order in USD
    avg_order_value is total_revenue / order_count

    #(doc) Percentage of orders that were cancelled
    cancellation_rate is count() { where: status = 'cancelled' } / order_count * 100

  view:
    #(doc) Monthly revenue trend with order counts
    monthly_revenue is {
      group_by: order_date.month
      aggregate: total_revenue, order_count
    }
}

Curating Discovery

Tags improve how well agents understand what they find; discovery curation narrows what they find in the first place. By default, every model is listed and every source is directly queryable. Curation happens at two levels: export in the model itself controls which sources a file exposes, and manifest fields control which files agents land on.

export { … } — in the model

export { … } filters which sources within a file are exposed. A file with no export exposes all its top-level sources; add one to keep imported or scaffolding sources out of discovery.

// Intermediate building block — hidden from discovery
source: raw_order_lines is conn.table('sales.order_lines') extend {
  measure: line_count is count()
}

// The curated interface built on it — this is what agents should find
source: orders is raw_order_lines extend {
  measure: order_revenue is sum(amount)
}

export { orders }

With the export, agents discover orders but not raw_order_lines; without it, both would be listed.

explores and queryableSources — in the manifest

Add optional fields to the package manifest (publisher.json, described in Publishing) to point agents at the model files that matter:

{
    "name": "ecommerce",
    "version": "0.0.1",
    "description": "ecommerce demo data",
    "explores": ["orders.malloy", "customer_health.malloy"],
    "queryableSources": "declared"
}

explores.malloy file paths (relative to the package root) whose models agents should discover and land on.

  • Omit — discovery unchanged; every model and source stays listed.
  • Set — listings narrow to these files, plus each file's export { … } closure.

queryableSources — whether non-curated sources can still be queried by name. Only applies when explores is set.

ValueListingQuery by name
"declared" (default)Curated set onlyNon-curated sources return 404
"all"Curated set onlyAll sources still queryable

This curates discovery, not access — what is listed and queryable by name, not who may query. To restrict access by user or group, use #(authorize) and secure givens, covered on the next page.

Hiding an #(authorize)-gated source. An access gate blocks querying, not discovery, so a gated source still appears in listings to callers who can't query it. To hide it while keeping it queryable for authorized callers, leave it out of the curated explores/export set and set queryableSources: "all". Under the default "declared", the non-curated source returns 404 by name — which blocks authorized callers too.

Next Steps

On this page