Composition
You rarely want a single detector. You want your deterministic config rules and an LLM judge and maybe a third-party guard model — and one decision out the other side. Composition is how a runtime merges multiple Verdicts into the single effective verdict the integration point enforces. OGR standardizes the mechanism; the choices stay the deployer's.
Strategies
Set per risk category (or category prefix) in your policy. With two decisions the strategies compose decisions, redaction spans, and findings separately:
| Strategy | Effective decision | Use for |
|---|---|---|
deny-wins | block if any contributing detector blocks, else allow | security — never relax on disagreement |
quorum | block only if ≥ count detectors agree (optionally above min_score) | noisy categories (toxicity) — reduce false positives |
weighted | sum provider weights per decision; highest wins | blending a trusted vendor with cheaper rules |
first-available | first responder wins (others may be fallback) | latency-critical paths |
composition:
# security defaults conservative: any detector blocking blocks the action
"security.*":
providers: [vendorA, vendorB, ogr.poc.config_rules]
strategy: deny-wins
timeout_ms: 200
on_timeout: degrade # drop the slow provider, decide on the rest
on_all_failed: block # fail closed for security
# safety toxicity tuned to reduce false positives via a vote
"safety.toxicity":
providers: [vendorX, vendorY, vendorZ]
strategy: quorum
quorum: { count: 2, min_score: 0.8 }
on_all_failed: allow # fail open for low-severity safety
"security.malicious_command":
providers: [ogr.poc.config_rules, ogr.poc.llm_judge]
strategy: deny-wins
short_circuit: true # stop at first block; skip costlier providers
conflict_default: most_severe
short_circuit: true lets the runtime stop once a block is reached, so an
expensive model provider is skipped when a cheap rule already blocked.
Composing findings and modifications
- Findings union. The effective verdict's
findingsare the union of every contributing detector's findings, each keeping its owndetectorattribution. Whitelisted findings are carried (marked), never dropped. - Spans union. The effective
modifications.spansare the union of spans from all contributing verdicts. Overlapping spans on the samepathmerge to the covering range. - Unjudged union. The effective
unjudgedis the union of every detector's unjudged paths — a path is covered only when every guardrail routed to it answered.
Failure and latency
timeout_msbounds each provider. A provider exceeding it is dropped peron_timeout(degrade= decide on the rest AND report the dropped provider's paths inunjudged;block= fail closed).on_all_failedsets the decision when every provider errors or times out. Security categories should fail closed (block); low-severity safety may fail open (allowwith the affected paths inunjudged). The choice is the deployer's, and it is explicit.
Note this is the runtime ↔ detectors side. The complementary
integration ↔ runtime side — what an enforcement point does when it
cannot reach the runtime at all — is
fail_mode, configured
locally at the integration (default: open).
Why composition matters
Detectors have complementary blind spots. On the OGR benchmark, a config detector
(macro-F1 0.45) and an LLM judge (0.41) composed reach 0.625 — better than
either alone. OGR is a referee: detectors compete on the
leaderboard, and you
compose the ones that win on your categories. The provider field on every
verdict — and detector on every finding — is what makes that attribution,
and per-vendor metering, possible.
Next: Policy — the file where composition and rules live.