Class RoleRagBinding

java.lang.Object
com.tnsai.intelligence.rag.binding.RoleRagBinding

public final class RoleRagBinding extends Object
Per-Role RAG state, lazily initialized on first forRole(java.lang.Class<?>) call and cached for the lifetime of the JVM.

Holds:

  • A populated all-source VectorMemoryStore and BM25Index
  • Immutable entry partitions keyed by Knowledge.name()
  • Lazily cached source-scoped index bundles used when a search covers a subset of the sources
  • RAGStrategy instances for SEMANTIC, KEYWORD, HYBRID, GRAPH, HIERARCHICAL, TEMPORAL, and REASONING, built over the selected data capability at retrieval time. MULTI_QUERY is composed by UnifiedRetrievalRuntime; HYBRID RRF-fuses BM25, vector, and a GraphStore when one is unambiguous, otherwise the two-stream fuse plus a typed skip diagnostic; GRAPH binds an unambiguous GraphStoreProvider or fails the action; HIERARCHICAL binds a structurally valid hierarchy index or fails the action; TEMPORAL binds a structurally valid temporal index or fails the action; REASONING binds an unambiguous TreeNavigator for the declared model or fails the action.

Annotation-only bindings are shared by Role class. Builder-configured bindings are keyed by Role class plus the immutable source template, so agents with identical declarations share ingestion while different builder declarations cannot leak corpora into one another.

Knowledge.Type.FILE is the only type TnsAI bundles a loader for (LocalFileSourceLoader). The rest are extension points, not gaps: an application registers a SourceLoader for them from an optional module, and until it does, sources of that type are logged and skipped.

Vector-backed strategies and embedding-based deduplication share one process-wide EmbeddingFunction. Applications install an alternative by registering exactly one implementation in META-INF/services/com.tnsai.memory.advanced.EmbeddingFunction. Without one, the deterministic hash implementation remains the default; multiple providers fail binding initialization rather than selecting by classpath order. A vector-dependent strategy (SEMANTIC, HYBRID, HIERARCHICAL, TEMPORAL) on that default logs one warning per Role: the hash embedding ranks by token overlap, not meaning. KEYWORD, GRAPH, MULTI_QUERY and REASONING do not, because their scoring path does not use the vector store (HYBRID does, even though BM25 still contributes). Applications that intend the hash embedding set the tnsai.rag.acknowledgeHashEmbedding system property to true rather than lowering the log level. Provider calls are admitted one at a time without a wait queue, so implementations must return promptly; returned vectors must be finite, non-empty, dimensionally stable, and no wider than 16,384 values.

