Setting Up Automated Governance Checks on GitHub PRs
Setting Up Automated Governance Checks on GitHub PRs
This is a practical, step-by-step guide to adding automated governance checks to your GitHub pull requests. By the end, every PR in your repository will show a governance report with a pass/warn/fail verdict, drift scores, and commit-to-session attribution.
Total setup time: about 5 minutes for the basics, 15 minutes if you add feature manifests and requirement traceability.
What You'll Get
Before diving into setup, here's what the end result looks like. Every PR will show:
- Verdict: pass (green), warn (yellow), or fail (red)
- Commit coverage: how many commits are attributed to governed sessions
- Per-session drift scores: how far each session deviated from its parameters
- Scope violations: files changed outside the allowed scope
- Boundary violations: changes to explicitly denied paths
- Feature coverage: which features have tagged code in the PR
- Requirement coverage: which requirements are implemented
The report appears as a GitHub check run, visible in the PR's checks tab. If you install the ExoProtocol GitHub App, it also posts an inline comment with the human-readable summary.
Prerequisites
You'll need:
- A GitHub repository (public or private)
- Python 3.10+ available in your CI environment
- A terminal with access to your repo for the initial setup
Step 1: Install ExoProtocol
Install the CLI tool. This is the governance kernel that powers everything:
pip install exoprotocol
Verify the installation:
exo --version
You should see the current version number. ExoProtocol has no runtime dependencies beyond PyYAML, so installation is fast and lightweight.
Step 2: Initialize Governance
Navigate to your repository root and initialize the governance structure:
cd your-project
exo init
This creates the .exo/ directory with the initial governance structure:
.exo/
constitution.yaml # Governance rules and constraints
config.yaml # Configuration settings
governance/ # Compiled governance state
memory/ # Session mementos and history
cache/ # Temporary session data
The constitution.yaml is where you define your governance rules. Open it and configure the basics:
# .exo/constitution.yaml
project:
name: "your-project"
description: "Brief description of your project"
governance:
deny_patterns:
- ".env"
- ".env.*"
- "*.key"
- "*.pem"
- "migrations/**"
- ".github/workflows/**"
- "infrastructure/**"
defaults:
max_files: 10
drift_threshold: 0.7
The deny_patterns define files that AI agents must never modify. The defaults set baseline budgets for governed sessions. Adjust these to match your project's norms.
Step 3: Compile Governance
Compile the constitution into the governance lock file:
exo compile
This generates .exo/governance/lock.json - the compiled governance state that all checks reference. The lock file is deterministic: the same constitution always produces the same lock.
Commit the .exo/ directory to your repository:
git add .exo/
git commit -m "chore: initialize exoprotocol governance"
Step 4: Generate the CI Workflow
ExoProtocol can generate a GitHub Actions workflow that runs governance checks on every PR:
exo adapter-generate --target ci
This creates .github/workflows/exo-governance.yml:
# .github/workflows/exo-governance.yml
# Auto-generated by ExoProtocol - do not edit manually
name: ExoProtocol Governance
on:
pull_request:
branches: [main]
jobs:
governance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history needed for commit analysis
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install ExoProtocol
run: pip install exoprotocol
- name: Verify Governance Integrity
run: exo verify
- name: Run PR Governance Check
run: |
exo pr-check \
--base ${{ github.event.pull_request.base.ref }} \
--head ${{ github.event.pull_request.head.sha }} \
--drift-threshold 0.7
- name: Upload Governance Report
if: always()
uses: actions/upload-artifact@v4
with:
name: governance-report
path: .exo/reports/
You can customize the workflow by editing .exo/config.yaml before generating:
# .exo/config.yaml
ci:
drift_threshold: 0.7
python_version: "3.11"
install_command: "pip install exoprotocol"
Commit the workflow file:
git add .github/workflows/exo-governance.yml
git commit -m "ci: add exoprotocol governance checks"
Step 5: Install the GitHub App (Optional)
The CI workflow gives you pass/fail verdicts on PRs. The ExoProtocol GitHub App adds richer integration:
- Inline PR comments with human-readable governance reports
- Check run annotations on specific files with violations
- Team dashboard with aggregate drift metrics
- One-click install, no workflow configuration needed
Visit the ExoProtocol website to install the GitHub App on your repository. The app runs exo pr-check in its own infrastructure, so it works even if you don't have the CI workflow set up.
The CI workflow and the GitHub App are complementary. The workflow gives you control (runs in your CI, your configuration). The app gives you convenience (richer UI, no workflow maintenance).
Step 6: Open a PR and See It Work
Now create a branch, make some changes, and open a pull request:
git checkout -b test-governance
# ... make some changes ...
git add -A
git commit -m "test: verify governance checks"
git push -u origin test-governance
Open a PR against main. Within a minute or two, you'll see the governance check appear in the PR's checks tab.
Reading the Governance Report
The report has several sections. Here's what each one means:
Verdict is the top-line result:
- PASS (green): All commits are attributed to governed sessions. Drift scores are below the threshold. No boundary violations. The PR is clean.
- WARN (yellow): Some drift detected or minor scope violations, but no hard failures. The reviewer should check the flagged areas but the PR isn't blocked.
- FAIL (red): One or more hard failures - ungoverned commits, boundary violations, governance integrity drift, or verify failures. The PR needs investigation.
Commit Coverage shows how many commits in the PR are matched to governed sessions:
Commits: 5 total, 4 governed, 1 ungoverned
An ungoverned commit means someone pushed a change without starting a governed session. This is the most common cause of FAIL verdicts. It doesn't necessarily mean the code is bad - it means there's no governance data to verify it.
Per-Session Detail shows each governed session that contributed to the PR:
Session s-abc123 (AUTH-42):
Drift Score: 0.18
Files: 4/8 budget (50%)
LOC: 120/300 budget (40%)
Scope: All files within allowed paths
Verdict: PASS
Low drift scores with no violations indicate well-governed work. High drift scores or scope violations indicate the agent exceeded its mandate.
Scope Violations list specific files that were changed outside the session's allowed scope:
Scope Violations:
- src/utils/helpers.py (not in allowed paths)
- config/database.yml (in denied paths - BOUNDARY VIOLATION)
Boundary violations (changes to denied paths) are more severe than regular scope violations.
Step 7: Advanced Configuration
Once the basics are working, you can add more governance layers.
Add a Feature Manifest
Create .exo/features.yaml to track which code belongs to which feature:
features:
user-auth:
status: active
description: "User authentication and authorization"
allow_agent_edit: true
payment-processing:
status: active
description: "Stripe payment integration"
allow_agent_edit: true
legacy-billing:
status: deprecated
description: "Old billing system - do not extend"
allow_agent_edit: false
Tag your code with @feature: annotations:
# @feature:user-auth
def login(email: str, password: str) -> AuthToken:
...
# @endfeature
Run the feature trace to check coverage:
exo trace
The PR governance check will automatically include feature coverage if a features.yaml exists.
Add a Requirement Registry
Create .exo/requirements.yaml to define what the system must do:
requirements:
REQ-001:
description: "Users must authenticate before accessing protected resources"
status: active
priority: high
tags: [security, auth]
REQ-002:
description: "All API responses must include request-id header"
status: active
priority: medium
tags: [api, observability]
Reference requirements in code with @req: annotations:
# @implements:REQ-001
@app.middleware("http")
async def auth_middleware(request, call_next):
if not request.headers.get("Authorization"):
return JSONResponse(status_code=401)
...
Check requirement coverage:
exo trace-reqs
Customize Drift Thresholds
Different projects have different tolerances. Adjust the drift threshold in your config:
# .exo/config.yaml
ci:
drift_threshold: 0.5 # Stricter: flag drift above 50%
Or per-session:
exo session-finish --session-id <id> --drift-threshold 0.3
Lower thresholds catch more drift but generate more warnings. Start with 0.7 (lenient) and tighten as your team calibrates.
Generate Agent Configurations
Keep your agent config files in sync with governance:
# Generate all agent configs from governance state
exo adapter-generate --target claude # CLAUDE.md
exo adapter-generate --target cursor # .cursorrules
exo adapter-generate --target agents # AGENTS.md
These files include deny patterns, budgets, and lifecycle commands derived from your governance configuration. Regenerate them whenever you update the constitution.
Run a Composite Drift Check
For a comprehensive health check of your entire governance setup, use the drift command:
$ exo drift
Governance Drift Report
========================
Overall: PASS
Governance Integrity: PASS
Constitution hash matches lock
Adapter Freshness: WARN
CLAUDE.md hash mismatch - regenerate with `exo adapter-generate`
Features: PASS
12 features, 0 violations
Requirements: PASS
8 requirements, 7 covered, 1 uncovered (warning)
Sessions: PASS
No stale or orphaned sessions
This checks everything in one command: governance integrity, adapter freshness, feature traceability, requirement coverage, and session health. Run it locally before pushing, or add it to your CI workflow.
Troubleshooting
"No governed sessions found" on every PR:
This means commits aren't being matched to sessions. Make sure developers are running exo session-start before making changes and exo session-finish when done. The PR check matches commits to sessions by timestamp, so the session must be active when the commit is created.
"Governance integrity: FAIL":
This means the compiled governance lock doesn't match the current constitution. Run exo compile and commit the updated lock file.
CI workflow fails with "exo: command not found":
The Python setup step may not be adding pip-installed binaries to PATH. Add pip install exoprotocol to the install step and ensure the Python version matches what's in your config.
Drift scores seem too high:
Your budgets may be too tight for the type of work being done. Check the per-session detail to see which budget dimension is driving the drift, and adjust accordingly.
What's Next
With automated governance checks running on every PR, you've established a baseline of accountability for AI-generated code. From here, you can:
- Tighten drift thresholds as your team calibrates
- Add feature manifests for traceability across large codebases
- Add requirements for compliance and audit trails
- Use audit sessions for independent code review
Every PR, every commit, every session - governed, measured, and visible. That's the foundation of trustworthy AI-assisted development.
Get started now. Five minutes of setup saves hours of review debt on every PR that follows.