# Tri-Agent Harness Mode

> Status: opt-in feature flag on scheduled tasks (`harness_mode: "tri_agent"`).
> Shipped: W-mq07a9gf000jbc2b. Module: [`engine/harness.js`](../engine/harness.js).

## What it is

A way to turn one schedule firing into a coordinated **Planner → Generator → Evaluator** trio that iterates on a shared on-disk artifact until the artifact meets a rubric or hits an iteration cap. Useful for "produce a piece of work, then improve it" loops where a single agent call would either underspecify the task or produce uneven quality.

The three roles in order:

1. **Planner** (`ask` type, read-only) — reads the rubric, writes a short plan into the mission directory.
2. **Generator** (defaults to `ask`, inherits `sched.type`) — produces the artifact at `<MINIONS_DIR>/engine/harness/<missionId>/artifact.md` per the plan.
3. **Evaluator** (`ask`, read-only) — scores the artifact against the rubric and reports a verdict.

If the evaluator's verdict score is below `harness_threshold` (and the iteration cap hasn't been hit), the engine appends a fresh `Generator → Evaluator` pair carrying the evaluator's feedback in the next generator's prompt. Loop continues until pass or cap.

## Config schema (add to a schedule in `config.json`)

```json
{
  "id": "weekly-design-review",
  "title": "Tri-agent design review",
  "cron": "0 9 * * MON",
  "type": "ask",
  "harness_mode": "tri_agent",
  "harness_rubric": "Score 0-1. 1.0 = all sections complete with code examples. 0 = missing sections.",
  "harness_threshold": 0.7,
  "harness_max_iterations": 5
}
```

| Field                    | Required | Default | Notes                                                                 |
|--------------------------|----------|---------|-----------------------------------------------------------------------|
| `harness_mode`           | yes      | —       | Must equal `"tri_agent"` to enable. Any other value falls back to plain scheduled work. |
| `harness_rubric`         | yes      | —       | Non-empty string. Injected into every role's prompt. The evaluator scores against this. |
| `harness_threshold`      | no       | `0.7`   | Number in `(0, 1]`. Verdict score `>= threshold` = pass; `<` = iterate. |
| `harness_max_iterations` | no       | `5`     | Positive integer, capped at `20`. Counts generator iterations; planner is iteration 1. |

Invalid harness config logs a warning and **skips the firing without recording a schedule run**, so fixing the config and waiting for the next cron tick is enough to recover — no manual reset needed.

## Lifecycle

```
cron fires
  └─ scheduler.discoverScheduledWork detects harness_mode === 'tri_agent'
       └─ validateHarnessConfig (skip+warn on failure)
            └─ createTriAgentMission → 3 work items
                 ├─ Planner   (iteration 1)
                 ├─ Generator (iteration 1, depends on Planner)
                 └─ Evaluator (iteration 1, depends on Generator)
                      │
                      ▼ (on success)
                 lifecycle.runPostCompletionHooks
                  └─ handleHarnessIterationResult
                       └─ parseEvaluatorVerdict + shouldIterateAgain
                            └─ if iterate: append Generator + Evaluator (iteration N+1)
                                 └─ next tick dispatches them
                            └─ if pass / cap / inconclusive: mission terminal
```

## Artifact layout

```
<MINIONS_DIR>/engine/harness/<missionId>/
  └─ artifact.md            ← Generator writes here, Evaluator reads here
```

Mission ID format: `<scheduleId>-<unixMs>-<rand6>`. The mission directory is the contract — agents in all 3 roles get the same path injected into their prompts.

## Evaluator verdict protocol

The evaluator can signal pass/fail/score either way:

- **Preferred (structured):** include the fields in the completion report sidecar:
  ```json
  { "harness_pass": true, "harness_score": 0.82, "harness_feedback": "all sections present" }
  ```
- **Fallback (text):** include `Score: 0.82` and `PASS` / `FAIL` in the summary. Structured fields win when both present. `FAIL` takes precedence when both `PASS` and `FAIL` appear in the text.

If neither signal is parseable, the harness treats the verdict as inconclusive and stops iterating (`shouldIterateAgain` returns false) to avoid an infinite loop driven by a silent agent.

## Dedup behavior (engine.js)

Within a single tick the standard scheduled-work dedup is keyed by `_scheduleId`, which would collapse the harness trio to one item. The harness trio share a `_missionId`; engine.js snapshots active mission IDs **before** the dedup loop so all 3 land together, while plain scheduled items keep the original `_scheduleId` dedup.

## Operational notes

- Tri-agent items are **schedule-driven** — there's no manual "fire a harness mission" entry point. Add a schedule with `harness_mode: "tri_agent"` to opt in.
- Iteration pairs always reuse the original mission's artifact path, threshold, max-iterations, and rubric. The evaluator's verdict feedback is appended to the next generator's prompt.
- Mission state lives entirely on disk: the work-items.json trio + the artifact file. No new DB tables.
- Each iteration's evaluator is a separate work item, so dispatch retries, cooldowns, and steering apply normally to every role.
