# 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`.

```java
// 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.

```java
// 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`.

```java
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).

```java
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.

```java
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 |

```java
// 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

```java
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

```java
// 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:

```java
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

```java
// 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):

```java
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)

```java
// 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.

<!-- java-contract: src/main/java/com/example/tnsai/docs/ActionRetrievalContextExample.java -->
```java
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

```java
// 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)

```java
// 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](../capabilities/rag/index.md).

## 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 `DecisionTrace` entries 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

```java
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

```java
agent.enableContextGraph(snapshotStore, decisionTraceStore);

Optional<ContextManagerHandle> cm = agent.getContextManager();
boolean enabled = agent.isContextGraphEnabled();
```

### Resilience

```java
AgentHealthState health = agent.getHealthState();
ResilienceExecutor executor = agent.getResilienceExecutor();
RetryPolicy policy = agent.getDefaultRetryPolicy();
DeadLetterQueue dlq = agent.getDeadLetterQueue();
agent.clearRecoveryState();
```

## Related Documentation

- [Streaming](behavior/streaming.md) -- streaming basics and ChatChunk
- [Tools](../capabilities/tools/registration.md) -- tool registration and the Tool interface
- [Roles](fundamentals/roles.md) -- role-based action discovery
- [Events](fundamentals/events.md) -- TnsAI event system
- [Resilience](reliability/resilience.md) -- retry, circuit breaker, recovery
- [Variants](behavior/variants.md) -- AgentVariant cost/quality tiers
