- Green make test-fast suite: fixed exemplar corpus issues, proposal validation, atomic state checkpointing (scheme=2), turn-scoped state leakage in ChatRuntime.chat - ADR corpus consolidation: migrated all ADRs to docs/adr/, appended ADR-0225 governance cross-reference anchors to foundational ADRs (0001, 0027-0029, 0055-0057) - Pack definitional closure: fixed en_arithmetic_v1 glosses.jsonl JSON error, updated manifest checksum, marked en_core_syntax_v1 definitional_layer: false
12 KiB
ADR-0027: Identity Packs — Load-Bearing, Swappable, Ratified
Status: Accepted (2026-05-17) — Phases 1–6 complete; Phase 7 (this doc + the operational reference) complete; deep realizer wiring landed under ADR-0028 (2026-05-17).
Author: Joshua Shay + planner pass
Companion docs: docs/identity_packs.md, docs/teaching_order.md, ADR-0010 IdentityManifold (implicit), ADR-0017-agency-scope.md, ADR-0021-epistemic-grade-policy.md
Context
CORE's IdentityManifold (core/physics/identity.py) is already load-bearing in the runtime: PersonaMotor.from_identity_manifold() builds a non-identity CGA motor from a manifold's value_axes; IdentityCheck.check() scores every reasoning trajectory against the manifold; the score feeds into surface context.
But the manifold itself is hardcoded. chat/runtime.py::_default_identity_manifold() constructs three axes (truthfulness, coherence, reverence) inline — no configuration, no swapping, no per-deployment customization. Meanwhile a second identity surface exists in evals/identity_divergence/axes/{axis_a,axis_b}.yaml with a richer descriptive schema (preferences, modal style, hedges) but only drives a mock articulator inside the divergence eval — not the real pipeline.
Three problems follow from the hardcoding:
- Robotics, personalization, and creative-tool builders cannot author identity profiles without editing CORE Python code, defeating the trust boundary that says only reviewed teaching mutates the runtime.
- The identity-divergence eval cannot prove what the user actually experiences, because the mock used in the eval is not the production path.
- The shipping default cannot be deliberate. Today's three axes were chosen by an engineer at line 531 of
runtime.py. They happen to be reasonable, but their authorship has no provenance, no ratification, no review.
The architecturally correct fix is to make the identity manifold the contents of a pack — same as language packs, anchored under packs/identity/ — loaded at runtime, swappable by flag, and (when ratified) carrying a signed MasteryReport from the formation pipeline.
Decision
- Define a content-addressed identity-pack format at
packs/identity/<pack_id>.json. The pack contains the inputs required to construct anIdentityManifold:value_axes(each withaxis_id,name,direction,weight,theological_note),boundary_ids,alignment_threshold, plus pack metadata (pack_id,version,description,mastery_report_sha256optional). - Ship three packs at v1:
identity.default_general_v1— the new shipping default. Initially carries the exact three axes currently hardcoded (truthfulness,coherence,reverence) so the default is a byte-for-byte behavioral no-op for existing users. Free to evolve later by version bump.identity.precision_first_v1— specialization example A, lifted fromaxis_a.yamlsemantics intoValueAxisdirection vectors.identity.generosity_first_v1— specialization example B, lifted fromaxis_b.yaml.
- Replace
_default_identity_manifold()with a loader:packs.identity.loader.load_identity_manifold(pack_id). The default pack id lives incore/config.py(DEFAULT_IDENTITY_PACK = "default_general_v1") so deployments can override without code edits. - Add a
--identity <pack_id>CLI flag tocore pulse,core chat, andcore trace. Robotics / app builders supply ratified packs in their deployment'spacks/identity/overlay; the loader is path-aware. - Identity packs are first-class artifacts under the formation pipeline. Each pack ships with a companion
<pack_id>.mastery_report.jsonproduced by rendering a SubjectSpec through theidentity_anchortemplate, composing, compiling, running, and ratifying. Promote stamps theMasteryReportSHA into the pack'smastery_report_sha256field; loaders verify the seal at load time when present. Unratified packs (development / experimentation) are loadable but taggedratified: falsein the resulting manifold and excluded from production deployments by the runtime's startup gate. - Identity-pack mutation goes through
teaching/review.py(same path ADR-0021 mandates for all pack mutation). Adding a new identity pack topacks/identity/requires a reviewed promote step. The runtime never writes topacks/identity/; the pipeline does, once. - Safety axes are NOT identity packs. A future
packs/identity_safety/core_safety_axes_v1.jsonwill be always loaded alongside whatever identity pack is selected, never replaceable, ratified with the strictest possible adversarial set. That work is scoped to a follow-up ADR; this ADR only establishes the swappable-identity layer.
Consequences
Positive
- Identity is now load-bearing AND falsifiable in the real runtime, not just in the eval mock. The identity-divergence claim can be retested against the production pipeline by swapping packs at the CLI.
- The shipping default has provenance.
identity.default_general_v1is ratified throughidentity_anchortemplate → MasteryReport → signed pack. The choice of axes is auditable. - Downstream consumers get the configurability they need (robotics, personalization, creative tools) without touching CORE Python.
- Hardcoded identity is removed from
chat/runtime.py— one fewer source of un-auditable behavior in the runtime shell. - The trust boundary doctrine extends naturally — identity-pack changes go through the same reviewed teaching path as everything else.
Negative / risks
- Loader becomes a new trust boundary. Identity packs are JSON files read at startup; a malicious pack could declare zero axes (collapsing identity) or extreme directions (skewing every walk). Mitigated by (a) requiring
mastery_report_sha256and self-seal verification in production mode, (b) bounding axis directions and alignment thresholds at load time, (c) refusing to load packs with emptyvalue_axes. - The descriptive schema in
axis_a.yaml/axis_b.yaml(preferences, modal style, hedges) is not preserved by the v1 pack format — those fields don't map ontoValueAxis. They remain useful for the eval-layer mock and as authoring hints for future realizer-side wiring (P3 below); the v1 pack only carries what the runtime can currently consume. core pulse --identity Xwill not yet produce a measurably different surface on every prompt, because the existing realizer doesn't actively differentiate articulation by axis identity — it scores alignment but doesn't, for example, choose hedged vs. affirmative phrasing. The differentiation lives inPersonaMotor.from_identity_manifold(which biases field walks) and inIdentityScore(which feeds surface context). Visible divergence requires wiring axes more deeply into the realizer. This is a known follow-up (P3 below).
Scope limits (explicit non-goals for this ADR)
- P3 — Deep realizer wiring. Making the chosen pack visibly change phrasing (hedged vs. affirmative, narrow vs. broad scope, etc.) requires realizer changes beyond identity loading. This ADR establishes the loading mechanism; deep realizer wiring is a separate ADR.
- Safety axes. Always-loaded, never-replaceable safety axes are a follow-up.
- Identity composition. Multiple-pack overlays (e.g.,
--identity general,domain_medical) are deferred until single-pack wiring is proven. - Cross-language packs. Whether identity packs are language-specific or language-neutral is deferred. v1 packs are language-neutral.
Implementation phases
| Phase | Work | Exit criterion |
|---|---|---|
| 1. Pack format + loader | Define packs/identity/ JSON schema; implement packs.identity.loader.load_identity_manifold(pack_id, *, search_paths=None); bounds checking; helpful errors for missing/malformed packs. |
Loader can construct an IdentityManifold identical to the existing hardcoded one. |
| 2. Author three v1 packs | Author default_general_v1.json, precision_first_v1.json, generosity_first_v1.json. |
Three JSON files committed; each loads cleanly through Phase-1 loader. |
| 3. Replace hardcoded constructor | chat/runtime.py::_default_identity_manifold() calls the loader using core.config.DEFAULT_IDENTITY_PACK. |
All existing runtime / cognition / smoke tests still pass. |
| 4. CLI flag | Add --identity <pack_id> to core pulse (scripts/run_pulse.py) and core chat / core trace. Threaded into the runtime constructor. |
core pulse --identity precision_first_v1 "..." runs without error; identity score reflects different axes. |
| 5. Formation ratification ✅ | Author one SubjectSpec per pack; render through identity_anchor template; compile, run, ratify; write companion <pack_id>.mastery_report.json. Implemented as scripts/ratify_identity_packs.py (idempotent). |
Complete (2026-05-17). All three v1 packs ship with a verified self-sealed MasteryReport: default_general_v1 → 0b77357f…, precision_first_v1 → 5f5000db…, generosity_first_v1 → 91716117…. Loader now defaults to production mode (require_ratified=None); chat runtime no longer passes require_ratified=False. |
| 6. Tests | Pack loader unit tests; round-trip default test (loaded pack ≡ previous hardcoded manifold); CLI smoke test (pulse runs under each pack); divergence smoke test (pulse outputs differ between default and precision_first on a known prompt — even if only via score). | All pass; full formation/cognition/smoke suites still pass. |
| 7. Documentation | docs/identity_packs.md reference; README §Identity Packs paragraph; docs/teaching_order.md Layer 1 cross-reference; memory file. |
Documentation lands in the same PR. |
Alternatives considered
- Leave identity hardcoded; build downstream-customization elsewhere. Rejected: it strands robotics/personalization/creative builders in the "must edit CORE" hole, and leaves the eval-vs-runtime fork unfixed.
- Adopt the
axis_a.yamldescriptive schema as the pack format. Rejected: it carries hedge preferences and modal style that the runtime doesn't yet consume, so most of the file would be dead weight at load time. We can extend the pack format in v2 if and when the realizer learns to use those fields. - Allow identity pack mutation at runtime via the chat surface. Rejected: violates ADR-0021 and the CLAUDE.md Teaching Safety rule that no user text may mutate identity axes or runtime policy.
- Use language packs as identity packs (multi-purpose packs). Rejected: language packs already do enough; combining concerns produces tangled invariants and harder-to-audit drift.
Verification
This ADR is satisfied when:
chat/runtime.pycontains no hardcodedValueAxisinstances.packs/identity/contains three ratified packs.core pulse --identity default_general_v1 "Q"is behaviorally indistinguishable fromcore pulse "Q".core pulse --identity precision_first_v1 "Q"produces a differentidentity_score.deviation_axesthan the default.- The identity-divergence eval can swap mock identities for real ones by referencing pack ids.
- Tests covering all of the above pass in CI.
Governance Cross-Reference (ADR-0225)
This identity-anchor ADR is governed by ADR-0225:
- Safety boundaries: establishes identity pack loading (
packs/identity/loader.py); identity packs are immutable at runtime and isolated from user prompt mutation. - Versor closure: identity manifolds preserve exact geometric orientation invariants (
versor_condition(F) < 1e-6). - Reconstruction-over-storage: identity axes are loaded into structured runtime manifolds from verified pack checksums.
- Replay-equivalence: identity pack evaluation is deterministic and exact across runs.
- Mutation standing: identity axes cannot be altered by conversational interaction or unreviewed teaching proposals.