Class SCOPBridge
This is a facade that delegates to focused helpers:
SystemPromptBuilder- annotation-to-prompt conversionLLMDispatcher- provider-specific HTTP transport (fallback)BridgeLLMClient- LLMClient adapter over HTTP fallbackLLMConfiguration- resolved LLM config (provider, model, endpoint, keys)ActionExecutionResult- action execution result wrapperLLMResponse- LLM response wrapper
When Core's LLMClientProvider SPI is available on the classpath
(e.g., tnsai-llm module), this bridge uses real provider implementations
(OllamaClient, OpenAIClient, etc.) with full streaming and retry support.
Otherwise, it falls back to BridgeLLMClient + LLMDispatcher.
executeAction(Object, String, Map) requires explicit
AgentPrincipal and AuthorityScope wiring through the fluent
setters. A LiabilitySink is optional and is never selected by default.
Missing identity/authority wiring fails before retrieval or the
action body so a SEQUENTIAL knowledge cursor cannot advance and then
still return an error.
-
Method Summary
Modifier and TypeMethodDescriptionauthorityScope(com.tnsai.accountability.AuthorityScope authorityScope) Records this authority scope on every dispatch.com.tnsai.security.AccessRequestRechecks current execution binding without hooks, retrieval, approval consumption or invocation.Builds a system prompt from TnsAI annotations on the target.voidconfigureExecution(com.tnsai.security.AccessSubject agent) Configure the existing action gate once at trusted host startup.voidconfigureExecution(com.tnsai.security.AccessSubject agent, Function<Object, String> targetSystem) Optional trusted lookup for existing AuthorityScope target-system bounds.Executes a TnsAI@Actionmethod on the target object.executeAction(Object target, String actionName, Map<String, Object> params, Map<String, Path> resolvedPaths) Executes a TnsAI@Actionand locates named knowledge sources for this dispatch only.executeAction(Object target, String actionName, Map<String, Object> params, Map<String, Path> resolvedPaths, com.tnsai.security.InvocationContext invocation) Trusted ingress overload.Optional<com.tnsai.metadata.ActionMetadata> getActionSpec(Object target, String actionName) Gets the ActionMetadata for a specific action on the target.static SCOPBridgeReturns a new SCOPBridge with the default read timeout (300s).static SCOPBridgegetInstance(long readTimeoutSeconds) Returns a new SCOPBridge with a custom read timeout.static SCOPBridgegetInstance(com.tnsai.identity.AgentPrincipal principal, com.tnsai.accountability.LiabilitySink liabilitySink, com.tnsai.accountability.AuthorityScope authorityScope) Returns a new SCOPBridge whose action executor is already accountability-wired.booleanhasTnsAIActions(Object target) Checks if a target object has TnsAI @Action annotations.knowledgeScope(com.tnsai.rag.KnowledgeScope scope) Uses an explicit knowledge-source lifetime for subsequent action dispatches.liabilitySink(com.tnsai.accountability.LiabilitySink liabilitySink) Publishes per-dispatch liability records to this sink.llmConfigurationSource(LLMConfigurationSource llmConfigurationSource) Registers the external layer consulted when LLM configuration resolves.org.json.JSONArrayprepareConversationForDispatch(org.json.JSONArray conversation, Object... conversationOwners) Creates an outbound request copy with a fresh dynamic state snapshot.principal(com.tnsai.identity.AgentPrincipal principal) requireLoaded(Object target, Collection<String> names) requireLoaded(Object, Collection, Map)with no per-call locations, so the bridge-level ones fromresolvedSourcePaths(Map)apply, as they do forexecuteAction(Object, String, Map).requireLoaded(Object target, Collection<String> names, Map<String, Path> resolvedPaths) Loads the named SEARCH sources oftargetin the binding thatexecuteAction(Object, String, Map, Map)with the same target andresolvedPathsbinds, and returns the number of documents each holds.requireReady(Object target, com.tnsai.rag.KnowledgeScope scope) requireReady(Object, com.tnsai.rag.KnowledgeScope, Map)with no per-call locations, so the bridge-level ones fromresolvedSourcePaths(Map)apply, as they do forexecuteAction(Object, String, Map).requireReady(Object target, com.tnsai.rag.KnowledgeScope scope, Map<String, Path> resolvedPaths) Checks, before a run, that every configuredWHEN_CONFIGUREDsource oftargetis ready: its file exists and loads at least one unit, in the binding a dispatch oftargetwith the same scope andresolvedPathsreads.resolvedSourcePaths(Map<String, Path> resolvedSourcePaths) Supplies run-time locations for@Knowledgenames whosepathis empty or a placeholder.Optional<com.tnsai.llm.LLMClient> resolveLLMClient(Object target) Resolves an LLMClient for the given target.resolveLLMSpec(Object role, Object agent, Object playground) Resolves LLM configuration, deriving the external source's lookup key fromagent— seeresolveLLMSpec(Object, Object, Object, String).resolveLLMSpec(Object role, Object agent, Object playground, String agentName) Resolves LLM configuration following the hierarchy: run filerolessection > run file agent type section > role's own@Config> agent@Configrolessection > agent@Config> external source > Role > Agent > Playground.sendToLLM(LLMConfiguration config, org.json.JSONArray conversation, org.json.JSONArray tools) Sends a message to the LLM and returns the response.sendToLLM(LLMConfiguration config, org.json.JSONArray conversation, org.json.JSONArray tools, Object conversationOwner, com.tnsai.rag.ChatKnowledgeBinding binding, com.tnsai.rag.ChatRetrievalContext context) Sends a SCOP conversation through owner-scoped chat retrieval before dispatching it to the configured provider.sendToLLMForOwners(LLMConfiguration config, org.json.JSONArray conversation, org.json.JSONArray tools, List<?> conversationOwners, List<com.tnsai.rag.ChatKnowledgeBinding> bindings, com.tnsai.rag.ChatRetrievalContext context) Sends a SCOP conversation through the chat knowledge of several conversation owners, such as an agent followed by every@RoleSpecrole it plays, before dispatching it to the configured provider.sendToLLMForOwners(LLMConfiguration config, org.json.JSONArray conversation, org.json.JSONArray tools, List<?> conversationOwners, List<com.tnsai.rag.ChatKnowledgeBinding> bindings, com.tnsai.rag.ChatRetrievalContext context, String requestSection) Sends a conversation for several owners, assendToLLMForOwners(LLMConfiguration, JSONArray, JSONArray, List, List, ChatRetrievalContext)does, with a section the caller renders for this request alone, such as an agent's recent activity.
-
Method Details
-
getInstance
Returns a new SCOPBridge with the default read timeout (300s).Every call returns a new instance: this is a factory, not an accessor for a shared bridge. Configuration such as
configureExecution(com.tnsai.security.AccessSubject),principal(AgentPrincipal),authorityScope(AuthorityScope),liabilitySink(LiabilitySink),resolvedSourcePaths(Map)andllmConfigurationSource(LLMConfigurationSource)belongs to the instance it is called on, so apply it to the instance that executes the actions: configuring the result of a separategetInstance()call leaves every other instance unchanged, and some of these settings are then missing silently rather than with an error. -
getInstance
Returns a new SCOPBridge with a custom read timeout. LikegetInstance(), every call returns a new instance whose configuration is its own. -
getInstance
public static SCOPBridge getInstance(com.tnsai.identity.AgentPrincipal principal, com.tnsai.accountability.LiabilitySink liabilitySink, com.tnsai.accountability.AuthorityScope authorityScope) Returns a new SCOPBridge whose action executor is already accountability-wired. LikegetInstance(), every call returns a new instance whose configuration is its own.- Parameters:
principal- identity recorded on every dispatchliabilitySink- sink that receives per-dispatch recordsauthorityScope- authority recorded on every dispatch- Returns:
- wired bridge
- Throws:
NullPointerException- if any argument isnull- Since:
- 0.15.0
-
principal
Attributes subsequentexecuteAction(java.lang.Object, java.lang.String, java.util.Map<java.lang.String, java.lang.Object>, java.util.Map<java.lang.String, java.nio.file.Path>, com.tnsai.security.InvocationContext)calls to this principal.- Parameters:
principal- identity recorded on every dispatch- Returns:
- this bridge
- Throws:
NullPointerException- ifprincipalisnull- Since:
- 0.15.0
-
liabilitySink
Publishes per-dispatch liability records to this sink.There is no silent no-op default. Callers choose the sink explicitly, including test recording sinks.
- Parameters:
liabilitySink- sink that receives per-dispatch records- Returns:
- this bridge
- Throws:
NullPointerException- ifliabilitySinkisnull- Since:
- 0.15.0
-
authorityScope
Records this authority scope on every dispatch.Use
AuthorityScope.unrestricted(java.time.Duration)when the caller deliberately chose not to narrow — that is still an explicit decision, not a hidden default.- Parameters:
authorityScope- authority recorded on every dispatch- Returns:
- this bridge
- Throws:
NullPointerException- ifauthorityScopeisnull- Since:
- 0.15.0
-
resolvedSourcePaths
Supplies run-time locations for@Knowledgenames whosepathis empty or a placeholder.Subsequent
executeAction(Object, String, Map)calls put this map on the dispatch context so action knowledge (SEARCH, DIRECT and SEQUENTIAL) binds through the same locate-and-override rules asRoleRagBinding.forRole(Class, Map). A per-call map onexecuteAction(Object, String, Map, Map)wins for that dispatch only.- Parameters:
resolvedSourcePaths- source name to its location; copied- Returns:
- this bridge
- Throws:
NullPointerException- ifresolvedSourcePathsor a value isnull- Since:
- 0.16.1
-
knowledgeScope
Uses an explicit knowledge-source lifetime for subsequent action dispatches. The caller owns and closes the scope when its lifetime ends; active leased conversations keep resources until they finish. This bridge never closes it.- Parameters:
scope- knowledge-source lifetime, including one closing after this thread's turn has already acquired a lease- Returns:
- this bridge
- Since:
- 0.17.4
-
buildSystemPromptFromAnnotations
Builds a system prompt from TnsAI annotations on the target. Delegates toSystemPromptBuilder. -
hasTnsAIActions
Checks if a target object has TnsAI @Action annotations. -
getActionSpec
Gets the ActionMetadata for a specific action on the target. -
configureExecution
public void configureExecution(com.tnsai.security.AccessSubject agent) Configure the existing action gate once at trusted host startup. -
configureExecution
public void configureExecution(com.tnsai.security.AccessSubject agent, Function<Object, String> targetSystem) Optional trusted lookup for existing AuthorityScope target-system bounds. -
authorizeAction
public com.tnsai.security.AccessRequest authorizeAction(Object target, String actionName, Map<String, Object> params) Rechecks current execution binding without hooks, retrieval, approval consumption or invocation. -
executeAction
public ActionExecutionResult executeAction(Object target, String actionName, Map<String, Object> params, Map<String, Path> resolvedPaths, com.tnsai.security.InvocationContext invocation) Trusted ingress overload. Invocation identity is never read from params.- Since:
- 0.18.0
-
executeAction
public ActionExecutionResult executeAction(Object target, String actionName, Map<String, Object> params) Executes a TnsAI@Actionmethod on the target object. Routes through Core'sActionExecutorpipeline.principal(AgentPrincipal)andauthorityScope(AuthorityScope)are required before dispatch.liabilitySink(LiabilitySink)is an optional recording destination. Missing wiring fails before retrieval or the action body so a SEQUENTIAL knowledge cursor cannot advance and then still return an error. Role and plain-Object hosts share this gate.- Parameters:
target- action host (Role or plain Object)actionName- declared action method nameparams- action parameters;nullbecomes empty- Returns:
- success, or a configuration/execution error that did not run retrieval or the action body when wiring is missing
-
executeAction
public ActionExecutionResult executeAction(Object target, String actionName, Map<String, Object> params, Map<String, Path> resolvedPaths) Executes a TnsAI@Actionand locates named knowledge sources for this dispatch only.resolvedPathsis copied onto the dispatch context underRetrievalSpi.RESOLVED_SOURCE_PATHS_KEY. Action knowledge keeps reading the annotations; the map only replacespathfor names the Role already declared.nullfalls back toresolvedSourcePaths(Map).- Parameters:
target- action host (Role or plain Object)actionName- declared action method nameparams- action parameters;nullbecomes emptyresolvedPaths- source name to its location for this call, ornullto use the fluent setter- Returns:
- success, or a configuration/execution error
- Since:
- 0.16.1
-
requireLoaded
requireLoaded(Object, Collection, Map)with no per-call locations, so the bridge-level ones fromresolvedSourcePaths(Map)apply, as they do forexecuteAction(Object, String, Map).- Parameters:
target- action host (Role or plain Object)names- declared, enabled sources read withuseSEARCH- Returns:
- each name's document count, in sorted name order
- Since:
- 0.18.0
-
requireLoaded
public Map<String,Integer> requireLoaded(Object target, Collection<String> names, Map<String, Path> resolvedPaths) Loads the named SEARCH sources oftargetin the binding thatexecuteAction(Object, String, Map, Map)with the same target andresolvedPathsbinds, and returns the number of documents each holds. Nothing is retrieved and no action runs.The check goes through the code the dispatch runs: it binds the role to its owner's configuration, takes the per-call locations or, for
null, the bridge-level ones, and leases this bridge'sknowledge scope, so the binding checked is the one the dispatch leases. Use this rather than the retrieval runtime's own preflight for a target this bridge dispatches; that one sees neither the bridge-level locations nor the owner's configuration.- Parameters:
target- action host (Role or plain Object)names- declared, enabled sources read withuseSEARCHresolvedPaths- source name to its location, as given toexecuteAction, ornullto use the fluent setter- Returns:
- each name's document count, in sorted name order
- Throws:
IllegalArgumentException- when a name or a location names a source the target does not declare, or a name is not read withuseSEARCHcom.tnsai.rag.RetrievalException- when a named source did not loadIllegalStateException- when no retrieval runtime is on the classpath, the knowledge scope is closed, the target's class has taken a differentrolessection, or the owner'srolessection names a role the owner does not play yet; the role is bound as a dispatch binds it, so check it once the owner plays all its configured rolesUnsupportedOperationException- when the retrieval runtime has no preflight- Since:
- 0.18.0
-
requireReady
requireReady(Object, com.tnsai.rag.KnowledgeScope, Map)with no per-call locations, so the bridge-level ones fromresolvedSourcePaths(Map)apply, as they do forexecuteAction(Object, String, Map).- Parameters:
target- action host (Role or plain Object)scope- the knowledge scope the host later sets withknowledgeScope(com.tnsai.rag.KnowledgeScope), ornullfor the process-wide one- Returns:
- each checked source's unit count, in sorted name order
- Since:
- 0.18.1
-
requireReady
public Map<String,Integer> requireReady(Object target, com.tnsai.rag.KnowledgeScope scope, Map<String, Path> resolvedPaths) Checks, before a run, that every configuredWHEN_CONFIGUREDsource oftargetis ready: its file exists and loads at least one unit, in the binding a dispatch oftargetwith the same scope andresolvedPathsreads. An unconfigured WHEN_CONFIGURED source is left out, and so is every MUST_LOAD source.The role is bound to its owner's configuration first, as a dispatch binds it. A target that then declares no knowledge, or a bridge with no retrieval runtime on the classpath, has nothing to check: the call returns an empty map without leasing the scope.
Pass the scope this bridge's dispatch will use, the one later given to
knowledgeScope(com.tnsai.rag.KnowledgeScope); another scope loads a generation the dispatch does not read. Unlike that setter, this method acceptsnull, meaning the process-wide cache, as a dispatch with no scope set uses.- Parameters:
target- action host (Role or plain Object)scope- the knowledge scope, ornullfor the process-wide oneresolvedPaths- source name to its location, as given toexecuteAction, ornullto use the fluent setter- Returns:
- each checked source's unit count, in sorted name order
- Throws:
com.tnsai.exceptions.KnowledgeNotReadyException- when a checked source's file is missing, loads no unit, or fails to load or count, whatever the reasoncom.tnsai.rag.RetrievalException- unchanged, when it comes from outside a source's load and count:CONFIGURATIONwhen an action of the target reads a WHEN_CONFIGURED source DIRECT, or the binding's own lease or buildIllegalArgumentException- for a source the knowledge generation cannot hold or readIllegalStateException- when the knowledge scope is closed, the target's class has taken a differentrolessection, or the owner'srolessection names a role the owner does not play yetUnsupportedOperationException- when the retrieval runtime has no readiness check- Since:
- 0.18.1
-
llmConfigurationSource
Registers the external layer consulted when LLM configuration resolves.Whatever the source names wins over
@RoleSpec.llm()and@AgentSpec.llm(); whatever it leaves out keeps its annotation value. The class configuration levels and the run file'sAgentssection sit above it, so this is the place for settings shared by every agent, such as a run'sLLMsection; an agent type's own settings reach the bridge through the run file, asresolveLLMSpec(Object, Object, Object, String)describes. The source is read on every resolution and never cached here, so an edit is visible to the next resolution in the same process.A source is trusted infrastructure. It can name an
apiKeyEnv, which reads any environment variable or System property the process can see, and anendpoint, which decides where that value is sent — read the trust paragraph onLLMConfigurationSourcebefore registering one.- Parameters:
llmConfigurationSource- the external source, e.g.LLMConfigurationSource.folder(Path)orLLMConfigurationSource.environment();LLMConfigurationSource.noOp()to disable the layer- Returns:
- this bridge
- Throws:
NullPointerException- ifllmConfigurationSourceisnull- Since:
- 0.16.4
-
resolveLLMSpec
Resolves LLM configuration, deriving the external source's lookup key fromagent— seeresolveLLMSpec(Object, Object, Object, String).- Throws:
IllegalStateException- if a@RoleSpecrole's class has taken a differentrolessection in this process, from another agent class or from a dispatch with no owner
-
resolveLLMSpec
public Optional<LLMConfiguration> resolveLLMSpec(Object role, Object agent, Object playground, String agentName) Resolves LLM configuration following the hierarchy: run filerolessection > run file agent type section > role's own@Config> agent@Configrolessection > agent@Config> external source > Role > Agent > Playground.The annotation tiers are combined in two groups, because not every setting is safe to mix across authors:
- Routing —
provider,model,endpoint,apiKeyEnv— is taken whole from the highest-precedence tier that declares amodel, which is exactly the tier that used to win outright. A tier naming other routing fields without a model contributes nothing, as before, but now says so. These four only mean anything as a set: a model belongs to a provider, an endpoint speaks one provider's wire format, a key is issued by one provider. Letting them cross tier boundaries independently would send a role author's local model to an agent author's hosted endpoint under that host's key — a configuration neither author wrote. - Tuning —
temperatureandmaxTokens— merges field by field across every tier. These carry no routing meaning, so an agent that declares a temperature but no model now contributes that temperature to whichever tier supplies the routing. That merge is the change TAN-6201 asked for; before it, the whole tier was skipped becausehasModel()was false.
A setting no layer declares keeps the default
LLMSpecdeclares for it.A tier is read through
LLMOverrides.fromAnnotation(LLMSpec), which treats a member as declared when it differs fromLLMSpec's own default — necessarily, since@AgentSpec.llm()and@RoleSpec.llm()both default to@LLMSpec()and therefore carry a full set of member values whether or not their author wrote any of them.The one all-or-nothing rule that remains is the terminal one: if no layer at all supplies a model, the resolution yields
Optional.empty()rather than a configuration that names no model.A tier that declares
modelwithoutprovidertherefore gets theLLMSpecdefault provider, not a lower tier's — which is exactly what that annotation means on its own, sinceprovider()defaults toOLLAMA.The external source is the one layer that does overlay every field individually, including routing. That is deliberate and is what TAN-6201 requires: the tiers are different authors, whereas a source is one operator configuring a deployment, and requiring them to restate provider and temperature in every file is the duplication this layer exists to remove. A source naming
modelwithoutproviderkeeps the annotation's provider, so the two must agree.Above the external source sit the
llmlevels of the class configurations (Config): the agent's file, then therolessection bound for the role, then the role's own file, then the agent type's section of the run file a host passed toClassConfig.useBaseDirectory(ClassConfig.RunFile)and therolessection that gives the role. The bound sections are the ones the role's knowledge reads; with no agent they are empty, so only the role's own file applies, and an agent class that gives the role a section fails. Each overlays field by field, like the external source, so a file namingmodelalone keeps the provider resolved below it. The class configuration is read once per class and run.- Parameters:
agentName- lookup key for the external source. The no-key overload derives it from the agent's owngetName()— whichcom.tnsai.agents.Agentdeclares — and falls back to the simple class name; pass it explicitly to override that derivation. Anullor blank key skips the source.- Throws:
LLMConfigurationException- if the external source holds settings for the agent but they cannot be readIllegalStateException- if a@RoleSpecrole's class has taken a differentrolessection in this process, from another agent class or from a dispatch with no owner; if a class configuration file or the run file section breaks its rules (ClassConfig.of(java.lang.Class<?>),ClassConfig.useBaseDirectory(ClassConfig.RunFile)), such as a simple-name key two classes share; or if the run file section givesrolesthat name no role the agent plays, or that cannot be checked because the agent does not list its roles- Since:
- 0.16.4
- Routing —
-
resolveLLMClient
Resolves an LLMClient for the given target. Prefers Core's LLMClientProvider SPI when available; falls back to BridgeLLMClient.- Throws:
IllegalStateException- if the target's class, a@RoleSpecrole, has taken a differentrolessection in this process, from another agent class or from a dispatch with no owner
-
sendToLLM
public LLMResponse sendToLLM(LLMConfiguration config, org.json.JSONArray conversation, org.json.JSONArray tools) Sends a message to the LLM and returns the response. Delegates toLLMDispatcher. -
prepareConversationForDispatch
public org.json.JSONArray prepareConversationForDispatch(org.json.JSONArray conversation, Object... conversationOwners) Creates an outbound request copy with a fresh dynamic state snapshot.The caller-owned conversation is never mutated. Only fields explicitly annotated with
Stateare read. Static persona and instruction content stays in place while any prior bridge-markedCurrent Statesection is replaced by one versioned request-local section. Calls with no annotated owners return a deep copy after removing any prior bridge-marked state.When multiple owners contribute state, owners are rendered in caller encounter order and each owner's fields are sorted lexically by their effective
state name. Repeated references to the same owner object are rendered once. This transport-neutral preparation step must be used immediately before both buffered and streaming LLM dispatches so action/tool-loop mutations are visible to the follow-up request without growing stored history.Diagnostics log only owner and field counts; state values are never written to logs by this bridge.
Prompts persisted from bridge versions that predate versioned dynamic markers must be rebuilt once. This method deliberately does not infer section boundaries from untrusted Markdown headings.
- Parameters:
conversation- caller-owned conversation historyconversationOwners- exact owner or role objects whose annotated state contributes to this request; null entries and annotation-free objects are ignored, and each owner is passed itself, not inside aCollection- Returns:
- a request-local deep copy, never the input instance
- Throws:
NullPointerException- ifconversationisnullIllegalArgumentException- if an entry ofconversationOwnersis aCollection, or annotated state must be added to a system message whose content is neither text nor a structured content array- Since:
- 0.16.0
-
sendToLLM
public LLMResponse sendToLLM(LLMConfiguration config, org.json.JSONArray conversation, org.json.JSONArray tools, Object conversationOwner, com.tnsai.rag.ChatKnowledgeBinding binding, com.tnsai.rag.ChatRetrievalContext context) Sends a SCOP conversation through owner-scoped chat retrieval before dispatching it to the configured provider.The exact
conversationOwnersupplies the@AgentSpec(knowledge = ...)declarations; its enabled SEARCH entries are the ones the binding may search. The caller supplies the matching owner-scoped binding and request-local context; the bridge does not discover owners from global state or keep retrieval lifecycle state of its own. Run, episode, reset, and end transitions remain explicit throughChatKnowledgeBinding.invalidate()andChatKnowledgeBinding.close().The owner's chat knowledge is the one an
Agentchat turn sees (KnowledgeDeclarations.chatScope(List)): enabled SEARCH and DIRECT entries, SEQUENTIAL ignored, GRAPH left out. DIRECT entries are given whole through the retrieval runtime on every call, within their budget against the input window ofconfig's model, and render around the SEARCH evidence in declaration order. The bridge keeps no state for them, so each call loads and measures them again.Knowledge is added only to a deep copy of the outbound conversation. A call without a retrieval context, owners without chat knowledge, empty results, and recoverable retrieval failures preserve the existing direct-dispatch behavior. A binding that belongs to another owner fails the call even without a retrieval context.
- Parameters:
config- provider-neutral LLM configurationconversation- caller-owned conversation historytools- optional provider-neutral tool declarationsconversationOwner- exact object whose chat annotations apply; chat-enabled bindings must be created with the same object through an exact-ownerChatKnowledgeBindingfactory; not aCollection, as several owners go tosendToLLMForOwners(LLMConfiguration, JSONArray, JSONArray, List, List, ChatRetrievalContext)binding- exact-owner-scoped live retrieval binding, ornullwhen the owner has no SEARCH chat knowledge or the optional retrieval runtime is unavailablecontext- immutable request-local scope and lifecycle context, ornullwhen the optional retrieval runtime is unavailable- Returns:
- provider response
- Throws:
IllegalArgumentException- whenconversationOwneris aCollection, with or without a retrieval context, or the owner's knowledge and the supplied binding disagreeIllegalStateException- when the owner's DIRECT knowledge has no retrieval runtime to give it, or its knowledge breaks a declaration rulecom.tnsai.rag.RetrievalException- when the owner's DIRECT knowledge cannot be given whole, exceeds its budget, or relies on the default budget while the model input window is unknown- Since:
- 0.14.0
-
sendToLLMForOwners
public LLMResponse sendToLLMForOwners(LLMConfiguration config, org.json.JSONArray conversation, org.json.JSONArray tools, List<?> conversationOwners, List<com.tnsai.rag.ChatKnowledgeBinding> bindings, com.tnsai.rag.ChatRetrievalContext context) Sends a SCOP conversation through the chat knowledge of several conversation owners, such as an agent followed by every@RoleSpecrole it plays, before dispatching it to the configured provider.An agent owner's chat knowledge is the one
sendToLLM(LLMConfiguration, JSONArray, JSONArray, Object, ChatKnowledgeBinding, ChatRetrievalContext)gives that owner alone. A role owner, an object whose class carries@RoleSpecwithout@AgentSpec, gives the enabled DIRECT and SEARCH entries of its@RoleSpecknowledge, which the single-owner call never reads for chat. The turn sees their union: the owners in list order, each owner's blocks in its declaration order, and each owner's SEARCH evidence where its own first SEARCH entry was declared, or after its blocks when it declares none, fitted to its own binding's context window, all behind one boundary. A DIRECT entry withmaxTokensmust fit that value; the DIRECT entries without it, of every listed owner together, share one default budget of 25% of the model input window, so owners that each fit on their own can fail together. Each call loads and measures every owner's DIRECT entries again. An owner's recoverable retrieval failure or oversized evidence fails open for that owner only, as in the single-owner call; any other retrieval failure fails the call, and a failure while rendering the turn dispatches the whole turn ungrounded. Owners are searched one after another under the onecontext, so the turn's retrieval time is their sum and a later owner may find the context's deadline already spent.@Stateis refreshed for every owner on each request.The caller decides which owners take part; a role owner is not checked against an agent's
@AgentSpecroles. Declaration rules, such as unique knowledge names and a MEMORY entry standing alone, apply to each owner's own declarations, not to the union, so a source or name that two owners declare is given once for each of them. Without a retrieval context, no owner's knowledge is read and the turn is dispatched without knowledge, with only its@Staterefreshed.Bindings stay exact-owner: each binding must have been created for one listed owner object, and an owner has at most one. A binding that belongs to no listed owner fails the call, even without a retrieval context. An owner whose SEARCH knowledge has no binding is not searched, as in the single-owner call.
- Parameters:
config- provider-neutral LLM configurationconversation- caller-owned conversation historytools- optional provider-neutral tool declarationsconversationOwners- the exact owner objects whose chat annotations apply, in order; at least one, each listed once and none aCollectionbindings- exact-owner-scoped live retrieval bindings, at most one per listed owner, in any order;nullor empty when no owner is searchedcontext- immutable request-local scope and lifecycle context, ornullwhen the optional retrieval runtime is unavailable- Returns:
- provider response
- Throws:
NullPointerException- whenconversationOwnersisnullor names anullowner, orbindingsholds anullIllegalArgumentException- whenconversationOwnersis empty, lists an owner twice or lists aCollection, a binding belongs to no listed owner or shares its owner with another, or an owner's knowledge and its binding disagreeIllegalStateException- when, with a retrieval context, an owner's DIRECT knowledge has no retrieval runtime to give it or an owner's knowledge breaks a declaration rulecom.tnsai.rag.RetrievalException- when an owner's DIRECT knowledge cannot be given whole or exceeds its budget, the owners' DIRECT knowledge together exceeds the shared default budget or cannot be checked against it because the retrieval runtime reports no DIRECT usage row for an entry withoutmaxTokensor the model input window is unknown, or an owner's retrieval fails in a way its failure policy does not let opencom.tnsai.cancellation.CancellationException- when the retrieval context's cancellation token is cancelled while an owner's knowledge is given or searched; a retrieval engine'sjava.util.concurrent.CancellationExceptionalso passes through- Since:
- 0.18.4
-
sendToLLMForOwners
public LLMResponse sendToLLMForOwners(LLMConfiguration config, org.json.JSONArray conversation, org.json.JSONArray tools, List<?> conversationOwners, List<com.tnsai.rag.ChatKnowledgeBinding> bindings, com.tnsai.rag.ChatRetrievalContext context, String requestSection) Sends a conversation for several owners, assendToLLMForOwners(LLMConfiguration, JSONArray, JSONArray, List, List, ChatRetrievalContext)does, with a section the caller renders for this request alone, such as an agent's recent activity.The section is added only to the outbound copy, never to
conversation, so it is not carried into the next request. It ends the last system message, before the owners' refreshed annotated state, as text or as a text part of structured content; a conversation without a system message is opened with one holding it. Its dynamic state markers are escaped, so it cannot open or close the state section.On a grounded request, the untrusted-context policy is appended to the last system message only when that message does not already hold its exact text, from the section, the state or anything else. A section that quotes the policy verbatim, for example in a peer's message, therefore keeps the policy paragraph from being appended. The policy is not removed by this, since the quoted text is still there, but the section can frame or contradict it, and it then sits in the section rather than after the state.
- Parameters:
config- provider-neutral LLM configurationconversation- caller-owned conversation historytools- optional provider-neutral tool declarationsconversationOwners- the exact owner objects whose chat annotations apply, in order; at least one, each listed once and none aCollectionbindings- exact-owner-scoped live retrieval bindings, at most one per listed owner, in any order;nullor empty when no owner is searchedcontext- immutable request-local scope and lifecycle context, ornullwhen the optional retrieval runtime is unavailablerequestSection- the section for this request, ornullor blank for none- Returns:
- provider response
- Throws:
NullPointerException- as the call without a section doesIllegalArgumentException- as the call without a section does, or when the section must be added to a system message whose content is neither text nor a structured content arrayIllegalStateException- as the call without a section doescom.tnsai.rag.RetrievalException- as the call without a section doescom.tnsai.cancellation.CancellationException- as the call without a section does- Since:
- 0.18.5
-