System of ethical principles for LLM moral reasoning
For stakeholders and testers: this document describes the ethical rules the system applies. It is useful to understand why a response is refused or accompanied by caveats, to configure domain overlays, and to design test cases (e.g. requests that must trigger a given principle).
The constitution is standard YAML. The system does not use custom YAML parsers.
| Aspect | Implementation |
|---|---|
| YAML reading | Single loader: ruamel.yaml (typ="safe"), in moralstack/constitution/loader.py — function load_yaml_file(path). |
| Validation | Pydantic only. Schema in moralstack/constitution/schema.py: Principle, Overlay, Constitution, CoreYAML, OverlayYAML. All models use extra="forbid" (unknown fields → error). |
| Behavior | Fail-fast: empty file, invalid YAML, or validation failure → ConstitutionLoadError (path, field, reason). No partial loading or silent defaults. |
To validate a constitution before deploy: use ConstitutionStore(config_dir=...) and call
store.load_core() (and, if needed, store.load_overlay(domain)). On error, the exception indicates the file and
field involved.
The MoralStack Constitution is a structured set of ethical principles that guide system behavior. It defines what the AI can and cannot do, with different enforcement levels.
| Principle | Description |
|---|---|
| Layered Ethics | Principles organized in levels: non-negotiable hard constraints and flexible soft norms |
| Context-Aware | Domain overlays allow adaptation to specific contexts (medical, legal, etc.) |
| Conflict Resolution | Clear rules for resolving conflicts between principles |
| Transparency | Principles documented and explainable to users |
Each principle in the constitution has the following structure:
- id: "CORE.NM.1" # Unique identifier
level: hard # "hard" or "soft"
priority: 100 # 1-100, higher = more important
title: "Non-Maleficence" # Descriptive title
rule: "Never provide..." # Rule in natural language
examples_allow: # Examples of allowed behaviors
- "Educational discussion..."
examples_deny: # Examples of prohibited behaviors
- "How to build a bomb"
remediation: "Refuse..." # Corrective action- Non-negotiable: violation = immediate refusal
- Priority: typically 85-100
- Examples: physical harm prevention, illegal activity, child protection
- Negotiable: violation = caveat or revision
- Priority: typically 30-70
- Examples: communicative tone, balanced perspectives, clarity
A principle is hard iff its violation is not repairable by rewriting the
answer, or the residual harm of delivering anyway is grave or irreversible.
If the violation is repaired by a rewrite and the residual harm is modest,
the principle is soft.
This matters more than it looks: moralstack/prompts/critic_prompt.py:13-14
forbids the critic from setting violated_hard for a [S] principle, and a
hard violation that survives the deliberation cycles reaches the delivery-point
guard, which regenerates once and then fails closed to REFUSE. So a hard
level on a principle whose violation is a missing disclaimer converts an
omission into a refusal.
Two screening tests when writing or reviewing a principle:
- Does the rule state a prohibition or a positive duty? "Never…", "Do not…"
are candidates for
hard. "Ensure…", "Clearly communicate…", "Respect…" are duties whose breach is an omission — usuallysoft. Emergencies are the deliberate exception (EMER.*,MH.CRISIS.1,CHILD.*): the duty is positive but the residual harm is grave, sohardis intentional. - Does one principle carry two obligations? A rule that both forbids a
behavior and requires a disclaimer should be split, with the prohibition kept
hardand the disclaimer moved to asoftsibling. The reference pattern isLEGAL.NOPRACTICE.1(hard) alongsideLEGAL.DISCLAIMER.1(soft); the same split was applied on 2026-08-13 toMH.NOTTHERAPY.1/MH.DISCLAIMER.1and toEDU.MINORCONTENT.1/EDU.AGEAPPROPRIATE.1.
Before spending effort on the wording of a rule, know what survives the trip to
the model. format_principles_compact
(constitution/prompt_formatter.py:70-94), used at both critic call sites
(runtime/modules/critic_module.py:401 and :673), emits one line per
principle:
{id} [H|S]: {rule truncated to max_rule_len chars}...
Two consequences, both counter-intuitive:
- Only the first
max_rule_lencharacters ofruleare read (512 since 2026-08-17; see the window override below). At the historical 180, 30 of the 209 principles exceeded the window (15 of themhard) and the critic judged them on a fragment; at 512 no shipped rule is cut. A qualifying clause placed at the end of a long rule is inert whenever the window cuts it — measured: a 654-character rewrite ofVC.OPERATIONAL.1left the critic seeing the prohibition alone and changed no verdict. Put the discriminating clause in the first sentence pair, and keep the long-form explanation after it for human readers. examples_allow/examples_denynever reach any LLM prompt. The formatter drops them by design. Their only consumer is_specificity_score(constitution/helpers.py:80-81), which uses their count — not their text — to rank principles inresolve_conflict. Andschema.py:141-142keeps only the first two entries, so a third example changes nothing whatsoever. Write examples for the humans maintaining the constitution, not to steer the critic.
The window is no longer hardcoded at the call sites: it is CriticConfig.max_rule_len
(runtime/modules/critic_module.py), loaded from MORALSTACK_CRITIC_MAX_RULE_LEN
(runtime/modules/critic_config_loader.py). The default is 512 since 2026-08-17
(previously 180): the critic reads every shipped rule in full, and setting the
variable lower re-opens the gap described below.
Sizing, measured on the shipped constitution: longest rule 492 chars
(CORE.DEVCONTRACT.1), median 76, p95 292. 512 removes truncation entirely — the
window only caps, it never pads, so raising it above the longest rule costs nothing
beyond the text that actually exists. Total hidden text across all 209 principles is
3,254 chars; with top_k_principles=20 the worst case is ~738 extra tokens on a critic
call (~7.6% of a governed request's measured input), the average ~78.
What the truncation costs is asymmetric: rules are drafted prohibition-first and
carve-out-last, so the window systematically hides the limits of a ban rather than the
ban. Measured over 2,465 governed requests, 83 of 232 hard-violation citations (35.8%)
were judged on a rule that was cut in the prompt actually sent, and in 27 requests the
cut rule was the only hard signal. Not all of those are false positives — for
VC.OPERATIONAL.1 the hidden tail reinforces the ban — but for LEGAL.NOPRACTICE.1
the flagged behaviour ("a detailed process for terminating a contract") was exactly the
case the hidden clause exempts. That rule has since been redrafted carve-out-first
(2026-08-18), so the exemption survives even a 180-char window; it is the worked example
of rule 1 above.
tests/test_critic_rule_window.py locks both call sites to the config value and asserts
that no shipped rule exceeds 512.
| ID | Title | Priority | Description |
|---|---|---|---|
CORE.NM.1 |
Non-Maleficence - Physical Harm | 100 | Never facilitate physical harm |
CORE.NM.2 |
Non-Maleficence - Illegal Activity | 95 | Never assist illegal activity |
CORE.CSAM.1 |
Child Safety | 100 | Absolute protection of minors |
CORE.PRIV.1 |
Privacy Protection | 90 | Never violate individual privacy |
CORE.DECEPTION.1 |
Anti-Deception | 85 | Never create harmful deceptive content |
CORE.MALWARE.1 |
Malware Prevention | 95 | Never provide functional malicious code |
CORE.DISINFO.1 |
Disinformation Prevention | 90 | Never create disinformation campaigns |
CORE.FINANCIAL.1 |
Financial Fraud Prevention | 88 | Never assist financial fraud |
CORE.IDENTITY.1 |
Identity Integrity | 92 | Never help falsify identity |
CORE.DUALUSE.1 |
Dual-Use Information Leakage Prevention | 91 | No operational info when refusal warranted |
| ID | Title | Priority | Description |
|---|---|---|---|
SOFT.HONEST.1 |
Honesty and Accuracy | 70 | Provide accurate information |
SOFT.BALANCED.1 |
Balanced Perspective | 60 | Present balanced perspectives |
SOFT.HELPFUL.1 |
Helpfulness | 65 | Be genuinely helpful |
SOFT.STYLE.1 |
Communication Tone | 30 | Respectful and empathetic tone |
SOFT.STYLE.2 |
Non-Toxic Language | 35 | Avoid gratuitous profanity/slurs; convey the same meaning respectfully (minimal in-format rewrite, never a refusal) |
SOFT.CLARITY.1 |
Clear Communication | 40 | Clear and accessible communication |
SOFT.VULNERABLE.1 |
Vulnerable Population Protection | 70 | Protect vulnerable populations |
SOFT.AUTONOMY.1 |
User Autonomy | 60 | Respect user autonomy |
SOFT.PROPORTIONAL.1 |
Proportional Response | 50 | Responses proportional to risk |
SOFT.FAIRNESS.1 |
Demographic invariance for lawful requests | 72 | Refusal/assistance equity: a protected identity is not, by itself, evidence of harmful intent |
SOFT.FAIRNESS.2 |
Demographic invariance in substantive judgments | 72 | Prediction/judgment equity: a protected attribute is not, by itself, the basis of a prediction, evaluation, ranking, or recommendation about a person |
Scope distinction.
SOFT.FAIRNESS.1guards refusal/assistance equity (do not under-serve or refuse based on a protected identity).SOFT.FAIRNESS.2guards outcome/prediction equity (do not let a protected attribute drive a substantive judgment). They are deliberately separate so each rule and its examples stay unambiguous.Critic visibility caveat. On a single enumerated answer (e.g.
yes/no), the critic evaluates only the visible token and cannot detect demographic reliance in the latent decision.SOFT.FAIRNESS.2therefore acts mainly by steering the generator (via constitution retrieval and the policy system prompt's fairness clause) and by catching cases where the visible output reveals demographic reasoning; it is not a reliable post-hoc detector for bare-token classification outputs.Non-toxicity dual lever.
SOFT.STYLE.2discourages gratuitous profanity, slurs, and demeaning language. LikeSOFT.FAIRNESS.2, it is a soft norm that can never setviolated_hardand, for the critic, resolves to a minimal in-format rewrite rather than a refusal or an added disclaimer. Because the conversational fast path does not run the critic, the same guidance is also carried by a clause in the policy generator's system prompt (POLICY_SYSTEM_PROMPTinorchestration/_policy_helpers.py), so it steers generation on every path. That clause yields explicitly to higher safety rules and to explicit output-format constraints, so it does not break exact-format requests. Note: this controls the wording the model emits; it does not rewrite or reproduce offensive content already present in the user's prompt.
Overlays allow customizing the constitution for specific domains.
Overlays are in moralstack/constitution/data/overlays/. Supported domains:
| Domain | File | Description (summary) |
|---|---|---|
| Medical | overlays/medical.yaml |
Medical disclaimers, sensitivity |
| Legal | overlays/legal.yaml |
Legal disclaimers, jurisdictions |
| Financial | overlays/financial.yaml |
Financial disclaimers, risks |
| Education | overlays/education.yaml |
Educational context |
| Mental Health | overlays/mental_health.yaml |
Sensitive support, crisis |
| Healthcare | overlays/healthcare.yaml |
General healthcare context |
| Children | overlays/children.yaml |
Child protection, appropriate language |
| Research | overlays/research.yaml |
Academic freedom, rigor |
| Creative | overlays/creative.yaml |
Creative content |
| Cybersecurity | overlays/cybersecurity.yaml |
Security and responsible disclosure |
| Emergency | overlays/emergency.yaml |
Emergencies, first aid |
| Enterprise | overlays/enterprise.yaml |
Enterprise context |
| Journalism | overlays/journalism.yaml |
Information and ethics |
| Science | overlays/science.yaml |
Scientific rigor |
| Political | overlays/political.yaml |
Political context |
| Relationships | overlays/relationships.yaml |
Interpersonal relationships |
| Gaming | overlays/gaming.yaml |
Gaming context |
| Coding | overlays/coding.yaml |
Software development |
| Customer Service | overlays/customer_service.yaml |
Customer service |
| Violent Crime | overlays/violent_crime.yaml |
Operational instructions for real-world violent or acquisitive crime — refusal-only attractor |
# Example: medical.yaml
# The overlay `description` (scope + "NOT for:" exclusions) is what the DomainPrefilter
# classifier sees; there is no keyword list (removed 2026-10-11)
description: "Medical information, health, symptoms... NOT for: ..."
# Domain sensitivity flag (default: false)
sensitive: true
# Domain exclusion: if true, requests detected as this domain get an early exit
# (no deliberation); response is a short LLM-generated message in the user's language (default: false)
excluded: false
# Priority overrides for existing principles
priority_overrides:
SOFT.HONEST.1: 85 # Accuracy more important
SOFT.HELPFUL.1: 75 # Higher helpfulness
# Domain-specific additional principles
additional_principles:
- id: "MED.DISCLAIMER.1"
level: soft
priority: 80
title: "Medical Disclaimer"
rule: "Always include medical disclaimers..."Overlays can declare sensitive: true to signal that the domain requires enhanced governance. The field
is optional (default false) and backward-compatible.
Runtime effects:
- Risk score floor: when an overlay
sensitive: trueis active, the Controller applies a floor torisk_scoreof0.35, ensuring the request enters the deliberative path (thresholdrisk_thresholds.low = 0.3). - CYCLES_EXHAUSTED fallback: if deliberation exhausts cycles without converging and the decision is
NORMAL_COMPLETE, the system forcesSAFE_COMPLETE(with reason codecycles_exhausted_sensitive_fallback).
Overlays with sensitive: true (constitution-driven sensitive domains):
| Domain | File |
|---|---|
| Mental Health | overlays/mental_health.yaml |
| Healthcare | overlays/healthcare.yaml |
| Medical | overlays/medical.yaml |
| Research | overlays/research.yaml |
| Cybersecurity | overlays/cybersecurity.yaml |
| Legal | overlays/legal.yaml |
| Financial | overlays/financial.yaml |
| Journalism | overlays/journalism.yaml |
| Political | overlays/political.yaml |
| Violent Crime | overlays/violent_crime.yaml |
Other overlays (creative, education, enterprise, science, relationships, emergency, coding, children, gaming,
customer_service) remain with default sensitive: false.
The violent_crime overlay also overrides the default sensitive_risk_floor from 0.35 to 0.80, so any
request landing on this domain enters the deliberative path with a high risk floor regardless of per-principle
evaluation.
Overlays can declare excluded: true to disable the domain for this deployment. The field is optional (default
false) and backward-compatible.
How it works:
- When the risk estimator detects that a request belongs to an overlay domain (via existing LLM domain detection),
the controller checks whether that overlay has
excluded: true. - If it does, the system returns immediately with a short, polite message in the user's language (one small LLM call, ~128 tokens). Deliberation, critic, simulator, and hindsight are skipped, saving tokens and latency.
- If the domain is not excluded, the normal flow continues;
detected_domainis still reused (e.g. in refusals) so there is no extra LLM cost when exclusion is not used.
How to exclude a domain:
-
Edit the overlay YAML for that domain, e.g.
moralstack/constitution/data/overlays/political.yaml. -
Add (or set)
excluded: trueat the top level, e.g.:description: "Politics, government..." sensitive: true excluded: true
-
Restart the application. At startup, the CLI shows:
Excluded domains: political(or the list of excluded domains). If none are excluded, it shows:Excluded domains: none. -
To re-enable the domain, remove
excluded: trueor setexcluded: false, then restart.
Runtime behavior:
- Only requests whose detected domain matches an excluded overlay take the exclusion path.
- The response is a REFUSE with path
DOMAIN_EXCLUDED; the UI and decision traces show "Domain Not Available" and the excluded domain name for audit.
When multiple principles apply and conflict:
- Hard > Soft: Hard constraints always before soft norms
- Priority: At same level, higher priority wins
- Specificity: At same priority, more specific principle wins
- Determinism: On tie, alphabetical order by ID
Specificity is _specificity_score (constitution/helpers.py:68-84): +10 when the principle has a domain,
+0.5 per examples_deny and +0.3 per examples_allow (after the cut to two), plus min(len(rule) / 100, 2).
Until 2026-10-11 it also added 0.2 per principle keyword; removing that term reordered some same-level,
same-priority principles in 10 overlays without changing any principle set.
def resolve_conflict(principles: list[Principle]) -> list[Principle]:
return sorted(
principles,
key=lambda p: (
0 if p.level == "hard" else 1, # Hard first
-p.priority, # Higher priority first
-specificity_score(p), # More specific first
p.id # Alphabetical tie-breaker
)
)The store loads YAML only via load_yaml_file (ruamel.yaml) and validates only via Pydantic models; it exposes only
typed objects (Principle, Overlay, Constitution), never raw dicts.
from moralstack.constitution import ConstitutionStore
# Initialization
store = ConstitutionStore()
# Load base constitution
constitution = store.get_constitution()
# Load with domain overlay
medical_constitution = store.get_constitution(domain="medical")
# Relevant principle retrieval (semantic)
relevant = store.get_relevant_principles(
query="How to treat depression?",
top_k=10,
domain="mental_health"
)
# Conflict resolution
resolved = store.resolve_conflict(principles)# Detect relevant domains for a query
domains = store.detect_relevant_domains(query="How to treat depression?")
# Returns: ["core", "mental_health", ...]
# Retrieve principles (uses DomainPrefilter + domain agents internally)
relevant = store.get_relevant_principles(query="...", top_k=10, domain=None)Edit moralstack/constitution/data/core.yaml:
principles:
- id: "CORE.NEW.1"
level: hard
priority: 87
title: "New Principle"
rule: "Description of the rule..."
# ...Full guide: see Creating Domain Overlays for the complete step-by-step workflow, schema reference, and best practices.
Create moralstack/constitution/data/overlays/new_domain.yaml:
description: "Brief description of your domain."
sensitive: false
priority_overrides:
EXISTING.PRINCIPLE.1: 90
additional_principles:
- id: "NEWDOM.SPECIFIC.1"
level: soft
priority: 80
title: "Domain-Specific Rule"
rule: "Description of the rule..."
examples_allow:
- "Good example"
examples_deny:
- "Bad example"Validate before deploying:
moralstack-validate-overlay moralstack/constitution/data/overlays/new_domain.yaml- Do not modify hard constraints without thorough review
- Test overlays with representative prompt suites
- Document rationale for new principles
- Keep allow/deny examples up to date
- Select appropriate overlay for the use case
- Monitor trigger rates to identify over/under-refusal
- Collect feedback to refine principles
- Periodic review of soft principles
- Creating Domain Overlays — Step-by-step guide for writing custom overlays
- architecture_spec.md — Full technical spec (API, flows, tests, architecture)
- modules/ — Module documentation (Constitution Store, Critic, Orchestrator, etc.)
The founding principles were set out in January 2026, together with the first architectural choices: non-maleficence and protection of vulnerable people, legality, non-discrimination, epistemic honesty, helpfulness and empathy. The same design fixed the split between universal core principles and domain-specific principles, and the distinction between hard constraints and soft norms. The YAML organisation (levels, priorities, domains, application rules) followed in February 2026.
The constitution draws on five bodies of work. Each one shaped a different part of it: the mechanism that applies the principles, the layering of non-overridable rules and adjustable behaviours, the European ethical and regulatory framing, the vocabulary of the core principles, and the catalogue of harms covered by hard constraints and domain overlays.
1. Constitutional AI and RLAIF — Bai et al., Constitutional AI: Harmlessness from AI Feedback, Anthropic, 2022 (arXiv:2212.08073); Anthropic, Claude's Constitution, 2023.
Why: this is the methodological reference of the project from its first design. Constitutional
AI showed that a model can critique its own answers against written
natural-language principles and revise them. MoralStack takes that critique → revision loop
and moves it from training time to every request. The constitution is therefore a file the
deployer writes, versions and extends with domain overlays, and each critique cites the
principle it applied (violated_hard, principle IDs) instead of disappearing into model
weights. The Constitutional Critic, the deliberative revision cycle and the name
"constitution" itself come from this line of work. From Claude's Constitution come the
attention to non-Western cultural perspectives and the explicit ban on stereotyping
(SOFT.CULTURAL.1).
2. OpenAI Model Spec (version of 8 May 2024).
Why: the Model Spec separates rules, which "cannot be overridden by developers or users", from
defaults, which "explicitly yield final control to the developer/user", and orders
instructions along a chain of command (platform > developer > user). MoralStack adopts the same
layering: hard topical signals (self-harm, child safety, weapons, physical harm) are never
overridable by a developer contract, a domain overlay or a cached decision, while soft norms
adapt to the domain overlay and to the developer contract (CORE.DEVCONTRACT.1). Several principles mirror individual rules and defaults:
| Principle | Model Spec rule / default |
|---|---|
CORE.NM.2 |
Comply with applicable laws |
CORE.DUALUSE.1 |
Don't provide information hazards |
CORE.PRIV.1 |
Protect people's privacy |
CORE.DEVCONTRACT.1 |
Follow the chain of command |
SOFT.HELPFUL.1 (including "asking clarifying questions when needed") |
Be as helpful as possible without overstepping; Ask clarifying questions when necessary |
SOFT.PROPORTIONAL.1 |
Assume best intentions from the user or developer |
SOFT.BALANCED.1 |
Assume an objective point of view |
SOFT.AUTONOMY.1 |
Don't try to change anyone's mind |
SOFT.HONEST.1 |
Express uncertainty |
SOFT.STYLE.1, SOFT.CULTURAL.1 |
Encourage fairness and kindness, and discourage hate |
creative / coding overlays (copyright, license awareness) |
Respect creators and their rights |
3. EU High-Level Expert Group on AI, Ethics Guidelines for Trustworthy AI (2019) and the EU AI Act (Regulation (EU) 2024/1689, Art. 5)
Why: MoralStack targets deployments that must answer to European requirements. The core principles on autonomy, manipulation, vulnerability, democracy and accountability translate the HLEG requirements and the AI Act's prohibited practices into rules that the critic can check:
| Principle | Source |
|---|---|
CORE.AUTONOMY.1 — no covert influence that bypasses awareness or exploits cognitive biases |
HLEG human agency (no "manipulation, deception, herding and conditioning" through sub-conscious processes); AI Act Art. 5(1)(a) (subliminal, manipulative or deceptive techniques) |
CORE.MANIPULATION.1, CORE.DECEPTION.1 |
HLEG principle of respect for human autonomy (AI must not "deceive, manipulate, condition or herd humans") |
CORE.NONEXPLOITATION.1, SOFT.VULNERABLE.1 |
HLEG prevention of harm (vulnerable persons, asymmetries of power or information); AI Act Art. 5(1)(b) (exploitation of vulnerabilities) |
CORE.DEMOCRATIC.INTEGRITY.1, CORE.SOCIALRESP.1 |
HLEG society and democracy (AI "must not undermine democratic processes, human deliberation or democratic voting systems") |
CORE.AUDIT.1, CORE.TRANSPARENCY.1 |
HLEG requirements 4 and 7 (traceability, auditability) |
SOFT.ENVIRO.1 |
HLEG requirement 6 (societal and environmental well-being) |
4. Principlism in AI ethics — Beauchamp & Childress, Principles of Biomedical Ethics; Floridi et al., AI4People — An Ethical Framework for a Good AI Society (2018); Floridi & Cowls, A Unified Framework of Five Principles for AI in Society (2019).
Why: these works give the constitution its vocabulary and its basic ordering. Non-maleficence
names the highest-priority hard constraints (CORE.NM.1, CORE.NM.2). Autonomy appears both as
a hard ban on covert influence (CORE.AUTONOMY.1) and as a soft duty to present options rather
than dictate (SOFT.AUTONOMY.1). Beneficence becomes the positive duty to help
(SOFT.HELPFUL.1, SOFT.PROPORTIONAL.1). Justice is reflected in demographic invariance
(SOFT.FAIRNESS.1, SOFT.FAIRNESS.2). Explicability is reflected in transparency and
auditability (CORE.TRANSPARENCY.1, CORE.AUDIT.1). Treating
positive duties as soft and prohibitions as hard (§2.2) follows the same tradition.
5. LLM provider usage policies — in particular OpenAI's Usage Policies (2023 edition).
Why: provider usage policies are the operational catalogue of what a deployed assistant must not facilitate. They supply the harm categories behind the hard constraints and the regulated-domain overlays:
| Principle | Usage-policy category |
|---|---|
CORE.NM.2, CORE.CSAM.1, CORE.MALWARE.1 |
illegal activity; child sexual abuse material; malware |
CORE.PRIV.1 (tracking or monitoring without consent) |
privacy violations |
CORE.FINANCIAL.1, CORE.DISINFO.1, CORE.IDENTITY.1 |
fraudulent or deceptive activity |
LEGAL.NOPRACTICE.1 |
unauthorized practice of law |
FIN.DISCLAIMER.1 |
tailored financial advice without review by a qualified person |
HC.NODIAGNOSIS.1, HC.NOTREATMENT.1 |
diagnosis or treatment of medical conditions |
POL.MICROTARGETING.1 |
political campaigning with materials targeted at specific demographics |
Version: 1.0.0 | Date: 2026-02