# Advanced Patterns

Advanced coordination patterns in TnsAI.Coordination for production multi-agent systems.

## ScatterGather

`ScatterGather` is a utility for fan-out/fan-in communication: send the same task to multiple agents at once and collect their responses. This is useful when you want multiple perspectives on the same question, or when you need redundancy for reliability. Supports several collection strategies (all, first-wins, quorum, consensus, score-based, aggregate).

### Scatter (Collect All)

Send the task to all agents and wait for every response. You get back all results along with metrics like success rate and which agent responded fastest.

```java
ScatterGather.ScatterResults results = ScatterGather.scatter(
    "Analyze market trends for Q4",
    List.of(analyst1, analyst2, analyst3),
    30, TimeUnit.SECONDS
);

List<String> responses = results.responseTexts();       // successful response texts
double successRate = results.successRate();              // 0.0 - 1.0
Optional<AgentResponse> fastest = results.fastest();     // quickest responder
```

### Race (First Wins)

When speed matters more than comprehensiveness, `race()` returns the first successful response and cancels the rest.

```java
Optional<AgentResponse> winner = ScatterGather.race(
    "What is the capital of France?",
    List.of(agent1, agent2, agent3),
    10, TimeUnit.SECONDS
);
```

### Quorum (Wait for N)

When you need a minimum number of confirmations before proceeding (like a distributed consensus check), `quorum()` waits until at least N agents respond successfully, or timeout.

```java
ScatterResults results = ScatterGather.quorum(
    "Verify transaction hash",
    validators,
    3,                   // need 3 confirmations
    30, TimeUnit.SECONDS
);
```

### Consensus (Majority Vote)

When you want to find the "agreed-upon" answer from multiple agents, `gatherWithConsensus()` collects all responses and picks the most common answer using word-level Jaccard similarity.

```java
Optional<AgentResponse> consensus = ScatterGather.gatherWithConsensus(
    "What is 2+2?", agents, 30, TimeUnit.SECONDS
);
```

### Score-Based Selection

When you have a specific quality metric in mind, `gatherWithScore()` lets you provide a scoring function to rank responses and pick the best one.

```java
Optional<AgentResponse> best = ScatterGather.gatherWithScore(
    "Write a haiku about Java",
    agents,
    response -> (double) response.length(),  // score by length
    30, TimeUnit.SECONDS
);
```

### Aggregate (Synthesize)

When you want a single combined answer instead of picking one response, `gatherAndAggregate()` collects all responses and passes them to a dedicated aggregator agent that synthesizes them into a unified answer.

```java
Optional<String> synthesized = ScatterGather.gatherAndAggregate(
    "What are the risks of this deployment?",
    List.of(securityAgent, infraAgent, qaAgent),
    summarizerAgent,
    60, TimeUnit.SECONDS
);
```

## Auction

The `Auction` class implements multi-agent auction mechanisms for competitive task allocation. Agents bid on tasks, and the auction determines the winner based on the auction type. Useful for resource allocation, task assignment, and market-based coordination.

### Auction Types

| Type | Behavior |
|---|---|
| `ENGLISH` | Ascending price. Bidders raise until no one outbids. Multiple rounds. |
| `DUTCH` | Descending price. Price drops each round; first bidder to accept wins. |
| `SEALED_BID` | Single round. All bids submitted simultaneously. Highest bidder wins at their bid price. |
| `VICKREY` | Single round. Highest bidder wins but pays the second-highest price (incentivizes truthful bidding). |

### Usage

