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:

StrategyEffective decisionUse for
deny-winsblock if any contributing detector blocks, else allowsecurity — never relax on disagreement
quorumblock only if ≥ count detectors agree (optionally above min_score)noisy categories (toxicity) — reduce false positives
weightedsum provider weights per decision; highest winsblending a trusted vendor with cheaper rules
first-availablefirst 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 findings are the union of every contributing detector's findings, each keeping its own detector attribution. Whitelisted findings are carried (marked), never dropped.
  • Spans union. The effective modifications.spans are the union of spans from all contributing verdicts. Overlapping spans on the same path merge to the covering range.
  • Unjudged union. The effective unjudged is the union of every detector's unjudged paths — a path is covered only when every guardrail routed to it answered.

Failure and latency

  • timeout_ms bounds each provider. A provider exceeding it is dropped per on_timeout (degrade = decide on the rest AND report the dropped provider's paths in unjudged; block = fail closed).
  • on_all_failed sets the decision when every provider errors or times out. Security categories should fail closed (block); low-severity safety may fail open (allow with the affected paths in unjudged). 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.