Blog/
11 min read

A two-agent system, and the trade-off that shaped everything

Why one model wasn't enough, how classifier and verifier agents split the work, and the distillation and RLHF loops that kept cost and behavior in check.

Part 1 framed the problem: a policy that couldn't enforce itself, and underclassification as the dominant failure mode. Part 2 is the architecture — why one model wasn't enough, what replaced it, and how the system learned from real usage without leaving the document platform.

01 — ONE MODEL

Why not one model?

The first version was closer to what you'd expect: one model, given the policy document and the target document, producing a classification. It didn't work reliably enough to trust.

This was early 2025 — models still struggled with long-context reliability and were prone to missing a single sensitive detail buried in an otherwise unremarkable document. And in this domain, missing one detail wasn't a rounding error: one overlooked clause could be the entire reason a document needed the highest classification level. A single-pass model would confidently produce a classification and be wrong in exactly the cases that mattered most.

  • One read of policy + document
  • High confidence on unremarkable docs
  • Misses buried sensitive clauses
  • No independent check on reasoning
  • Classifier proposes with cited justification
  • Verifier independently reviews
  • Disagreement → stricter label wins
  • Both agents retrained on usage signals
02 — TWO AGENTS

The two-agent design

The system that replaced it split the work:

Document save (Confluence)

On save, document text is extracted and sent to the classification API. Target: near-real-time feedback inline with existing policy tagging.

Agent 1 — Classifier

Reads the document against the current policy. Outputs a proposed classification with confidence, and cites specific passages as justification.

Agent 2 — Verifier

Independently reviews the document and Agent 1's reasoning. Produces a final determination.

Consensus logic

Compare both outputs. Agreement → suggested label + grounded highlights. Disagreement → default to the more restrictive classification.

OutcomeSystem behavior
Both agents agreeSurface suggestion with cited justification
Agents disagreeEscalate to the stricter of the two labels
Low confidenceStill bias restrictive; creator decides via accept/reject
Suggested downgradeNever — escalation only (Part 3)
03 — DISTILLATION

Chasing cost and latency, not just accuracy

Running two LLM calls against thousands of documents a day, with a target of near-real-time feedback at the moment someone saves a document, isn't cheap or fast if every call goes to a large frontier model.

We experimented with a teacher-student setup: larger API-based models (OpenAI's GPT-family) generating high-quality classifications and reasoning, distilled down into smaller open-source models intended to run the bulk of production traffic.

This was explicitly an experiment, not a fully solved problem by the end of the six months — but the motivation was clear from day one: cost, at the volume of documents being processed, and latency, because the entire value proposition depended on users seeing a classification suggestion almost immediately after saving.

ConstraintTeacher (frontier API)Student (distilled OSS)
Accuracy / reasoningHigher quality labels + citationsGood enough for bulk traffic
Cost per documentProhibitive at full volumeTarget production path
LatencyAcceptable for training signalMust feel native on save
Status at project endGround truth generatorPromising, not fully solved
04 — RLHF

Teaching the system from real behavior, not just labels

The dataset powering retraining came from three signals, not one:

  1. Correctly labeled example documents, used as a baseline training set.
  2. Explicit user feedback — accept or reject on a suggested reclassification (rejection required selecting a reason, which became its own useful signal).
  3. Behavioral change independent of explicit feedback — if a user updated or edited a document to remove sensitive information after seeing a suggestion, even without clicking "accept," that counted as evidence the system had done its job.

That third signal is easy to overlook but was one of the more important design choices in the project: it meant success wasn't defined by whether people agreed with the system, but by whether the system changed behavior in the right direction. Both agents were periodically retrained as this dataset grew.

05 — CONFLUENCE

Built natively into Confluence

None of this worked as a separate tool people had to remember to use. Working with a Confluence developer, the system was wired into the native save flow:

  • On save, the document was sent to the classification API
  • The response — label suggestion plus inline justification — was surfaced directly in the Confluence UI, in line with the existing policy-tagging mechanism
  • Parallel document processing (via UV and Unicorn) kept this fast enough to feel native rather than bolted on

Part 3 covers two problems that turned out to be harder than the modeling itself.

06 — REFLECTION

What I learned

The architectural bet wasn't "better models" — it was structural skepticism. One agent confident and wrong is worse than two agents disagreeing and escalating. Distillation and RLHF were downstream of that: make the skeptical architecture affordable, and teach it from how people actually behave when they see a suggestion, not just from a static labeled corpus.