```java
AuctionResult result = Auction.builder()
    .type(Auction.Type.VICKREY)
    .task("Translate document to French")
    .bidders(List.of(agent1, agent2, agent3))
    .bidStrategy((agent, task, currentPrice, round, previousBids) -> {
        String response = agent.chat("Bid on: " + task + " (current: " + currentPrice + ")");
        return Double.parseDouble(response.trim()); // or Double.NaN to abstain
    })
    .startingPrice(100.0)
    .reservePrice(10.0)
    .increment(10.0)       // price step for English/Dutch auctions
    .maxRounds(10)
    .build()
    .execute();

if (result.success()) {
    System.out.println("Winner: " + result.winner().name());
    System.out.println("Winning bid: " + result.winningBid().value());
    System.out.println("Pay price: " + result.payPrice());    // differs from bid in Vickrey
    System.out.println("Rounds: " + result.rounds());
    System.out.println("Duration: " + result.durationMs() + "ms");
} else {
    System.out.println("No winner: " + result.reason());
}

// Async execution
CompletableFuture<AuctionResult> future = auction.executeAsync();
```

### BidStrategy

`BidStrategy` is a `@FunctionalInterface` that determines how an agent decides its bid value. Return `Double.NaN` to abstain from bidding.

```java
double calculateBid(Agent agent, String taskDescription, double currentPrice,
                    int round, List<Bid> previousBids);
```

### Bid Record

Each bid is a record with `agent()`, `value()`, `round()`, and `timestamp()`. Create with `Bid.of(agent, value, round)`.

### AuctionResult Record

Fields: `success()`, `winner()`, `winningBid()`, `payPrice()`, `allBids()`, `rounds()`, `durationMs()`, `reason()`. Factory methods: `AuctionResult.won(...)` and `AuctionResult.noWinner(...)`.

## StreamingChannel

A `StreamingChannel` lets agents send a continuous flow of data to each other in real time, rather than waiting for complete responses. This is useful for long-running tasks where you want to process results incrementally. It is a typed streaming channel with configurable backpressure that supports reactive-style `map`, `filter`, and `subscribe` operations.

```java
StreamingChannel<String> channel = StreamingChannel.<String>builder()
    .id("analysis-stream")
    .bufferSize(100)
    .backpressure(BackpressureStrategy.DROP_OLDEST)
    .offerTimeout(Duration.ofSeconds(5))
    .build();

// Consumer side
channel.subscribe(chunk -> processChunk(chunk));
channel.onError(err -> log.error("Stream error", err));
channel.onComplete(() -> log.info("Stream finished"));

// Producer side
channel.emit("data chunk 1");
channel.emit("data chunk 2");
channel.emitAll(List.of("chunk 3", "chunk 4", "chunk 5"));
channel.complete();
```

### BackpressureStrategy

When a producer emits data faster than the consumer can handle, the backpressure strategy determines what happens. Choose based on whether you prefer to wait, lose old data, lose new data, or fail loudly.

| Strategy | Behavior |
|---|---|
| `BLOCK` | Block producer until buffer has space (with configurable timeout) |
| `DROP_OLDEST` | Remove oldest item from buffer to make room |
| `DROP_NEWEST` | Reject incoming item silently |
| `ERROR` | Throw `BufferOverflowException` when full |

### Transformations

You can chain `filter()` and `map()` operations on a channel to create derived channels, similar to Java Streams or reactive pipelines.

```java
StreamingChannel<Integer> lengths = channel
    .filter(s -> !s.isBlank())
    .map(String::length);
```

### Metrics

Each channel tracks how many items were emitted, dropped, and buffered, so you can monitor throughput and detect backpressure issues.

```java
StreamingChannel.ChannelMetrics metrics = channel.metrics();
// channelId, emitted, dropped, bufferUsed, bufferCapacity, subscriberCount, completed
double dropRate = metrics.dropRate(); // percentage of dropped items
```

The channel implements `Closeable`. Call `close()` to shut down the dispatcher thread and clear the buffer.

## HandoffChain

A `HandoffChain` passes a task through a sequence of agents, where each agent's output becomes the next agent's input. This is similar to a pipeline but uses the handoff mechanism, which allows agents to pass structured context (metadata, priority, previous output) along the chain. Supports conditional steps that can be skipped based on the context.

