Constitutional governance framework for AI agents — parameterized templates rendered once at onboarding
Project description
ai-constitution
A constitutional governance framework for AI agents. Bring your own organization name.
Most AI agent setups bake the agent's name, the operator's name, and infrastructure details directly into prompt files. Cloning the same governance pattern for a new organization means find-and-replace by hand and hoping you didn't miss anything.
This repo separates the framework — the universal pattern for principal authority, ministries, sherpas, protocols, and lifecycle — from the organization that uses it. One config file (org-config.toml) drives a lossless render of the entire constitution into your own copy.
It's the framework half of a three-repo stack:
| Repo | Role |
|---|---|
| ai-constitution (this) | Articles, protocols, command surface, render pipeline |
| ai-ministers | Domain-expert minister skills (planning, design, external affairs, resources, review) |
| ai-sherpa | Execution sherpas (boot, exit, onboarding, plus optional scrum, design, nextcloud, crawler) |
Status: v0.0.1. The render API and 24 templates are populated. Onboarding sherpa lands in M3 (RAJ-61). Public roadmap in Linear.
If you've never built an agent before
Think of an organization. There's a person at the top — call them the principal — who has authority and sets direction. Below them is a principal secretary: an aide who organizes the work, delegates to specialists, keeps the calendar, and makes sure decisions get carried out.
In this framework, the principal is you (the human). The principal secretary is your AI agent. The specialists are ministers (each one expert in a domain — planning, design, etc.). The errand-runners are sherpas (each one wraps a concrete operation: starting services, shutting down, onboarding a new colleague).
The agent doesn't decide everything itself. When a task comes in, it figures out which minister's domain it belongs to, delegates the thinking, and uses sherpas to execute the result. The principal stays in charge — every decision routes back through them.
The reason this exists: when you write all of that down once as text — articles, protocols, identity — and parameterize the names, any organization can adopt the same pattern. You don't fork prompts; you fill in a config file.
Three repos, four files, one render. That's it.
If you're building agents
The framework is text + a small Python render layer. Here's the shape:
ai-constitution/
├── ai_constitution/ # Python: Constitution.load + render API
├── templates/ # 24 jinja2 templates lifted from raj-sadan
│ ├── CONSTITUTION.toml.j2 # 20 articles
│ ├── AGENT.toml.j2 # operating system for the agent
│ ├── IDENTITY.toml.j2 # the agent's persona
│ ├── PRINCIPAL.toml.j2 # the human at the top
│ ├── CLAUDE.md.j2 # bootloader prompt for Claude Code
│ ├── boot-prompt.md.j2 # session boot template
│ └── protocols/PROTOCOL-*.toml.j2 # 13 protocols, "00".."12"
├── skills/ # 5 lifecycle commands as Claude Code skills
│ └── {amend, boot, checkpoint, delegate, reboot}/SKILL.md.j2
├── defaults.toml # baseline values for every variable
└── ai_constitution/schema.py # Pydantic models for org-config.toml
How rendering works
- The framework ships templates with
{{ org.* }}placeholders. - The consumer writes
org-config.toml(or runs the onboarding sherpa to generate it). ai_constitution.Constitution.load(...)readsdefaults.toml, merges the consumer's overrides, validates against the Pydantic schema, and renders every template into a target directory.- The output is plain TOML and Markdown — no template engine ships into the consumer's runtime.
Quick start
pip install ai-constitution
# Render a default "Your Org" bundle just to see the shape
python -m ai_constitution render --output ./my-bundle/
# Or with overrides
python -m ai_constitution render --config my-org.toml --output ./my-bundle/
# Validate an org-config without rendering
python -m ai_constitution validate --config my-org.toml
# See what would change vs. an existing bundle
python -m ai_constitution render --config my-org.toml --output ./my-bundle/ --diff
The variable registry
Every value an organization can override lives under org.*:
[org]
name = "Acme"
slug = "acme"
timezone = "Europe/Madrid"
[org.pm]
name = "Vishal"
title = "Founder"
[org.agent]
name = "Friday"
emoji = "🤖"
[org.ministries]
enabled = ["planning", "design", "review"] # subset of the ai-ministers registry
[org.sherpas]
enabled = ["boot", "exit", "onboarding"] # mandatory three; opt in to more
[org.protocols]
enabled = ["00", "12"] # minimum viable: delegation + cognitive continuity
Full registry in CONTRACTS.md. Every variable has a default, so a partial override is fine.
Three-repo relationship
- ai-constitution ships the framework + render pipeline. It depends on nothing else.
- ai-ministers ships the prompt skills. The render pipeline pulls in only the ministers in
org.ministries.enabled. - ai-sherpa ships the execution code. The onboarding sherpa lives here and calls into
ai_constitution.Constitution.load + render.
A consumer adopts all three as git submodules (or pip-installs the published packages) and points the renderer at their own org-config.toml.
If you're integrating it into something larger
A few design notes worth knowing before you write code against this:
The render contract is lossless. Same inputs always produce the same bytes. StrictUndefined is on, so a typo in a template raises UndefinedError instead of silently rendering empty. Idempotent re-renders are tested.
Validation lives in two places, deliberately. The Pydantic schema (ai_constitution.schema) catches type errors and unknown fields at config-load time. "Required-ness" — refusing to proceed when org.name is still "Your Org" — is the onboarding sherpa's job, not the schema's. This keeps the schema reusable for non-interactive renders (CI pipelines, programmatic tests).
Auto-derivation only fills empties. If org.slug is empty, the renderer derives it from org.name (kebab-case, max 64 chars). If you set org.slug explicitly, your value wins. Same for org.agent.reports_to (defaults to org.pm.title).
Mandatory sherpas are auto-added. Even if a consumer omits boot, exit, and onboarding from org.sherpas.enabled, the schema adds them back. They're load-bearing for the lifecycle.
No conditional templates. Every variable has a default; templates render against whatever the merged config produces. We avoided {{#if minister.X.enabled }} blocks because they make the templates harder to read for everyone — orgs override the lists in org-config.toml and the rendered output reflects only what's enabled.
No runtime substitution. Templates are rendered once, at onboarding (or after python -m ai_constitution render). The output is plain TOML / Markdown that any agent runner can read. Re-render after a framework upgrade by re-running the renderer; we don't ship a template engine into your runtime.
The mockability contract for sherpas: every sherpa must support --dry-run. v0.0.1 has a known portability gap (the lifted sherpas reference raj-sadan filesystem paths) — see CHANGELOG. v0.2 will close it via injected config.
Why this exists (the long-form rationale)
A few decisions are worth defending in detail.
Why constitution-as-code? Most agent stacks treat governance as ad-hoc prompt scaffolding. That works for one project; it doesn't compose. Treating the constitution as code means you get version control, schema validation, automated tests, diff review, and the ability to upgrade the framework underneath an organization without rewriting their rules. The substrate (Python + jinja2 + TOML) is boring on purpose.
Why parameterization at all? The alternative is hand-forking. raj-sadan accumulated 222 mentions of "Mr. V", 61 of "Raj Sadan", 50 of "Vishal", 11 hardcoded ports, 8 service URLs across 18 files. Hand-forking that for a new org is realistic for one fork; it's not realistic for an ecosystem. The audit (raj-sadan/audit/PARAMETERIZATION.md) walked through every substitution.
Why one-shot render at onboarding rather than runtime substitution? Two reasons. First: simplicity at the consumer side — no template engine in the runtime, no per-request rendering cost. Second: the rendered output is a plain artifact you can grep, edit by hand, and version-control. Runtime substitution couples every consumer to a template engine forever and makes the artifacts harder to debug.
Why scoped variable namespace (org.pm.name) rather than flat (PM_NAME)? It scales. Flat names work for ten variables; the registry has fifty and will grow. Dotted scoping mirrors TOML's table structure and lets us add org.contacts.support_email later without naming collisions.
Why three repos rather than one or twelve? One repo means consumers fork the whole thing to opt out of any minister or sherpa. Twelve repos (one per skill) means version-pinning hell. Three repos splits along the natural seams: framework / policy / execution. The collection repos (ai-ministers, ai-sherpa) hide individual-skill versioning behind a single submodule pin.
Why Mustache-style {{ org.x }} rather than ${org.x}? Cloudflare's WAF flags ${...} as injection in some contexts, and the dollar-brace form conflicts with future TOML-native env reference. Mustache is unambiguous and has parsers in every major language.
Status & roadmap
| Milestone | Status | Linear |
|---|---|---|
| M1 — Audit & strategy | Done (in review) | RAJ-45..50 |
| M2 — Templates + render API | Done (in review) | RAJ-51..59 |
| M4 — Collection repos | Done (in review) | RAJ-60, 66..70 |
| M5 — Docs + v0.1.0 release | In progress | RAJ-71..75 |
| M6 — raj-sadan as consumer | Not started | RAJ-76..81 |
| M3 — Onboarding sherpa | Not started (last by design — it's the integration test) | RAJ-61..65 |
Contributing
This is in active development. The Linear project at the link above tracks the roadmap. Issues welcome via GitHub.
License
MIT.
Credits
raj-sadan (github.com/vraj0703) is the reference implementation that ai-constitution was extracted from.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ai_constitution-0.1.3.tar.gz.
File metadata
- Download URL: ai_constitution-0.1.3.tar.gz
- Upload date:
- Size: 25.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c5e8d5b185b80e4471cb6d21939eaa763a37812ce96d7312f56b5d20339e47a0
|
|
| MD5 |
47475bdc5bf5fb28d7614b3e4ea225a1
|
|
| BLAKE2b-256 |
0ebf986d4e17b47660e0739450ba842ee597892251e569e7a851e66fac38b69c
|
Provenance
The following attestation bundles were made for ai_constitution-0.1.3.tar.gz:
Publisher:
release.yml on vraj0703/ai-constitution
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ai_constitution-0.1.3.tar.gz -
Subject digest:
c5e8d5b185b80e4471cb6d21939eaa763a37812ce96d7312f56b5d20339e47a0 - Sigstore transparency entry: 1398929978
- Sigstore integration time:
-
Permalink:
vraj0703/ai-constitution@2c42e69aeba8d944ac14c986f166cb10354c01fc -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/vraj0703
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2c42e69aeba8d944ac14c986f166cb10354c01fc -
Trigger Event:
push
-
Statement type:
File details
Details for the file ai_constitution-0.1.3-py3-none-any.whl.
File metadata
- Download URL: ai_constitution-0.1.3-py3-none-any.whl
- Upload date:
- Size: 16.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1b1a8643f997781ab869d321cd062da6d4170997b7293d89475ad56e653f6747
|
|
| MD5 |
4d60b56522a93da22182da38f8015c5d
|
|
| BLAKE2b-256 |
20538e9d83e2da0cf06df473c5805de672e670a2ee3e49d649e622b5ca291de6
|
Provenance
The following attestation bundles were made for ai_constitution-0.1.3-py3-none-any.whl:
Publisher:
release.yml on vraj0703/ai-constitution
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ai_constitution-0.1.3-py3-none-any.whl -
Subject digest:
1b1a8643f997781ab869d321cd062da6d4170997b7293d89475ad56e653f6747 - Sigstore transparency entry: 1398930001
- Sigstore integration time:
-
Permalink:
vraj0703/ai-constitution@2c42e69aeba8d944ac14c986f166cb10354c01fc -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/vraj0703
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2c42e69aeba8d944ac14c986f166cb10354c01fc -
Trigger Event:
push
-
Statement type: