Skip to main content

pantheon-guardrails

Three guardrails for LLM agents, extracted from a private production system:

  • ConstitutionScorer — scores a draft reply against a weighted rubric, but only when the draft looks like it's worth paying for.
  • SafetyLayer — redacts emails, phone numbers and card-like digit runs, while allowing a business to publish its own contact details.
  • CrisisProtocol — detects a user in distress and breaks the assistant's persona.

Apache-2.0. Python 3.11+. One runtime dependency (pydantic, and only for the rubric model).

pip install pantheon-guardrails

What is actually interesting here

Most of this is unremarkable. Two ideas are worth the read.

1. Conditional judging

Using an LLM to grade another LLM's output is a well-known technique and a well-known cost — you double your inference bill to check work that is usually fine.

So the judge doesn't always run. A cheap regex pre-pass decides whether a draft is worth checking, and it fires on the things a customer acts on: money, quoted times and dates, percentages, bookings, refunds, deletions, anything medical or legal.

scorer = ConstitutionScorer(rubric, judge=my_judge)

await scorer.score_if_needed("The garden is lovely this time of year.", manifest)
# -> None. No LLM call. Nothing here can hurt anybody.

await scorer.score_if_needed("I've booked you in for 3pm Tuesday, that's £40.", manifest)
# -> Score(total=0.87, guidance=None). A time, a date and a price: worth checking.

The heuristic is a first cut and is stated as such in the source. It has not been calibrated against a labelled set in this repo. Override _should_judge or pass always_judge=True if your risk profile differs.

2. The judge must be a different model

scorer = ConstitutionScorer(rubric, judge=judge)   # required, not optional

A generator asked to mark its own work shares its own blind spots — the same training, the same failure modes, the same confident wrongness. The judge is a required argument specifically so that "which model grades this?" is a decision you make rather than a default you inherit.

The Judge protocol is two methods wide, so adapting whatever client you already use takes about five lines:

class AnthropicJudge:
    def __init__(self, client, model="claude-haiku-4-5"):
        self.client, self.model = client, model

    async def complete(self, messages):
        r = await self.client.messages.create(
            model=self.model, max_tokens=512,
            system=next(m.content for m in messages if m.role == "system"),
            messages=[{"role": "user", "content": m.content}
                      for m in messages if m.role == "user"],
        )
        return type("C", (), {"text": r.content[0].text})()

The redaction bug worth knowing about

A PII redactor that blanks out phone numbers will, by default, blank out the business's own phone number — turning "call us on 01234 567890" into "call us on [redacted:phone]". The assistant becomes useless at the thing it is asked most often.

layer = SafetyLayer(allow=["01234 567890", "hello@theshop.co.uk"])
layer.screen("Call us on 01234 567890, not 07700 900999").text
# 'Call us on 01234 567890, not [redacted:phone]'

Published contact details survive; a stranger's number does not. This is obvious in hindsight and was not obvious in advance.

Crisis detection: the default is the feature

CrisisProtocol fires on distress and breaks persona. The part that took the work was not firing — "I need to dye my hair" must not trigger a safeguarding response. A false positive here derails an ordinary conversation, which is its own kind of harm.

In the system this came from, the protocol is on by default and must be explicitly opted out of, rather than opted into. That ordering is the actual safety property.


What this is not

Stated plainly, because the alternative is letting you find out yourself:

  • Not a complete safety system. These are three narrow controls. Prompt injection, jailbreaks, training-data leakage and tool-use authorisation are all out of scope.
  • Not battle-tested at scale. It runs in one production system with a small user base. It has not been attacked by anyone competent.
  • Not novel. LLM-as-judge, PII regexes and crisis keyword detection are all prior art. The contributions here are the conditional trigger and the decorrelation requirement, both of which are engineering judgement rather than research.
  • The regexes are first cuts. PII detection by regex is inherently incomplete. The high-stakes trigger is English-only and will miss idioms it was not written for.

Provenance

Extracted from PANTHEON, a private multi-tenant agent substrate, in September 2026. The git history is preserved from the original commits — the first is phase 00: bootstrap, 5 June 2026. The extraction removed a dependency on the product's global settings object; the judge is now injected instead of built from a configuration singleton.

The tests were rewritten during extraction. The originals exercised these classes through the agent loop, which meant they tested the loop as much as the guardrail — if a behaviour can only be demonstrated by standing up a whole runtime, it isn't really a library.

Development

uv venv && uv pip install -e ".[dev]"
python -m pytest

Release files for pantheon-guardrails 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pantheon-guardrails 0.1.0
File Size Uploaded
pantheon_guardrails-0.1.0.tar.gz 16.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pantheon-guardrails 0.1.0
File Interpreter ABI Platform
pantheon_guardrails-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 34.5 kB

Release files / pantheon_guardrails-0.1.0.tar.gz

Download URL pantheon_guardrails-0.1.0.tar.gz
Size 16.5 kB
Tags Source
SHA-256 checksum
How to use checksums
6494ccd9b6dd9254386a0d3cf26482579a4a0a6973a88f21fdd0cc5f2afd28b3
BLAKE2b-256 checksum
How to use checksums
9c0672558274c1083aa66edaddb0285f8079d40f672cc937c7dccb36e2fa9375
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.16

Release files / pantheon_guardrails-0.1.0-py3-none-any.whl

Download URL pantheon_guardrails-0.1.0-py3-none-any.whl
Size 18.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
749aed5d5a1032b2eae2774d8244a1f9b5b6df546173f23d666ca70462baec09
BLAKE2b-256 checksum
How to use checksums
936fe75b32c722d26f597ee27f93177e1f3cc1fe115048de335160f0a8ad1346
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.16

Release history Release notifications | RSS feed

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page