```java
HandoffChain chain = HandoffChain.builder()
    .add(triageAgent)
    .add(researchAgent)
    .add(qualityAgent, ctx -> ctx.has("previousOutput"))  // conditional
    .add(responseAgent)
    .build();

HandoffContext initialContext = new HandoffContext(
    "task-123",
    Map.of("query", "How to reset password?"),
    Map.of("priority", "high"),
    null
);

List<HandoffResult> results = chain.execute(handoff, initialContext);
```

The first agent in the chain is the source. Conditional steps whose predicate returns `false` are skipped. Execution stops on the first failure.

## LazyAgentPool

When you have many concurrent tasks but don't want to create a new agent for each one, `LazyAgentPool` manages a reusable pool of agents. Agents are created on demand, returned to the pool after use, and automatically destroyed when idle too long. This is an object pool with lazy creation, idle eviction, and configurable activation policies.

```java
LazyAgentPool pool = LazyAgentPool.builder()
    .factory(() -> new WorkerAgent(llmClient))
    .maxSize(20)
    .minIdle(2)
    .idleTimeout(5, TimeUnit.MINUTES)
    .policy(ActivationPolicy.PREWARMED)
    .listener(new PoolListener() { ... })
    .build();

// Checkout/checkin via try-with-resources
try (AgentHandle handle = pool.checkout()) {
    String result = handle.execute("Process this document");
} // agent is returned to pool automatically
```

### ActivationPolicy

The activation policy controls when agents are created. Pre-warming creates agents upfront so they are ready immediately, while on-demand waits until they are needed.

| Policy | Behavior |
|---|---|
| `ON_DEMAND` | Agents created only when checked out |
| `PREWARMED` | Pre-creates `minIdle` agents at pool construction |
| `BURST` | Scale up fast on demand, scale down when idle |

### PredictiveActivator

The `PredictiveActivator` monitors pool checkout patterns in a sliding window and pre-warms agents when burst demand is detected. It implements `PoolListener` and auto-registers with the pool.

```java
PredictiveActivator activator = PredictiveActivator.builder()
    .pool(pool)
    .windowSize(60)          // 60-second sliding window
    .burstThreshold(5)       // 5+ checkouts/window triggers pre-warm
    .preWarmCount(3)         // pre-warm 3 agents
    .analyzeInterval(10)     // analyze every 10 seconds
    .build();

activator.start();   // begins monitoring in background
// ... use pool normally ...
activator.stop();    // stops monitoring

// Inspect statistics
int recent = activator.recentActivationCount();
int exhaustions = activator.recentExhaustionCount();
```

### PoolMetrics

Monitor pool utilization to right-size your pool configuration and detect resource leaks.

```java
PoolMetrics metrics = pool.getMetrics();
// active, idle, totalCreated, totalDestroyed, peakActive
```

The pool schedules periodic eviction to destroy idle agents exceeding `minIdle`. Call `preWarm(count)` to proactively create agents before a traffic spike. The pool implements `Closeable` and destroys all agents on `close()`.

## SharedBlackboard

`com.tnsai.communication.SharedBlackboard` is the shared working-memory
board. Agents post typed entries, query them, reply, and vote.
There is **no** TTL or auto-eviction on this type.

The unused coordination `Blackboard`, `KnowledgeEntry`, and
`KnowledgeSource` types are **removed** (TAN-5758 / `TnsAI@ba6bd8f7`).
The only public `KnowledgeSource` is the RAG annotation
`com.tnsai.annotations.KnowledgeSource`.

```java
SharedBlackboard board = new SharedBlackboard();

SharedBlackboard.Entry finding = board.post(
    "analyzer",
    SharedBlackboard.EntryType.ISSUE_FOUND,
    "Memory leak in UserService",
    Map.of("file", "UserService.java", "line", 42));

board.addTypeListener(SharedBlackboard.EntryType.DESIGN_PROPOSAL, entry ->
    System.out.println(entry.getTitle()));

List<SharedBlackboard.Entry> fromAnalyzer = board.getByAuthor("analyzer");
board.vote(finding.getId(), "reviewer", SharedBlackboard.VoteType.APPROVE);
boolean consensus = board.hasConsensus(finding.getId(), 1, 0.66);
```

