---
title: "Search & agentic planning"
source: "https://docs.vertesiahq.com/content/view-experiences/search"
markdown: "https://docs.vertesiahq.com/llms/content/view-experiences/search.md"
---

# Search & agentic planning

A View's `search` section chooses how users search. There are two modes: **deterministic** (the default — keyword
search plus structured key terms) and **agentic** (natural language, translated into a validated Elasticsearch query
by a model). Both compose with the View's fixed scope, active navigation, project boundary, and content-security
filters.

Numeric ranges, date facets, and typed sorts depend on how the project indexes properties — see
[Typed property indexing](#typed-property-indexing) at the end of this page.

## Deterministic search

Deterministic search is the default. It combines the text query with the View's fixed scope, active navigation
selections, structured key terms, configured sort, project boundary, and content-security filters.

The default text query searches content name, description, full text, and properties. Key terms add predictable
domain controls, each declaring a concrete `field`:

- `match` performs analyzed text matching;
- `term` performs an exact value filter; and
- `range` accepts a `from..to` value, with either bound allowed to be empty.

Use exact property terms for fields such as `properties.customer` and `properties.status`, while leaving the main
query available for descriptions or order text. Omit the `search` section entirely for a browse-only experience; the
runtime then rejects query and key-term input instead of silently ignoring it.

## Agentic query and presentation planning

Set `search.mode` to `agentic` when users should express domain intent in natural language. The default
`sys:ContentSearchAgent` interaction converts the request into a supported Elasticsearch query. The server limits the
model to built-in fields and the fields disclosed by the View configuration, validates the returned query, then adds
the immutable scope and content-security filters.

Agentic Views support two planning modes:

- `query` generates only the Elasticsearch query and uses the configured View presentation.
- `query_and_view` generates the query plus a safe, ephemeral presentation suited to that question.

A View has one model execution configuration. Set it under `search.agentic.config`, or omit it to inherit the system
interaction and project defaults:

```json
{
  "mode": "agentic",
  "placeholder": "Describe the sales orders you need",
  "fields": [
    { "field": "text", "description": "Full ingested and OCR sales-order text.", "type": "text", "mode": "full_text" },
    { "field": "properties.instructions", "description": "Customer instructions associated with the order.", "type": "text", "mode": "full_text", "boost": 2 },
    { "field": "properties.order_total", "description": "Numeric order total.", "type": "number", "mode": "exact" }
  ],
  "agentic": {
    "interaction": "sys:ContentSearchAgent",
    "config": { "environment": "<ENVIRONMENT_ID>", "model": "<MODEL_ID>" },
    "mode": "query_and_view",
    "instructions": "Treat customer and sold-to state as exact filters. Prefer open orders.",
    "timeout_ms": 12000,
    "minimum_confidence": 0.6,
    "rerank": {
      "max_candidates": 30,
      "include_why_match": true,
      "timeout_ms": 15000
    }
  }
}
```

`query_and_view` uses one model call and the same model configuration as query-only planning. Alongside the validated
query, the model may choose a title, description, browse/worklist layout, up to six property facets, one built-in
list/table/cards/gallery/board display, and up to three sort clauses. For example, “compare orders over $10,000” can
produce a numeric comparison table, while “browse product photos by brand” can produce a gallery with a brand facet.

The generated presentation is returned as the execution response's `definition`; it is not persisted and the existing
`ViewExperience` component renders it without a second API or component. The model cannot change the View's scope,
search configuration, actions, selection, upload/drop behavior, or app-contributed renderers. Folder and collection
navigation, active selections, and those configured behaviors remain server-owned and are merged into the ephemeral
definition.

## Agentic result reranking

Add `search.agentic.rerank` when Elasticsearch should retrieve the candidates and the model should make a bounded
second pass over the results. Reranking is independent of `mode`: it works with both `query` and `query_and_view` and
uses the same `search.agentic.config` model. By default it calls `sys:ContentSearchReranker`; set `interaction` inside
`rerank` only when the project provides a purpose-built reranking interaction.

The server sends at most `max_candidates` authorized hits from the current result page (default 30, maximum 50), with
only fields from the View's validated field catalog. The interaction must return every candidate ID exactly once.
Zeno rejects invented, omitted, or duplicate IDs, so reranking can change order but cannot change the candidate set,
scope, total count, facets, or canonical content. Hits beyond the bounded prefix retain their Elasticsearch order.

When `include_why_match` is true, the response may include a grounded explanation under
`hit.annotation.why_match`; the built-in result layouts display it automatically. The execution response also reports
`search.rerank.status` as `applied`, `fallback`, or `skipped` and includes the candidate count. Any applied configured
sort—including an explicit user sort or a generated `query_and_view` sort—skips reranking. Query-planning fallback also
skips it. A timeout, interaction failure, or invalid permutation returns the original Elasticsearch order with an
`agentic_rerank_failed` warning.

Reranking applies to each requested page independently. It is intended to improve findability inside a strong,
bounded Elasticsearch candidate set, not to produce one global model-ranked order across every result page.

`search.fields` is the **semantic field catalog** the model plans against. The active Elasticsearch mapping remains
authoritative: a field configured as `full_text` participates in deterministic fallback only when it is actually
mapped as `text`. An explicit field list replaces the default lexical fields and fails closed when none is actively
mapped as text.

When a natural-language plan contains scoring text clauses, results use Elasticsearch relevance unless the request
explicitly selects a configured sort. Browse and structured-only plans continue to use `results.default_sort`.

If query planning times out, fails validation, or does not meet `minimum_confidence`, the runtime **falls back to
deterministic search** over the View's mapped text fields and returns an explicit warning. If only the generated
presentation is invalid, the validated agent query still runs with the configured presentation and an
`agentic_presentation_failed` warning. The fallback never applies
fuzzy queries outside the validated field catalog. When no explicit `search.fields` list is configured, the broad
lexical default searches `name`, `description`, full document `text`, and `properties.*`.

  Reranking preserves every candidate. Model-driven curation that removes results, query-specific answers, and
  generated excerpts remain future runtime work.

## Typed property indexing

By default, indexed property strings are **keyword** fields — exact-match and aggregatable, which is what property
facets and `term` key terms need. But a `range` facet, a date facet, or a numeric sort needs the field to be mapped
with a **typed** Elasticsearch type. This is the most common reason a range or date control appears to do nothing.

Two rules govern how properties are typed:

- **JSON numbers and booleans** are inferred as native numeric/boolean types automatically, so store numbers as JSON
  numbers (not strings) if you want ranges and numeric sorts to work.
- **Strings** map to `keyword`. Date-looking strings stay keywords too — JSON has no date scalar — so a date facet or
  date sort requires an **explicit** `date` mapping for that path.

Declare explicit types in the project's `indexing.property_mappings`, keyed by dotted property path:

```json
{
  "indexing": {
    "property_mappings": {
      "order_total": { "type": "double", "ignore_malformed": true },
      "delivery_date": { "type": "date", "format": "strict_date_optional_time||epoch_millis", "ignore_malformed": true },
      "customer_code": { "type": "keyword", "ignore_above": 256 }
    }
  }
}
```

Supported `type` values are `keyword`, `text`, `boolean`, `long`, `double`, and `date`. `ignore_malformed` lets a
single bad value be skipped instead of rejecting the whole document; `date` accepts a `format`; `keyword` accepts
`ignore_above`.

  Changing `property_mappings` does **not** rewrite the active index. Elasticsearch cannot change the type of an
  existing field, so the new mapping only takes effect after a **full reindex** of the project. Until then, the field
  keeps its old type and typed Views over it will not work. See [Search Configuration](/content/search) for reindexing.

New indexes store property leaves with `subobjects: false`, so a scalar path such as `properties.bill_to` can coexist
with a more specific path such as `properties.bill_to.name`, even when source documents use nested JSON objects. The
same leaf should still have a consistent scalar type across documents.