# Judge Agent Pattern

`JudgeCoordinator` wraps a coordination cycle with a `JudgePolicy`. After each attempt the policy returns a `JudgeVerdict` of `APPROVE`, `RETRY`, or `REJECT`. This is cycle-end quality control for one agent's output, not a tournament that ranks several candidates. Package: `com.tnsai.coordination.judge`.

## Quick Start

<!-- java-contract: src/main/java/com/example/tnsai/docs/JudgeQuickstartExample.java -->
```java
JudgePolicy policy = LLMJudgePolicy.builder()
    .llmClient(llmClient)
    .criteria("Evaluate for correctness, completeness, and clarity.")
    .build();

JudgeCoordinator<String> coordinator = JudgeCoordinator.<String>builder()
    .policy(policy)
    .maxRetries(3)
    .timeout(Duration.ofSeconds(30))
    .executor((agentId, feedback) -> produceOutput(agentId, feedback))
    .build();

JudgeCoordinator.Result<String> result = coordinator.execute("agent-1");
if (result.isApproved()) {
    System.out.println(result.output());
}
```

`LLMJudgePolicy` has no public constructor. The builder accepts only `llmClient` and `criteria`. Scores are always in `[0.0, 1.0]`.

## JudgePolicy

`JudgePolicy` is a functional interface. `JudgeCoordinator` calls it once per iteration:

| Parameter | Type | Role |
|-----------|------|------|
| `agentId` | `String` | Agent whose output is being judged |
| `output` | `Object` | Cycle output (type depends on the executor) |
| `context` | `JudgeContext` | Iteration number, history, and time budget |

The return type is `JudgeVerdict`, not a ranked list of candidates. There is no `CandidateOutput`, `JudgeResult`, or `ScoredCandidate` type. Built-in policies are constructed in code; they are not loaded from `META-INF/services`.

### ThresholdJudgePolicy

Numeric gate. The caller supplies the output class and a scoring function. The policy approves when the score is at least the threshold; otherwise it retries, or rejects on the final iteration.

<!-- java-contract: src/main/java/com/example/tnsai/docs/ThresholdJudgePolicyExample.java -->
```java
JudgePolicy policy = new ThresholdJudgePolicy<>(
    0.8,
    String.class,
    output -> score(output)
);

JudgeVerdict verdict = policy.evaluate(
    "agent-1",
    "draft answer",
    JudgeContext.first(3)
);
if (verdict.isApproved()) {
    System.out.println(verdict.score());
}
```

Best for: deterministic CI gates and tests that must not call an LLM.

### LLMJudgePolicy

Sends one output to an LLM with your natural-language criteria. The model must answer in a fixed `DECISION` / `SCORE` / `REASON` / `SUGGESTIONS` shape. The only builder methods are `llmClient(LLMClient)` and `criteria(String)`.

| Parameter | Default | Description |
|-----------|---------|-------------|
| `llmClient` | required | `LLMClient` used for evaluation |
| `criteria` | required | Natural-language quality criteria |

There is no `llm()`, `scoringScale()`, `requireReasoning()`, or `model()` setter.

## JudgeCoordinator

`JudgeCoordinator<T>` runs the executor, judges the output, and retries with the previous verdict's suggestions until the policy approves, rejects, or `maxRetries` is exhausted.

| Parameter | Default | Description |
|-----------|---------|-------------|
| `policy` | required | `JudgePolicy` used after each attempt |
| `executor` | required | `(agentId, feedback) -> T`. Feedback is empty on the first call |
| `maxRetries` | `3` | Total attempts, including the first |
| `timeout` | unset | Optional bound for the whole cycle |

`execute(String agentId)` is the only run method. There is no `evaluate(task, candidates)` and no `runAndJudge(...)`.

### Result

`JudgeCoordinator.Result<T>` is the value returned by `execute`:

| Accessor | Meaning |
|----------|---------|
| `output()` | Best output produced, or `null` if every attempt failed |
| `finalVerdict()` | Last `JudgeVerdict` |
| `verdictHistory()` | Verdict from every iteration |
| `iterations()` | Attempt count |
| `duration()` | Wall time for the cycle |
| `isApproved()` | Whether `finalVerdict()` is `APPROVE` |
| `score()` | `finalVerdict().score()` |

### JudgeVerdict

| Component | Meaning |
|-----------|---------|
| `decision()` | `APPROVE`, `REJECT`, or `RETRY` |
| `reason()` | Human-readable explanation |
| `score()` | Quality in `[0.0, 1.0]` |
| `suggestions()` | Feedback for the next retry (empty on approve) |
| `isApproved()` | `decision() == APPROVE` |

Factories: `JudgeVerdict.approve(reason, score)`, `retry(...)`, `reject(...)`.

### JudgeContext

| Component | Meaning |
|-----------|---------|
| `iteration()` | 1-based attempt number |
| `maxIterations()` | Same bound as `maxRetries` |
| `previousVerdicts()` | Earlier verdicts (empty on the first attempt) |
| `timeBudget()` | Optional remaining budget |
| `isFinalIteration()` | `iteration >= maxIterations` |

`JudgeContext.first(maxIterations)` builds the first attempt.

## Integration with Team

Use `JudgeCoordinator` around `Team.execute`. `Team.execute` returns `TeamResult`, not `String`. `Team.builder()` requires a `mission` and adds members with `member(...)`, not `addMember(...)`.

<!-- java-contract: src/main/java/com/example/tnsai/docs/JudgeTeamIntegrationExample.java -->
```java
Team team = Team.builder()
    .name("Draft team")
    .mission(task)
    .formation(TeamFormation.PARALLEL)
    .member(agent1, TeamRole.MEMBER)
    .member(agent2, TeamRole.MEMBER)
    .build();
team.start();

JudgeCoordinator<TeamResult> coordinator = JudgeCoordinator.<TeamResult>builder()
    .policy(LLMJudgePolicy.builder()
        .llmClient(llmClient)
        .criteria("Approve only if the aggregated team output is complete and accurate.")
        .build())
    .maxRetries(3)
    .executor((agentId, feedback) -> team.execute(task))
    .build();

JudgeCoordinator.Result<TeamResult> judged = coordinator.execute(team.getId());
TeamResult teamResult = judged.output();
```

For group shapes themselves, see [Topologies](topologies.md).