## SupervisorAgent with Guardrails

A `SupervisorAgent` adds a safety layer around a group of agents. Before any agent executes an action, the supervisor checks it against a list of guardrails (policy rules). This lets you enforce constraints like "require human approval for deployments" or "block actions that exceed a budget" without modifying individual agents.

This is `com.tnsai.coordination.supervisor.Guardrail` — action *permission*.
It is not `@InputGuardrail` and not the quality tracing `Guardrail` SPI.
See [Guardrails](../security/guardrails.md).

```java
SupervisorAgent supervisor = new SupervisorAgent(group, List.of(
    new HITLApproval(Set.of("deploy", "delete"), approvalCallback),
    myBudgetGuardrail,
    myContentFilterGuardrail
));

Guardrail.GuardrailResult result = supervisor.checkAction(
    "agent-1", "deploy", Map.of("target", "production")
);

if (result.allowed()) {
    // proceed
} else {
    System.out.println("Blocked: " + result.reason());
    // result.severity() -> BLOCK or WARNING
}

// Dynamic guardrail management
supervisor.addGuardrail(new RateLimitGuardrail());
supervisor.removeGuardrail("old-guardrail-name");
```

### Guardrail Interface

Each guardrail implements a simple interface: given an agent ID, action name, and context, it returns a result saying whether the action is allowed, blocked, or a warning.

```java
public interface Guardrail {
    GuardrailResult check(String agentId, String action, Map<String, Object> context);
    String getName();
}
```

Guardrails are evaluated in order. If any guardrail blocks, evaluation stops immediately. `WARNING` severity allows execution but logs a warning.

### Built-in: HITLApproval

The Human-in-the-Loop (HITL) guardrail pauses execution and requests human approval for sensitive actions. Takes a set of action names that require approval and a callback for requesting it.

### Built-in: SandboxedExecution

The `SandboxedExecution` guardrail runs agent actions in an isolated context for safety testing, ensuring they cannot affect production state.

## TrustRegistry

The `TrustRegistry` tracks how reliably each agent performs each capability. As agents complete (or fail) tasks, their trust scores update automatically. You can then use these scores to route tasks to the most trusted agent or filter out unreliable ones.

```java
TrustRegistry registry = TrustRegistry.create(
    TrustUpdateStrategy.ema(0.3),  // Exponential Moving Average with alpha=0.3
    0.5                             // initial trust score for new agents
);

// Or with decay
TrustRegistry registry = TrustRegistry.create(
    TrustUpdateStrategy.ema(0.3),
    0.5,    // initial score
    0.02    // decay rate per cycle
);

// Record outcomes
registry.recordOutcome("agent-a", "translation", Outcome.SUCCESS, 0.95);
registry.recordOutcome("agent-a", "translation", Outcome.PARTIAL, 0.60);
registry.recordOutcome("agent-b", "translation", Outcome.FAILURE, 0.0);

// Query trust
double trust = registry.getTrust("agent-a", "translation"); // 0.0 - 1.0

// Full trust score object
Optional<TrustScore> score = registry.getTrustScore("agent-a", "translation");
// score.get().getTotalInteractions(), getSuccessfulInteractions(), getSuccessRate(), getLastUpdated()

// Rank agents for a capability
List<String> best = registry.rankAgents("translation", 3);

// Get agents above threshold
List<String> qualified = registry.getAgentsAboveThreshold("translation", 0.7);

// Periodic decay (call e.g. daily) -- decays scores toward 0.5 (neutral)
registry.applyDecay();
```

### TrustBasedRouter

The `TrustBasedRouter` uses the registry to route tasks to the most trusted agent for a given capability. Agents below the minimum trust threshold are excluded.

```java
TrustBasedRouter router = TrustBasedRouter.builder()
    .registry(trustRegistry)
    .minimumTrust(0.6)
    .build();

Optional<String> best = router.selectAgent("translation", List.of("agent-a", "agent-b", "agent-c"));
List<String> ranked = router.rankCandidates("translation", candidateIds);
```

