Skip to content
tnsaijava agent framework

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

StrategyWalkSelectorCost shape
SEMANTIC / HYBRIDFlat corpusVector / BM25 / RRF scoreIndex + one search
HIERARCHICALChild → parentSemantic hit, then deterministic parentsIndex + one search + cheap expansion
REASONINGRoot → leafInstalled TreeNavigatorOne 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.

@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.strategyForTreeNavigatorRegistry.resolveReasoningRAGStrategy. 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:

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:

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:

ReasonMeaning
LEAFThe current node has no children.
NAVIGATOR_STOPThe provider returned Decision.stop().
UNKNOWN_NODEThe provider named an id that was not offered.
BUDGET_EXHAUSTEDmaxNodesVisited was spent.
MAX_DEPTHThe 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
  • Tree ingestion, persistence, or a second document-tree model
  • Optional PDF / Office extractors
  • PageIndex competitor positioning