Guardrails as Code: AI-Assisted Development for Federal Systems
A look at how our team builds rights-impacting systems with Claude Code — fast, and inside the guardrails federal work demands.
Most conversations about AI coding assistants in government start from one of two assumptions, and both are wrong.
The first is that AI assistants are a productivity silver bullet, i.e. point them at a backlog and watch delivery accelerate. The second is that they're a compliance liability, i.e. a way to smuggle unreviewed code, leaked data, and unexplainable models into systems that decide whether a citizen gets a benefit. Agencies that believe the first move fast and accumulate risk; those that believe the second leave capacity on the table and still fall behind.
We hold neither. Our position is simpler, and we can show it in working code: an AI assistant is a capable collaborator that has to operate inside your governance system, not around it. When you set it up that way, you get the speed without giving up the controls. This post walks through how we actually do that, using a reference system we built and open-sourced as the example.
The problem, concretely
Our public reference implementation is a fuzzy identity-resolution service. It revolves around resolving record linkage, the "is this the same person?" problem that runs underneath benefits rosters, case files, and watchlists across nearly every federal program.
Done badly, it denies benefits to people whose names don't match cleanly across systems, married names, hyphenation, diacritics, transliteration, plain data-entry error, and it tends to fail unevenly across demographic groups. Under OMB M-25-21, a system like this is a high-impact AI use case, carrying obligations for pre-deployment testing, ongoing monitoring, human review with a right to appeal, and public accountability.
That is exactly where "just let the AI code it" falls apart and where we've found AI-assisted development pays off most, because the discipline the domain requires is discipline you can encode.
The core idea: make the right thing the cheap thing
An AI assistant is eager to help you past a constraint, even when the constraint exists for a good reason. Ask it to "get the tests passing" and it may quietly weaken the test. Ask it to add a feature and it may log a field it shouldn't. This isn't malice; it's an optimizer doing what optimizers do.
You don't fix that with a longer prompt asking it to please be careful. You fix it by changing the environment the assistant works in, so that the compliant path is also the path of least resistance. In practice, that's four moves.
1. The rules are code, not requests
The project's non-negotiables live in a checked-in contract (CLAUDE.md) that the assistant reads at the start of every session, no real PII ever, no matcher change without a bias re-evaluation, no logging of unredacted data, every decision must be explainable. But a contract is only a statement of intent. The teeth are hooks: small scripts that run automatically and can stop a write before it lands.
- A PII guard blocks any write to the data directory that would introduce a real-looking Social Security number instead of synthetic test data.
- An audit-log guard rejects edits that would add an update or delete operation to what must remain an append-only, hash-chained record.
- A post-edit hook, triggered whenever the matcher changes, reminds the assistant to re-run the fairness evaluation before it can consider the work done.
The PII guard, for instance, inspects the proposed content before it's written and refuses anything with a real-looking Social Security number:
# .claude/hooks/guard_pii_writes.py (PreToolUse on Write/Edit)
hit = looks_like_real_ssn(content) # any SSN with area < 900
if hit:
print(f"BLOCKED: content contains an SSN-shaped value ({hit}) "
"outside the synthetic 900-999 range.", file=sys.stderr)
return 2 # exit 2 blocks the write; stderr is shown to the assistant
A prompt can be ignored. A hook cannot. The difference is the whole game.
2. Judgment-heavy work is consistent, every time
Some tasks need judgment, not a yes/no gate — "is this fair?", "does this leak PII?", "does the model card still describe what the code actually does?" We encode those as focused subagents, each with a narrow tool set and a clear posture:
# .claude/agents/model-card-writer.md
description: Drafts or updates docs/governance/MODEL_CARD.md to
reflect the current state of the matcher. Use after a matcher
change has been implemented and tests pass. Produces a clean,
reviewer-friendly draft; the maintainers must review before commit.
tools: Read, Grep, Glob, Bash, Edit
The model card is the plain-language summary a non-engineer reviewer relies on — a privacy officer, a civil-rights office, an agency CAIO. Keeping it in sync with the code is exactly the easy-to-defer work that gets skipped under deadline; a subagent that redrafts it on every matcher change means it never falls behind. And the posture is explicit: the subagent produces a draft, then a human reviews before it's committed.
The point isn't to replace people. It's to guarantee that the same diligence happens every time, regardless of which engineer is at the keyboard or how busy the sprint is.
3. The rituals are one keystroke
Governance work that's tedious gets skipped. So the recurring tasks are slash commands: /bias-check runs the full fairness eval and summarizes it; /pii-scan looks for handling regressions; /update-model-card refreshes the model card from the current code and metrics; /threat-model walks a structured security checklist before a change is built. If a ritual is worth doing twice, it becomes a command. If a command is never used, we delete it.
4. High-stakes changes plan before they act
For anything touching the matcher, the audit-log schema, the API surface, or dependencies, the convention is plan-first: the assistant writes the plan, a human reviews it, and only then does implementation begin. For docs, tests, and behavior-preserving refactors, that ceremony is overkill and we skip it. Matching the level of process to the level of risk is the entire discipline in one sentence.
What we deliberately don't do
Restraint is part of the method, and it's usually what a reviewer wants to hear:
- No auto-merge. The assistant never merges its own work. On rights-impacting pull requests it posts findings as a comment; the gate is the test suite and a human.
- No "fix-it" mode. The assistant does not silently re-run tests until they pass and declare victory. A failing test surfaces to a person who decides what to do.
- No hidden instructions. Everything the assistant is asked to respect lives in checked-in files that humans review in the open.
- No bypass paths. If the assistant needs to do something a hook blocks, the answer is to change the hook in a reviewed pull request — never to route around it.
Why this matters for your program
We didn't adopt AI-assisted development to write code faster and hope the governance kept up. We adopted it because the governance obligations of federal work — testing, fairness evaluation, explainability, auditability, human review — are exactly the kind of recurring, high-discipline work that benefits most from being encoded once and enforced automatically.
The result, from a program's point of view, is threefold. Delivery is faster, because engineers spend their time on judgment instead of ceremony. The controls are stronger, not weaker, because machinery enforces them rather than memory. And it's all auditable: every gate, review posture, and rule lives in version control where your ISSO, privacy officer, and CAIO can read it.
That's the standard we bring to federal engagements and because our reference implementation is public domain, it's a standard you can inspect before you ever talk to us.
The reference implementation described here is open source and in the public domain. Contact us to talk about how this approach applies to your program.