### TrustUpdateStrategy

The update strategy determines how new outcomes affect an agent's trust score. EMA (exponential moving average) gives more weight to recent performance, while simple average treats all outcomes equally. You can also provide custom implementations.

## RecursiveTaskSolver (ROMA)

The `RecursiveTaskSolver` handles complex tasks that are too big for a single agent. It breaks a task into smaller sub-tasks, solves each one, combines the results, and verifies quality. If the sub-tasks are still too big, it recurses until reaching atomic (simple enough to solve directly) tasks. This implements the ROMA recursive task-solving pattern.

```java
RecursiveTaskSolver solver = RecursiveTaskSolver.builder()
    .solver(solverAgent)
    .decomposer(TaskDecomposer.llmBased(plannerAgent))
    .atomizer(Atomizer.depthBased(3))
    .verifier(Verifier.llmBased(reviewerAgent))
    .maxDepth(5)
    .maxRetries(2)
    .build();

RecursiveTaskSolver.SolveResult result = solver.solve(
    "Build a REST API for user management"
);

System.out.println("Success: " + result.success());
System.out.println("Output: " + result.output());
System.out.println("Traces: " + result.traces().size());
System.out.println("Duration: " + result.durationMs() + "ms");
```

### Algorithm

Here is how the recursive solving process works step by step:

1. Check if task is atomic (via `Atomizer`).
2. If atomic: solve directly with solver agent, then verify.
3. If not: decompose into sub-tasks (via `TaskDecomposer`).
4. Recursively solve each sub-task (fail-fast on any sub-task failure).
5. Aggregate sub-results using the solver agent.
6. Verify aggregated result. Retry up to `maxRetries` on rejection.

### Components

The solver is composed of three pluggable components. You can use the built-in implementations or provide your own.

| Component | Interface | Built-in |
|---|---|---|
| `Atomizer` | `isAtomic(task, depth)` -- decides if a task is solvable directly | `Atomizer.depthBased(maxDepth)`, `Atomizer.llmBased(agent)` |
| `TaskDecomposer` | `decompose(task, depth)` -- breaks task into sub-tasks | `TaskDecomposer.llmBased(agent)` |
| `Verifier` | `verify(task, result, attempt)` -- accepts or rejects results | `Verifier.llmBased(agent)`, `Verifier.alwaysAccept()` |

### SolveTrace

For debugging and auditability, each step in the recursive solving process produces a trace entry. Each step produces a trace entry with type `DECOMPOSED`, `SOLVED`, `AGGREGATED`, or `VERIFIED`, the task, detail, depth, and attempt number.

## CouncilExecutor

The `CouncilExecutor` orchestrates a multi-model council deliberation inspired by Karpathy's llm-council pattern. It runs a 3-stage pipeline where multiple LLM models independently answer a question, anonymously peer-review each other's responses, and then a chairman synthesizes the best final answer.

### Three-Stage Pipeline

1. **Stage 1 -- Individual Responses**: All members answer the question independently in parallel (using virtual threads).
2. **Stage 2 -- Peer Review**: Each member evaluates all responses (anonymized as "Model A", "Model B", etc.) and ranks them.
3. **Stage 3 -- Chairman Synthesis**: The chairman receives all responses, reviews, and aggregate rankings, then produces the final synthesized answer.

```java
CouncilExecutor council = CouncilExecutor.builder()
    .members(List.of(gpt4, claude, gemini, grok))    // at least 2 required
    .chairman(gemini)
    .anonymize(true)        // anonymize identities during peer review (default)
    .timeout(Duration.ofMinutes(5))
    .build();

CouncilResult result = council.deliberate("How should we architect auth?");

// Final synthesized answer
System.out.println(result.finalAnswer());

// Aggregate ranking of members
result.topRanked().ifPresent(r ->
    System.out.println("Top: " + r.llmName() + " (avg rank: " + r.averageRank() + ")"));

// Access individual stages
List<CouncilResponse> responses = result.stage1Responses();   // individual answers
List<CouncilReview> reviews = result.stage2Reviews();          // peer reviews
CouncilSynthesis synthesis = result.stage3Synthesis();          // chairman output
List<RankedMember> ranking = result.aggregateRanking();        // aggregate ranking
```

