# Vectorless reasoning retrieval

`Retrieval.Strategy.REASONING` descends a document tree with a model choosing
the branch. It does not score fragments, so there is no embedding and no
similarity on the path. The strategy and the `TreeNavigator` SPI are
`@since 0.14.0` in TnsAI 0.14.0 (`TnsAI@c2bb5306`). They are
in Maven Central `0.14.1`.

TnsAI does not bundle a navigator. A blank `navigatorModel`, or an identifier
no installed provider uniquely serves, is a configuration error. It fails
before `@Retrieval.onFailure` applies.

## When to use it

| Strategy | Walk | Selector | Cost shape |
|---|---|---|---|
| `SEMANTIC` / `HYBRID` | Flat corpus | Vector / BM25 / RRF score | Index + one search |
| `HIERARCHICAL` | Child → parent | Semantic hit, then deterministic parents | Index + one search + cheap expansion |
| `REASONING` | Root → leaf | Installed `TreeNavigator` | One model call per opened level |

Use `REASONING` on long structured corpora — regulation, specification,
contract — where the answering passage often shares no vocabulary with the
question. Use `HIERARCHICAL` when a similarity hit is already close and you
only need surrounding parents. Use vector or hybrid when lexical or embedding
overlap is a good proxy for relevance.

A traversal that shipped full content to the model would cost what reading the
corpus costs. The navigator therefore sees only titles and summaries.

## Declarative

`navigatorModel` is required. `"provider:model-id"` below is a placeholder,
not a working identifier.

```java
@KnowledgeSource(
    name = "handbook",
    type = KnowledgeSource.KnowledgeType.FILE,
    path = "knowledge/handbook")
@Retrieval(
    strategy = Retrieval.Strategy.REASONING,
    sources = "handbook",
    navigatorModel = "provider:model-id",
    queryParam = "question")
public final class HandbookRole extends Role {
    @ActionSpec(
        type = ActionType.LLM,
        description = "Answer from the handbook tree")
    public String answer(String question) {
        return question;
    }
}
```

`RetrievalConfig.builder().strategy(Retrieval.Strategy.REASONING)
.navigatorModel("provider:model-id")` is the builder counterpart.

The runtime path is `RoleRagBinding.strategyFor` →
`TreeNavigatorRegistry.resolve` → `ReasoningRAGStrategy`. The binding
validates `HierarchyIndex` and resolves the provider at strategy-selection
time, so a missing tree or a missing navigator surfaces above
`@Retrieval.onFailure`. Only failures the provider raises while choosing a
branch follow the failure policy. Cancellation is not a retrieval failure.

## Install a TreeNavigator

Implement `com.tnsai.intelligence.rag.TreeNavigator` with a public no-argument
constructor. Register exactly this UTF-8 service file:

```text
META-INF/services/com.tnsai.intelligence.rag.TreeNavigator
```

The file contains the provider's fully qualified class name. The package-private
`TreeNavigatorRegistry` loads providers with `ServiceLoader`. Resolution is
deterministic:

- a blank model fails with `IllegalArgumentException`
- no provider that `supports(model)` fails with `TreeNavigatorException`
- two or more matching providers fail with `TreeNavigatorException`
- classpath order never picks a winner

TnsAI owns the walk. The provider only judges the current node. The runtime
bounds visits (`maxNodesVisited` default `15` across all roots, `maxDepth`
default `6` on any single path), checks every returned id against the
children just offered, and records why a path stopped.

`Request` carries `query`, `model`, `current`, `candidates`,
`remainingVisits`, and a `CancellationToken`. Implementations should observe
the token before and after blocking work. The current
`ReasoningRAGStrategy` constructs that request with
`CancellationToken.none()` and also fails the retrieve if the calling thread
is interrupted.

## Programmatic

Construct the strategy over an existing `HierarchyIndex`. Titles and summaries
are metadata, not content:

```java
Map<String, String> metadata = new LinkedHashMap<>();
metadata.put(HierarchyMetadata.ID, "refunds");
metadata.put(HierarchyMetadata.PARENT_ID, "billing");
metadata.put(HierarchyMetadata.SOURCE, "handbook");
metadata.put(ReasoningRAGStrategy.NODE_TITLE_METADATA, "Refunds");
metadata.put(ReasoningRAGStrategy.NODE_SUMMARY_METADATA,
    "Refunds are issued within 14 days");
RetrievedDocument node = new RetrievedDocument(
    "Refunds are issued within 14 days.",
    "refunds",
    0.0,
    metadata);

HierarchyIndex index = new HierarchyIndex(List.of(node /* , … */));
RAGStrategy strategy = new ReasoningRAGStrategy(
    index,
    new ApplicationTreeNavigator(),
    "docs:title-match");
List<RetrievedDocument> landed =
    strategy.retrieve("how do refunds work", new RAGContext());
```

`ApplicationTreeNavigator` is application code. Absent
`reasoningTitle` / `reasoningSummary` keys, the navigator sees the hierarchy
id and an empty summary. Producing those summaries is indexing work; it is
not part of this retrieval strategy.

## What comes back

There is no score model. Every landed node carries
`ReasoningRAGStrategy.SELECTED_SCORE` (`1.0`), so `RAGContext.minScore()`
admits every result. `metadataFilters()` and `maxResults()` still apply.

Stop reasons are written to `HierarchyMetadata.STOP_REASON`. None of them
fail the request:

| Reason | Meaning |
|---|---|
| `LEAF` | The current node has no children. |
| `NAVIGATOR_STOP` | The provider returned `Decision.stop()`. |
| `UNKNOWN_NODE` | The provider named an id that was not offered. |
| `BUDGET_EXHAUSTED` | `maxNodesVisited` was spent. |
| `MAX_DEPTH` | The single-path depth cap was hit. |

The path taken is in `HierarchyMetadata.PATH`; depth is
`HierarchyMetadata.DEPTH`. A hallucinated branch therefore returns a partial
answer from the last valid node instead of throwing.

## Not this page

- The eight-strategy inventory on [Strategies](strategies.md)
- Tree ingestion, persistence, or a second document-tree model
- Optional PDF / Office extractors
- PageIndex competitor positioning
