Advanced Agent Features
Beyond the basic Agent lifecycle (create, chat, stop), TnsAI.Core provides specialized subsystems for cognitive support, streaming, ensemble execution, hierarchy management, and chat orchestration. These are internal components extracted from the Agent class for cohesion; most are accessed through the Agent public API rather than directly.
AgentCognitiveSupport
com.tnsai.agents.AgentCognitiveSupport holds planning, reasoning, evaluation, environment perception, and feedback collection for an Agent. It is created internally during agent construction and accessed through AgentCapabilities.
Evaluation Hooks
Eval hooks fire at lifecycle points (before/after chat, before/after tool call, on error, on agent stop) and record metrics into an EvalContext.
// Add an evaluation hook
agent.addEvalHook(myHook);
// Enable/disable evaluation
agent.setEvalEnabled(true);
// Record goal completion
agent.recordGoalCompletion("goal-1", true, Map.of("steps", 3));Key methods on AgentCognitiveSupport:
| Method | Description |
|---|---|
addEvalHook(EvalHook hook) | Register an eval hook |
removeEvalHook(EvalHook hook) | Unregister an eval hook |
clearEvalContext() | Create a fresh EvalContext with new session ID |
setEvalEnabled(boolean) | Enable or disable all eval hooks |
isEvalEnabled() | Check if evaluation is active |
recordGoalCompletion(String goalId, boolean success, Map<String, Object> details) | Record a goal outcome |
fireOnBeforeChat(String message) | Fire pre-chat eval event |
fireOnAfterChat(String result, long latencyMs) | Fire post-chat eval event |
fireOnBeforeToolCall(String toolName, Map<String, Object> arguments) | Fire pre-tool eval event |
fireOnAfterToolCall(String toolName, Object result, boolean success, long latencyMs) | Fire post-tool eval event |
fireOnAgentStop(String status) | Fire agent stop event and complete the eval context |
Planning (BDI)
Planning uses a PlannerHandle (SPI, provided by tnsai-intelligence) for GOAP/HTN planning.
// Set a planner
agent.setPlannerHandle(myPlanner);
// Plan for all goals
List<PlannerHandle.PlanStep> steps = agent.plan();
// Plan for a specific goal
List<PlannerHandle.PlanStep> steps = agent.planForGoal("deliverPackage");
// Execute a plan
PlannerHandle.PlanResult result = agent.executePlan(steps);Key methods on AgentCognitiveSupport:
| Method | Signature |
|---|---|
plan | List<PlanStep> plan(List<Role> roles) |
planForGoal | List<PlanStep> planForGoal(String goalName, List<Role> roles) |
executePlan | PlanResult executePlan(List<PlanStep> steps, List<Role> roles) |
extractCurrentState | Map<String, Object> extractCurrentState(List<Role> roles) |
setPlannerHandle | void setPlannerHandle(PlannerHandle handle) |
getPlannerHandle | Optional<PlannerHandle> getPlannerHandle() |
isPlanningEnabled | boolean isPlanningEnabled() |
Reasoning Strategy
Reasoning strategies (ReAct, Tree-of-Thought, etc.) are pluggable via ReasoningStrategyHandle.
agent.setReasoningStrategy(reactStrategy);
// After chat, inspect reasoning
Optional<ReasoningResult> result = agent.getLastReasoningResult();When reasoning is enabled, AgentOrchestrator.chat() routes through the strategy instead of direct LLM invocation.
Environment Perception
Agents can perceive and act on environments (BDI perception-to-belief pipeline).
agent.setEnvironment(myEnvironment);
Percept percept = agent.perceive();
ActionOutcome outcome = agent.actOnEnvironment("moveForward", Map.of("speed", 5));Environment changes trigger onEnvironmentChangeWithRoles, which checks for unsatisfied goals if a planner is configured.
Feedback Collection
FeedbackCollector (SPI, provided by tnsai-intelligence) records tool outcomes for preference learning.
agent.setFeedbackCollector(myCollector);
FeedbackCollector collector = agent.getFeedbackCollector();Tool outcomes are recorded automatically via recordToolOutcome(toolName, result, success, latencyMs).
AgentEnsembleExecutor
com.tnsai.agents.ensemble.AgentEnsembleExecutor provides multi-LLM ensemble operations accessed via agent.getEnsembleExecutor().
Patterns
| Method | Signature | Description |
|---|---|---|
parallelChat | ParallelResults parallelChat(String message, List<LLMClient> llms) | Fan out to all LLMs, collect all responses |
raceChat | Optional<LLMResult> raceChat(String message, List<LLMClient> llms, long timeoutSeconds) | First successful response wins |
consensusChat | Optional<LLMResult> consensusChat(String message, List<LLMClient> llms, Function<String, Double> scorer) | Score responses, pick highest |
majorityVoteChat | Optional<LLMResult> majorityVoteChat(String message, List<LLMClient> llms) | Most common response pattern |
ensembleChat | Optional<String> ensembleChat(String message, List<LLMClient> llms, LLMClient aggregator) | Synthesize responses with aggregator LLM |
// Fan-out to multiple LLMs
ParallelLLM.ParallelResults results = agent.getEnsembleExecutor()
.parallelChat("Explain quantum computing", List.of(gpt4, claude, gemini));
// Race: first response wins
Optional<ParallelLLM.LLMResult> fastest = agent.getEnsembleExecutor()
.raceChat("Quick question", List.of(gpt4, claude), 10);
// Consensus with scoring
Optional<ParallelLLM.LLMResult> best = agent.getEnsembleExecutor()
.consensusChat("Review this code", llms, response -> scoreQuality(response));
// Ensemble synthesis
Optional<String> synthesized = agent.getEnsembleExecutor()
.ensembleChat("Complex analysis", List.of(gpt4, claude), aggregatorLLM);AgentHierarchyManager
com.tnsai.agents.hierarchy.AgentHierarchyManager manages parent-child relationships between agents, including bidirectional consistency and task delegation/escalation.
Hierarchy Setup
Agent supervisor = new SupervisorAgent();
Agent developer = new DeveloperAgent();
Agent tester = new TesterAgent();
// Bidirectional parent-child links
supervisor.addChild(developer);
supervisor.addChild(tester);
// developer.getParent() == supervisor
// supervisor.getChildren() contains both
// Add multiple at once
supervisor.addChildren(developer, tester, designer);Task Delegation and Escalation
// Supervisor delegates to child
supervisor.delegateToChild(developer.id(), "writeCode", Map.of(
"task", "Implement authentication",
"language", "Java"
));
// Developer escalates to supervisor
developer.escalateToParent("requestApproval", Map.of(
"reason", "Production deployment requires sign-off"
));Both methods use AgentCommunicationManager to send TaskMessageType.REQUEST messages. delegateToChild throws NoSuchElementException if the child is not found; escalateToParent throws IllegalStateException if no parent is set.
HierarchyContext Interface
The manager uses a HierarchyContext interface (implemented by Agent) to access internals:
public interface HierarchyContext {
String getAgentId();
Agent getAgentRef();
Agent getParentRef();
void setParentRef(Agent parent);
List<Agent> getChildrenSnapshot();
boolean hasChild(Agent child);
boolean addChildDirect(Agent child);
boolean removeChildDirect(Agent child);
Agent findChildById(String childId);
AgentCommunicationManager getCommunicationManager();
}AgentStreamingSupport
com.tnsai.agents.streaming.AgentStreamingSupport handles streaming and event-based chat operations.
Token Streaming
// Stream tokens as a Java Stream
Stream<String> tokens = agent.streamChat("Tell me about Java");
tokens.forEach(System.out::print);Streaming with Tool Calls
The streamChatWithTools method combines streaming with multi-turn tool execution (up to 10 iterations):
agent.streamChatWithTools("Search for TnsAI docs", chunk -> {
if (chunk.isContent()) {
System.out.print(chunk.getContent());
} else if (chunk.isToolCall()) {
System.out.println("Tool: " + chunk.getToolCall().get().getName());
}
});Flow: stream LLM response -> if tool calls received, execute them -> send results back to LLM -> repeat until final text or max iterations (10).
Event-Based Chat (AG-UI)
// With direct event consumer
agent.chatWithEvents("Hello", event -> {
if (event instanceof TextDeltaEvent delta) {
System.out.print(delta.getText());
} else if (event instanceof ToolCallStartEvent tc) {
System.out.println("Calling: " + tc.getToolName());
}
});
// With session-based publisher
agent.chatWithEvents("Hello", sessionId);Events emitted: RunStartEvent, StatusEvent, TextDeltaEvent, ToolCallStartEvent, ToolCallEndEvent, ErrorEvent, RunEndEvent.
Request-Local Retrieval Context
The retrieval-aware overloads accept one ChatRetrievalContext per dispatch.
Use it to isolate tenant/session scope and to propagate a cancellation token,
deadline, run identity, simulation state, current episode, source revision, and
document metadata filters without mutating agent-wide configuration. Agent
fills missing tenant/session and agent-name values from its entry context; it
does not overwrite application-supplied values.
package com.example.tnsai.docs;
import com.tnsai.actions.model.ActionRequest;
import com.tnsai.agents.Agent;
import com.tnsai.cancellation.CancellationToken;
import com.tnsai.rag.ChatRetrievalContext;
import java.time.Duration;
import java.util.Map;
public final class ActionRetrievalContextExample {
private ActionRetrievalContextExample() {}
public static void dispatch(Agent agent) {
CancellationToken cancellation = CancellationToken.create();
ChatRetrievalContext context = ChatRetrievalContext.builder()
.scope("tenant-a", "conversation-42")
.runId("run-7")
.simulationState("review")
.currentEpisode("episode-3")
.sourceRevision("kb-2026-08-12")
.cancellationToken(cancellation)
.timeout(Duration.ofSeconds(3))
.metadataFilters(Map.of("tier", "approved"))
.build();
agent.streamChatWithTools("Summarize the evidence", chunk -> {}, context);
agent.streamChatWithRetrievedContext(
"Fenced outbound request", "Original user message", chunk -> {}, context
);
agent.chatWithEvents("Summarize the evidence", event -> {}, context);
agent.executeAction(
"answer", Map.of("question", "What changed?"), context
);
agent.executeAction(
ActionRequest.of("answer", Map.of("question", "What changed?")),
context
);
agent.executeActionOnRole(
"researcher", "answer", Map.of("question", "What changed?"), context
);
agent.executeActionOnRole(
"researcher",
ActionRequest.of("answer", Map.of("question", "What changed?")),
context
);
}
}These are the seven public streaming/event/action overload families that take
ChatRetrievalContext. The session-publisher overload
chatWithEvents(String, String) creates its own default retrieval context and
does not accept one. Cancellation is cooperative, and a deadline is checked by
the retrieval pipeline; a blocking application retriever must also apply the
deadline to its own backend I/O to provide a wall-clock bound.
For an action with @Retrieval, the context reaches the unified retrieval
engine. The runtime adds canonical owner/run/agent/simulation/episode/source
and binding-revision metadata, while ordinary metadataFilters remain exact
document predicates. Successful retrieval still publishes the four reserved
dispatch keys: _rag_context, _rag_document_count,
_rag_stale_fallback, and _rag_context_truncated. LLMRoleExecutor consumes
positive, non-blank evidence by XML-escaping it inside <tnsai-memory> and
adding the untrusted-context disclosure to the system prompt. A custom action
executor may inspect the same keys but is responsible for equivalent safe
handling.
This per-dispatch context is distinct from ChatKnowledgeBinding: the binding
owns a chat knowledge source and its invalidation/close lifecycle, whereas
ChatRetrievalContext carries request-local isolation and cancellation through
one chat, stream, event, or action call.
AgentChatOrchestrator
com.tnsai.agents.chat.AgentChatOrchestrator handles LLM invocation, response processing, structured output, and RAG augmentation.
LLM Invocation
// Full control
Object response = chatOrchestrator.invokeLLM(message, useHistory, useTools, trace);
// Process response (handles text and tool calls)
String result = chatOrchestrator.processLLMResponse(response, useHistory);Structured Output (Guardrails Pattern)
// With custom parser
MyOutput output = agent.chatWithStructure(
"Extract the key points",
new MyOutputParser(),
3 // maxRetries for correction
);
// With format detection (JSON, YAML, TOML)
MyRecord record = agent.chatWithFormat("Generate config", MyRecord.class, 2);
// With explicit format
MyRecord record = agent.chatWithFormat("Generate config", MyRecord.class, OutputFormat.YAML, 2);The orchestrator detects output format from @OutputFormatSpec on the target class or agent class, defaulting to JSON.
RAG (Knowledge Base Augmentation)
setKnowledgeBase and setKnowledgeBaseTopK are removed in
0.16.0, on the orchestrator as well as on Agent. Through Maven
Central 0.15.1 they installed a
ChatKnowledgeBinding.snapshot(kb, topK) (source=knowledge-base) and
chat grounded through the same RetrievalEngine path as
@ChatKnowledge — there was never a second KnowledgeBase.search path.
Chat sources are now declared on the builder rather than pushed onto a
live agent: name a declared source with chatKnowledge(...), or supply
an application-owned corpus through liveChatKnowledge(...) and
ChatKnowledgeBinding.live(...) (@since 0.16.0). See
Declarative RAG.
AgentOrchestrator
com.tnsai.agents.orchestration.AgentOrchestrator is the top-level coordinator that wires together AgentToolExecutor, AgentChatOrchestrator, AgentStreamingSupport, and AgentEnsembleExecutor. It implements the context interfaces for all three sub-delegates so they can access agent internals without circular dependencies.
Key Public Methods
| Method | Description |
|---|---|
chat(String message, boolean useHistory, boolean useTools) | Main chat entry point with eval hooks and context graph tracing |
streamChat(String message) | Token-by-token streaming |
streamChatWithTools(String message, Consumer<ChatChunk> handler[, ChatRetrievalContext context]) | Streaming with tool calling loop and optional request-local retrieval context |
streamChatWithRetrievedContext(String outbound, String history, Consumer<ChatChunk> handler[, ChatRetrievalContext context]) | Stream pre-rendered evidence while preserving the original history message |
chatWithEvents(String message, Consumer<TnsAIEvent> consumer[, ChatRetrievalContext context]) | Event-based chat with optional request-local retrieval context |
chatWithEvents(String message, String sessionId) | Event-based chat with session publisher |
executeAction(String name, Map<String, Object> params[, ChatRetrievalContext context]) | Execute a named action |
executeAction(ActionRequest request[, ChatRetrievalContext context]) | Execute with typed request/response |
executeActionOnRole(String roleId, String name, Map<String, Object> params[, ChatRetrievalContext context]) | Execute on a specific role |
executeActionOnRole(String roleId, ActionRequest request[, ChatRetrievalContext context]) | Execute a typed request on a specific role |
setToolCallFilter(ToolCallFilter filter) | Set permission control for tool calls |
setToolCallListener(ToolCallListener listener) | Set progress callbacks |
shutdown() | End context conversations |
Context Graph Integration
When context graph is enabled, the orchestrator automatically:
- Starts/ends context conversations around chat sessions
- Creates snapshots at conversation boundaries
- Records
DecisionTraceentries for each chat (success or failure)
AgentCapabilities
com.tnsai.agents.capabilities.AgentCapabilities is the capability facade that bridges cognitive support, resilience, variants, and context graphs. Agent delegates to this class for all capability operations.
Variant Selection
agent.setVariant(AgentVariant.HIGH);
AgentVariant current = agent.getVariant();
// Auto-resolve based on task
AgentVariant resolved = agent.resolveVariant("Complex refactoring task");
// Custom selector
agent.setVariantSelector(mySelector);Context Graph
agent.enableContextGraph(snapshotStore, decisionTraceStore);
Optional<ContextManagerHandle> cm = agent.getContextManager();
boolean enabled = agent.isContextGraphEnabled();Resilience
AgentHealthState health = agent.getHealthState();
ResilienceExecutor executor = agent.getResilienceExecutor();
RetryPolicy policy = agent.getDefaultRetryPolicy();
DeadLetterQueue dlq = agent.getDeadLetterQueue();
agent.clearRecoveryState();