### CouncilResult

Fields: `stage1Responses()`, `stage2Reviews()`, `stage3Synthesis()`, `aggregateRanking()`, `memberCount()`, `duration()`. The `finalAnswer()` convenience method returns the chairman's synthesis text. The `topRanked()` method returns the member with the best (lowest) average rank across all reviewers.

> **Cross-reference**: For simpler multi-model decisions, see [Council Voting](council-voting.md).

## ChoreographyEngine

The `ChoreographyEngine` implements event-driven coordination without a central orchestrator. Instead of one agent telling others what to do, you define a set of event-reaction rules ("when event X happens, agent Y does Z and emits event W"), and the engine runs them automatically. This is ideal for microservice-style workflows where each agent handles its own piece of the process.

```java
ChoreographySpec spec = ChoreographySpec.builder()
    .name("order-processing")
    .initialEvent("order.received")
    .interaction("validate", "validator", "order.received",
        "Validate the order", "order.validated")
    .interaction("bill", "billing", "order.validated",
        "Process payment", "payment.processed")
    .interaction("fulfill", "warehouse", "payment.processed",
        "Ship the order", "order.shipped")
    .build();

ChoreographyEngine engine = ChoreographyEngine.builder()
    .spec(spec)
    .agent("validator", validatorAgent)
    .agent("billing", billingAgent)
    .agent("warehouse", warehouseAgent)
    .timeout(Duration.ofMinutes(2))
    .build();

ChoreographyEngine.ChoreographyResult result = engine.execute();
System.out.println("Success: " + result.success());
System.out.println("Completed: " + result.completedCount());
System.out.println("Failed: " + result.failedCount());
System.out.println("Duration: " + result.totalDurationMs() + "ms");

// Access individual step results
for (ChoreographyEngine.StepResult step : result.stepResults()) {
    System.out.println(step.interactionId() + ": " + (step.success() ? "OK" : step.error()));
}
```

### Safety Features

The engine includes built-in safety checks to catch common pitfalls in event-driven systems before they cause problems at runtime.

#### DeadlockDetector

Analyzes the choreography spec for circular event dependencies before execution using DFS-based cycle detection on the agent interaction graph.

```java
DeadlockDetector.DetectionResult result = DeadlockDetector.detect(spec);
if (result.hasDeadlock()) {
    for (DeadlockDetector.Cycle cycle : result.cycles()) {
        System.out.println("Cycle: " + cycle);           // "A -> B -> C -> A"
        System.out.println("Involves billing? " + cycle.involves("billing"));
    }
}

// Can also detect from runtime wait-for graphs
DeadlockDetector.DetectionResult result = DeadlockDetector.detectFromWaitGraph(waitForMap);
```

#### LivelockDetector

Detects livelock conditions where agents keep changing state without making real progress. Monitors three patterns: repeated messages, oscillating states, and progress stalls. Includes a circuit breaker that trips when violations exceed a threshold.

```java
LivelockDetector detector = LivelockDetector.builder()
    .repeatThreshold(5)                        // 5 identical messages triggers violation
    .repeatWindow(Duration.ofSeconds(10))
    .oscillationThreshold(4)                   // 4 alternating state changes
    .stallWindow(Duration.ofSeconds(30))        // no new unique action in 30s
    .circuitBreakerThreshold(3)                // trip after 3 total violations
    .build();

// Feed events
detector.recordMessage("agent-1", "retry-task-X");
detector.recordStateChange("agent-2", "IDLE");
detector.recordAction("agent-1", "process-order-123");

// Analyze
LivelockDetector.DetectionResult result = detector.analyze();
if (result.hasLivelock()) {
    for (LivelockDetector.Violation v : result.violations()) {
        System.out.println(v.type() + " on " + v.agentId() + ": " + v.detail());
    }
}

// Circuit breaker
if (detector.isCircuitBroken()) {
    detector.reset();   // clears all history and resets circuit breaker
}

// Listeners
detector.addListener(r -> log.warn("Livelock detected: {}", r.violations()));
```

