Skip to content

Latest commit

 

History

History
609 lines (473 loc) · 30.7 KB

constitution.md

File metadata and controls

609 lines (473 loc) · 30.7 KB

MoralStack Constitution

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).


0. Loading and validation (engineering)

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.


1. Overview

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.

1.1 Design philosophy

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

2. Principle structure

2.1 Principle format

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

2.2 Constraint levels

Hard constraints

  • Non-negotiable: violation = immediate refusal
  • Priority: typically 85-100
  • Examples: physical harm prevention, illegal activity, child protection

Soft norms

  • Negotiable: violation = caveat or revision
  • Priority: typically 30-70
  • Examples: communicative tone, balanced perspectives, clarity

Choosing the level (added 2026-08-13)

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:

  1. 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 — usually soft. Emergencies are the deliberate exception (EMER.*, MH.CRISIS.1, CHILD.*): the duty is positive but the residual harm is grave, so hard is intentional.
  2. Does one principle carry two obligations? A rule that both forbids a behavior and requires a disclaimer should be split, with the prohibition kept hard and the disclaimer moved to a soft sibling. The reference pattern is LEGAL.NOPRACTICE.1 (hard) alongside LEGAL.DISCLAIMER.1 (soft); the same split was applied on 2026-08-13 to MH.NOTTHERAPY.1 / MH.DISCLAIMER.1 and to EDU.MINORCONTENT.1 / EDU.AGEAPPROPRIATE.1.

What the critic actually reads (measured 2026-08-13)

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:

  1. Only the first max_rule_len characters of rule are 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 them hard) 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 of VC.OPERATIONAL.1 left 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.
  2. examples_allow / examples_deny never 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 in resolve_conflict. And schema.py:141-142 keeps 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 configurable (2026-08-17)

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.


3. Core principles

3.1 Hard constraints (Non-negotiable)

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

3.2 Soft norms (Negotiable)

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.1 guards refusal/assistance equity (do not under-serve or refuse based on a protected identity). SOFT.FAIRNESS.2 guards 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.2 therefore 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.2 discourages gratuitous profanity, slurs, and demeaning language. Like SOFT.FAIRNESS.2, it is a soft norm that can never set violated_hard and, 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_PROMPT in orchestration/_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.


4. Domain overlays

Overlays allow customizing the constitution for specific domains.

4.1 Available overlays

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

4.2 Overlay structure

# 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..."

4.3 Property sensitive

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: true is active, the Controller applies a floor to risk_score of 0.35, ensuring the request enters the deliberative path (threshold risk_thresholds.low = 0.3).
  • CYCLES_EXHAUSTED fallback: if deliberation exhausts cycles without converging and the decision is NORMAL_COMPLETE, the system forces SAFE_COMPLETE (with reason code cycles_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.

4.4 Property excluded (domain exclusion)

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_domain is still reused (e.g. in refusals) so there is no extra LLM cost when exclusion is not used.

How to exclude a domain:

  1. Edit the overlay YAML for that domain, e.g. moralstack/constitution/data/overlays/political.yaml.

  2. Add (or set) excluded: true at the top level, e.g.:

    description: "Politics, government..."
    sensitive: true
    excluded: true
  3. 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.

  4. To re-enable the domain, remove excluded: true or set excluded: 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.

5. Conflict resolution

When multiple principles apply and conflict:

5.1 Priority rules

  1. Hard > Soft: Hard constraints always before soft norms
  2. Priority: At same level, higher priority wins
  3. Specificity: At same priority, more specific principle wins
  4. 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.

5.2 Algorithm

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
        )
    )

6. Constitution Store API

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.

6.1 Main interface

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)

6.2 Domain detection and retrieval

# 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)

7. Extension

7.1 Adding core principles

Edit moralstack/constitution/data/core.yaml:

principles:
  - id: "CORE.NEW.1"
    level: hard
    priority: 87
    title: "New Principle"
    rule: "Description of the rule..."
    # ...

7.2 Creating a new overlay

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

8. Best practices

8.1 For developers

  1. Do not modify hard constraints without thorough review
  2. Test overlays with representative prompt suites
  3. Document rationale for new principles
  4. Keep allow/deny examples up to date

8.2 For operators

  1. Select appropriate overlay for the use case
  2. Monitor trigger rates to identify over/under-refusal
  3. Collect feedback to refine principles
  4. Periodic review of soft principles

9. References

9.1 Inspirational sources

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