Since:
0.5.4 (issue #172, Phase 1)
  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    static final record 
    What a sync(java.lang.Class<?>) found, by source name, each list in name order.
  • Method Summary

    Modifier and Type
    Method
    Description
    int
    Number of documents successfully ingested for this Role.
    int
    documentCount(String sourceName)
    Number of documents the named source contributes to this binding, in the unit documentCount() counts.
    forRole(Class<?> roleClass)
    Returns the (lazily computed) binding for the given Role class.
    forRole(Class<?> roleClass, List<com.tnsai.rag.KnowledgeSourceConfig> builderSources)
    Returns a binding isolated by Role class and builder source template.
    forRole(Class<?> roleClass, Map<String,Path> resolvedPaths)
    Returns a binding for a Role whose @Knowledge declarations are kept, with the locations of the named ones supplied at run time.
    static void
    invalidate(Class<?> roleClass)
    Drops every cached binding for the given Role class, so the next forRole(java.lang.Class<?>) call re-reads the Role's @Knowledge declarations from scratch.
    static void
    invalidate(Class<?> roleClass, String sourceName)
    Drops cached state whose canonical source set contains sourceName while preserving every sibling binding and sequential cursor that does not depend on that source.
    static List<com.tnsai.rag.KnowledgeSourceConfig>
    locateDeclaredSources(Class<?> roleClass, Map<String,Path> resolvedPaths)
    Copies the Role's @Knowledge declarations, replacing the path of each named entry.
    The Role class this binding was built for.
    strategyFor(com.tnsai.annotations.Search.Strategy requested)
    Builds the RAGStrategy matching the requested @Search.strategy.
    strategyFor(com.tnsai.annotations.Search.Strategy requested, String navigatorModel)
    Builds the requested strategy, supplying the tree-navigation model Search.Strategy.REASONING needs.
    sync(Class<?> roleClass)
    Reads the files of every FILE source of the Role again now, against the manifest of its last load, so only a file that changed is read, and puts the binding that reads a change in place of the cached one, as a refresh does.
    sync(Class<?> roleClass, String sourceName)
    Syncs only sourceName.

    Methods inherited from class java.lang.Object

    equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Method Details

    • forRole

      public static RoleRagBinding forRole(Class<?> roleClass)
      Returns the (lazily computed) binding for the given Role class. Subsequent calls for the same class return the cached instance.

      If the Role declares no knowledge the returned binding is still valid — it just contains an empty vector store and BM25 index. Retrieval against it produces an empty document list, and the action proceeds without context.

      Parameters:
      roleClass - the Role's runtime class
      Returns:
      the per-Role binding (never null)
    • forRole

      public static RoleRagBinding forRole(Class<?> roleClass, Map<String,Path> resolvedPaths)
      Returns a binding for a Role whose @Knowledge declarations are kept, with the locations of the named ones supplied at run time.

      @Knowledge.path() is a compile-time constant. A corpus that lives somewhere only known once the run starts — a timestamped transcript directory, an episode folder — cannot be named by it. This overload closes that gap without a template language in the annotation: the Role declares everything it can (name, format, selectors, unit), leaves path empty or as a placeholder, and the caller supplies the resolved location here.

      Each entry names a source declared on roleClass. The declaration is copied with only its path replaced — see KnowledgeSourceConfig.withPath(String) — and then takes part in the binding exactly as a builder source does, so the located path becomes part of the binding key. A relative FILE location resolves by KnowledgePaths.resolve(java.lang.String), against the run's base directory when one is set, so the key carries the path resolved when the binding is asked for. Two runs with different folders get two bindings; nothing is shared or stale between them. A FILE location that resolves to the declared path gives the declaration itself, which is not a builder source, so it shares the binding forRole(Class) gives, as a dispatch given that location does.

      Naming a source the Role does not declare is an error, not a silent new source: the override exists to locate a declaration, and an unrecognised name almost always means a typo in one of the two places.

      Parameters:
      roleClass - the Role's runtime class
      resolvedPaths - source name to its location for this run
      Returns:
      the per-Role binding (never null)
      Throws:
      IllegalArgumentException - if a key names no @Knowledge on roleClass
      Since:
      0.16.0
    • locateDeclaredSources

      public static List<com.tnsai.rag.KnowledgeSourceConfig> locateDeclaredSources(Class<?> roleClass, Map<String,Path> resolvedPaths)
      Copies the Role's @Knowledge declarations, replacing the path of each named entry. Same conversion forRole(Class, Map) uses; the SCOP dispatch path feeds the result in as builder sources so its knowledge sees the run-time folder without the action body calling this factory.
      Parameters:
      roleClass - the Role's runtime class
      resolvedPaths - source name to its location for this run
      Returns:
      located copies, in map-iteration order
      Throws:
      IllegalArgumentException - if a key names no @Knowledge on roleClass
      Since:
      0.16.1
    • forRole

      public static RoleRagBinding forRole(Class<?> roleClass, List<com.tnsai.rag.KnowledgeSourceConfig> builderSources)
      Returns a binding isolated by Role class and builder source template.
    • invalidate

      public static void invalidate(Class<?> roleClass)
      Drops every cached binding for the given Role class, so the next forRole(java.lang.Class<?>) call re-reads the Role's @Knowledge declarations from scratch.

      Call this after the corpus behind a Role's knowledge sources changes — files rewritten, a document added or withdrawn. A binding is built once per Role class per JVM and memoised for the process lifetime, so without this call a running deployment keeps answering from the corpus as it stood at first dispatch, however long ago that was. @Search.cacheTTL does not cover it: the result cache expires against the same immutable index and re-searching it returns the same documents.

      Invalidation is per Role class and covers every binding that class owns, including the ones keyed by distinct builder-supplied source sets. It drops the loaded documents, the per-source indexes, the resolved graph strategies, and the retrieval-result cache together — they describe one corpus snapshot and are only coherent as a set.

      The rebuild reloads and re-chunks every source and rebuilds every index, but it does not embed a chunk again whose content and embedding provider are unchanged: the vectors of the bindings retired here are kept for one generation, keyed by provider compatibility and chunk content, and reused by the next build for this Role. A vector is a function of the provider and the text, not of the snapshot, so reuse leaves the coherence guarantee above intact.

      Retrieval that is already in flight is unaffected and completes against the snapshot it started on; the next dispatch rebuilds. The rebuild happens on that dispatch, not here, so the cost of re-ingesting lands on the caller that needs the fresh corpus. Explicit KnowledgeScope snapshots and cursors are unaffected; their owner closes them independently.

      Parameters:
      roleClass - Role whose knowledge binding should be discarded
      Since:
      0.13.0
    • invalidate

      public static void invalidate(Class<?> roleClass, String sourceName)
      Drops cached state whose canonical source set contains sourceName while preserving every sibling binding and sequential cursor that does not depend on that source.

      The source identifier is exact and case-sensitive. It must name an enabled annotation or builder source already known for this Role; null, blank, disabled, and unknown identifiers fail loudly. A ranked binding containing the source is retired as one coherent snapshot, including its result cache, scoped indexes, and graph corpus. Retrieval already in flight completes on its prior snapshot. A concurrent build containing the source is fenced and rebuilt before it can enter the cache.

      Sequential readers are narrower: only readers for the named source reset to the first unit. Positions for every sibling source remain unchanged. invalidate(Class) retains its role-wide behavior. Explicit KnowledgeScope snapshots and cursors are unaffected. Deterministic Role-owned remote indexes are not physically cleared by this narrower overload because another process or builder template may share that owner; the rebuilt snapshot filters remote entries that are no longer part of its canonical corpus before ranking. The role-wide overload retains the same non-destructive remote-owner behavior.

      Parameters:
      roleClass - Role whose source-backed state should be discarded
      sourceName - exact canonical knowledge-source name
      Throws:
      IllegalArgumentException - if the source name is null, blank, disabled, or unknown for the Role
      Since:
      0.16.3
    • sync

      public static RoleRagBinding.SyncResult sync(Class<?> roleClass)
      Reads the files of every FILE source of the Role again now, against the manifest of its last load, so only a file that changed is read, and puts the binding that reads a change in place of the cached one, as a refresh does. It works whether or not a source sets @Knowledge.refresh, and ignores the interval of one that does.

      A search never waits for a refresh; this call does. When another call refreshes or reloads a binding of the Role, this one waits for it to end and then checks the binding that is cached, so a change made before the call is read by the time it returns. A source whose check fails keeps its previous content, as on a refresh. A SEQUENTIAL reader of a source that changed starts again at the first unit; readers of a source that did not change, and of every other source, keep their position. A SEQUENTIAL source that is no longer declared, or no longer enabled, is dropped with its readers, so a next open of it throws as a first open does; the result lists it nowhere.

      A binding whose declarations changed since it was built, as when an agent binds or releases the Role with its own roles section, is built again from the current declarations before it is checked; a source whose declaration changed is loaded again and listed as changed, and every other source is checked as above.

      Only sources loaded through the bundled FILE loader are checked; no other source is listed. A binding a remote index owner holds is left alone, as on a refresh. A source that did not load is listed as failed and is loaded again by the next search after its backoff, as before. With nothing cached for the Role, nothing is read and the result is empty. Explicit KnowledgeScope snapshots and cursors are unaffected.

      Parameters:
      roleClass - Role whose FILE knowledge is read again
      Returns:
      the sources that changed, did not change, and failed
      Since:
      0.18.7
    • sync

      public static RoleRagBinding.SyncResult sync(Class<?> roleClass, String sourceName)
      Syncs only sourceName. The name is exact and case-sensitive and must name an enabled annotation or builder source known for the Role, as for invalidate(Class, String).
      Parameters:
      roleClass - Role whose FILE knowledge is read again
      sourceName - exact canonical knowledge-source name
      Returns:
      what sourceName came to
      Throws:
      IllegalArgumentException - if the source name is null, blank, disabled, or unknown for the Role
      Since:
      0.18.7
    • strategyFor

      public RAGStrategy strategyFor(com.tnsai.annotations.Search.Strategy requested)
      Builds the RAGStrategy matching the requested @Search.strategy. SEMANTIC, KEYWORD, and HYBRID are base strategies. MULTI_QUERY requires annotation-bound expansion configuration and is composed by UnifiedRetrievalRuntime; GRAPH resolves its GraphStoreProvider here, so a missing or ambiguous provider fails the action before the @Search.onFailure guard can observe it. HIERARCHICAL selects child hits semantically and expands their hierarchy parents; its hierarchy index is validated here, for the same reason. TEMPORAL re-ranks semantic child hits through bounded time decay; its temporal index is validated here, for the same reason. REASONING descends the same hierarchy with a TreeNavigator; a blank model or a missing provider fails here, for the same reason.
    • strategyFor

      public RAGStrategy strategyFor(com.tnsai.annotations.Search.Strategy requested, String navigatorModel)
      Builds the requested strategy, supplying the tree-navigation model Search.Strategy.REASONING needs.

      Other strategies ignore navigatorModel. REASONING rejects a blank model and an identifier no installed provider serves.

      Parameters:
      requested - strategy discriminant
      navigatorModel - exact TreeNavigator model identifier
      Returns:
      the constructed strategy
    • documentCount

      public int documentCount()
      Number of documents successfully ingested for this Role. Useful for tests asserting a binding actually loaded its sources, and for the _rag_document_count context value when retrieval runs against an empty store.
    • documentCount

      public int documentCount(String sourceName)
      Number of documents the named source contributes to this binding, in the unit documentCount() counts. A source that loaded nothing counts 0, and so does an enabled source of a type no loader is registered for: this binding holds it, but it contributes nothing. A source that did not load is not counted as 0: its recorded failure is thrown, as requireLoaded reports it. Counting builds no index and loads nothing again.
      Parameters:
      sourceName - a source this binding holds
      Returns:
      the number of documents, 0 or more
      Throws:
      com.tnsai.rag.RetrievalException - the source's recorded load failure, with the source and the role in its message
      IllegalArgumentException - when this binding holds no source of that name, such as an undeclared or disabled one
      Since:
      0.18.0
    • roleClass

      public Class<?> roleClass()
      The Role class this binding was built for.