**Violation types**: `REPEATED_MESSAGE`, `OSCILLATING_STATE`, `PROGRESS_STALL`.

### Async Execution

Like other executors, the choreography engine supports non-blocking execution via `CompletableFuture`.

```java
CompletableFuture<ChoreographyResult> future = engine.executeAsync();
```

## Production Hardening

### EpisodicMemory

Cross-agent episodic memory for recording and querying interaction episodes. Enables agents to learn from past coordination patterns. Uses a bounded deque that evicts the oldest episodes when capacity is reached.

```java
EpisodicMemory memory = new EpisodicMemory(1000);   // max 1000 episodes

// Record episodes
memory.record(EpisodicMemory.Episode.of(
    "task-1", "agent-a", "translate", "success", Map.of("lang", "fr")));

// Query by task type keyword (case-insensitive contains match)
List<Episode> similar = memory.querySimilar("translate", 5);

// Query by agent
List<Episode> agentHistory = memory.queryByAgent("agent-a", 10);

// Query by outcome
List<Episode> failures = memory.queryByOutcome("failure", 10);

// Inspect
int total = memory.size();
List<Episode> all = memory.all();
memory.clear();
```

The `Episode` record contains: `taskId()`, `agentId()`, `taskType()`, `outcome()`, `metadata()` (Map), and `timestamp()`.

### FailureIsolator

Cascading failure protection using per-agent circuit breakers. When an agent fails repeatedly, the circuit opens and subsequent requests are handled via a configurable degradation strategy instead of waiting for the failing agent.

```java
FailureIsolator isolator = FailureIsolator.builder()
    .failureThreshold(3)
    .recoveryTimeout(Duration.ofSeconds(30))
    .degradationStrategy(FailureIsolator.DegradationStrategy.FALLBACK)
    .build();

String result = isolator.execute(
    "agent-a",
    () -> agent.chat("Hello"),      // primary action
    () -> "Fallback response"        // fallback supplier
);

// Inspect circuit state
FailureIsolator.CircuitState state = isolator.state("agent-a");   // CLOSED, OPEN, HALF_OPEN
int failures = isolator.failureCount("agent-a");
isolator.reset("agent-a");   // manually reset
```

**DegradationStrategy options**:

| Strategy | Behavior |
|---|---|
| `SKIP` | Return null, skip the agent |
| `FALLBACK` | Use the provided fallback supplier |
| `CACHED` | Return the last successful result |
| `FAIL_FAST` | Throw `CircuitOpenException` immediately |

**Circuit states**: `CLOSED` (normal) -> `OPEN` (after N failures, fast-fail) -> `HALF_OPEN` (after recovery timeout, probe one request).

### TokenBudget

Per-agent token budget management to prevent runaway costs in multi-agent orchestration. Tracks both individual agent budgets and a global budget, with automatic warnings at 90% usage.

```java
TokenBudget budget = new TokenBudget(100_000);   // global budget
budget.allocate("agent-a", 30_000);
budget.allocate("agent-b", 70_000);

// Record usage
budget.consume("agent-a", 5_000);

// Check before consuming
if (budget.canConsume("agent-a", 10_000)) {
    budget.consume("agent-a", 10_000);
}

// Query
long remaining = budget.remaining("agent-a");
long globalRemaining = budget.globalRemaining();
double usage = budget.usagePercent("agent-a");       // 0-100
double globalUsage = budget.globalUsagePercent();     // 0-100
Map<String, Long> allocations = budget.allAllocations();
```

## Simulation Framework

The simulation framework lets you test multi-agent behaviors in a controlled environment before deploying them. You define a world state and rules, then run agents through turns while a validator enforces the rules. TnsAI.Coordination includes this framework for modeling multi-agent interactions in controlled environments. See the `examples/simulation/` directory in the Coordination repository.

### SimulationEnvironment

The environment defines the initial world state (who the actors are, what resources exist, what relationships are in place) and the simulation constraints like maximum turns.

```java
SimulationEnvironment env = SimulationEnvironment.builder()
    .name("diplomacy-sim")
    .initialState(Map.of(
        "countries", List.of("A", "B", "C"),
        "resources", Map.of("A", 100, "B", 80, "C", 120),
        "alliances", List.of()
    ))
    .maxTurns(50)
    .build();
```

### SimulationLoop

The `SimulationLoop` is the main driver that runs the simulation turn by turn. Each turn, it asks agents for their actions, validates them, and applies them to the environment state.

```java
SimulationLoop loop = SimulationLoop.builder()
    .environment(env)
    .agents(List.of(agentA, agentB, agentC))
    .validator(new SecretaryValidator())
    .build();

SimulationResult result = loop.run();
result.getTurnCount();       // Number of turns executed
result.getFinalState();      // Final environment state
result.getHistory();         // Turn-by-turn action history
```

### SecretaryValidator

The `SecretaryValidator` acts as a referee, checking every agent action against the simulation rules before it is applied. It prevents illegal moves, enforces turn order, and checks resource constraints.

```java
public class SecretaryValidator implements ActionValidator {
    @Override
    public ValidationResult validate(AgentAction action, Map<String, Object> state) {
        if (action.getType().equals("trade") && !hasResources(action, state)) {
            return ValidationResult.reject("Insufficient resources for trade");
        }
        return ValidationResult.accept();
    }
}
```

### DiplomacySimulation Example

To see the simulation framework in action, the `examples/simulation/` directory includes a complete diplomacy simulation with country agents that negotiate treaties, trade resources, and form alliances. The `SecretaryValidator` enforces diplomacy rules while the `SimulationLoop` manages negotiation rounds.

```java
DiplomacySimulation sim = new DiplomacySimulation();
sim.addCountry("Alphaland", agentAlpha);
sim.addCountry("Betaland", agentBeta);
sim.addCountry("Gammaland", agentGamma);

SimulationResult result = sim.run(30); // 30 rounds
System.out.println("Final alliances: " + result.getFinalState().get("alliances"));
```

## DebateExecutor

The `DebateExecutor` improves answer quality by having two LLM agents argue opposing positions on a question, then having a judge evaluate both sides and deliver a verdict. This adversarial approach forces each side to identify weaknesses in the other's reasoning, leading to more thorough analysis than a single LLM would produce.

```java
DebateExecutor debate = DebateExecutor.builder()
    .proponent(proponentLLM)
    .opponent(opponentLLM)
    .judge(judgeLLM)
    .maxRounds(3)
    .format(DebateFormat.STRUCTURED)
    .timeout(Duration.ofMinutes(10))
    .build();

DebateResult result = debate.execute("Should we use microservices for this project?");

System.out.println("Verdict: " + result.verdict());
System.out.println("Confidence: " + result.confidence());  // 0.0 - 1.0
System.out.println("Rounds: " + result.roundCount());
System.out.println("Pro summary: " + result.proSummary());
System.out.println("Con summary: " + result.conSummary());

// Access individual rounds
for (DebateRound round : result.rounds()) {
    System.out.println("Round " + round.roundNumber());
    System.out.println("  Pro: " + round.proponentArgument());
    System.out.println("  Con: " + round.opponentArgument());
}
```

### DebateFormat

The debate format controls the structure of the argumentation.

| Format | Description |
|---|---|
| `STRUCTURED` | Formal pro/con structure with sections |
| `FREE_FORM` | Open-ended discussion |

The judge produces a verdict with structured sections (VERDICT, REASONING, PRO SUMMARY, CON SUMMARY, CONFIDENCE). Confidence is extracted as a percentage from the judge's output.
