core/docs/identity_packs.md
Shay 1574a4b030 feat(identity-packs): ADR-0028 — pack-driven hedge & claim-strength shaping
Closes the 'identity is load-bearing but not visibly differentiated'
gap noted at the end of ADR-0027.  Pack swap now produces visibly
different surfaces on identical trajectories at the same alignment.

Schema bump — packs gain an optional 'surface_preferences' block:

  hedge_threshold_strong, hedge_threshold_soft  → band entries
  preferred_hedge_strong, preferred_hedge_soft  → phrases per band
  claim_strength                                → balanced|qualified|affirmative
  qualified_band_high, preferred_qualifier      → marginal-band shaping

Loader enforces threshold ordering (strong <= soft <= qual_high),
phrase length bounds, and the enum-of-three for claim_strength.
Missing block resolves to defaults that reproduce pre-ADR behavior
byte-for-byte; existing tests pass unchanged.

Algorithm (deterministic, surface-only, no sampling/repair/normalize):

  alignment < strong              → preferred_hedge_strong + lower-cased surface
  alignment < soft                → preferred_hedge_soft + lower-cased surface
  soft <= alignment < qual_high
    and claim_strength=qualified  → preferred_qualifier + lower-cased surface
  otherwise                       → bare surface

Three v1 pack profiles:

  default_general_v1   balanced; 0.40 / 0.50 / 0.75 ; 'It seems that' / 'Perhaps'
  precision_first_v1   qualified; 0.55 / 0.70 / 0.85 ; 'Arguably,' / 'In some cases,' / 'Under certain conditions,'
  generosity_first_v1  affirmative; 0.20 / 0.30 / 0.50 ; default hedge phrases

Re-ratified.  New MasteryReport SHAs (superseding Phase-5):

  default_general_v1   → ddc1ba127231272660e6a435e177227558461b0278572a95635b416c3e1dec5a
  precision_first_v1   → cb5fb2323214a26afda33f2a67e22f38fe49f4763829d48ef67fd41241aba33c
  generosity_first_v1  → 94f2f49e1b16c7498fb52b8f9864eecc198618933dc8381a01b809c146826db7

Files touched:

* core/physics/identity.py — new SurfacePreferences dataclass;
  IdentityManifold gains 'surface_preferences' field with defaults.
* packs/identity/loader.py — _build_surface_preferences() parses,
  bounds-checks (threshold ordering, claim_strength enum, phrase
  length, threshold ranges); SurfacePreferences round-trips.
* generate/surface.py — SurfaceContext gains 7 new fields with defaults
  matching the pre-ADR module-level HEDGE_STRONG_THRESHOLD /
  HEDGE_SOFT_THRESHOLD; _apply_hedge takes the full context and
  implements the four-band algorithm; module-level constants retained
  for back-compat.
* chat/runtime.py — _build_surface_context lifts manifold.surface_preferences
  into SurfaceContext.
* packs/identity/*.json — three v1 packs gain surface_preferences blocks
  tuned to their roles; re-ratified via scripts/ratify_identity_packs.py
  (idempotent).
* tests/test_identity_surface_divergence.py — 15 tests covering hedge
  bands, claim_strength bands, pack-swap divergence proof, and runtime
  context wiring.

Suite status: cognition 121, teaching 17, runtime 19, formation 182,
smoke 67 — all green.  test_identity_packs.py 23/23, new
test_identity_surface_divergence.py 15/15.

Docs: ADR-0028 (Accepted) records the decision and verification; ADR-0027
status updated to point to ADR-0028 for deep realizer wiring; README
§Identity Packs notes the visible divergence; docs/identity_packs.md
gains a §Surface preferences section and closes the known-limit #1
about invisible surface differentiation.
2026-05-17 19:42:54 -07:00

185 lines
11 KiB
Markdown

# Identity Packs — Reference
**Status:** Operational reference doctrine. Update when pack format, loader contract, or CLI flag semantics change.
**Last updated:** 2026-05-17
**Companion docs:** [`decisions/ADR-0027-identity-packs.md`](decisions/ADR-0027-identity-packs.md), [`teaching_order.md`](teaching_order.md), [`runtime_contracts.md`](runtime_contracts.md)
## What an identity pack is
An identity pack is the on-disk, content-addressed representation of an `IdentityManifold`. At runtime startup, CORE loads exactly one identity pack and uses it to construct the manifold that drives `PersonaMotor.from_identity_manifold()` and `IdentityCheck`. Replacing the pack replaces the model's identity surface without touching code.
Identity packs sit alongside language packs in the trust hierarchy:
- **Language packs** (`packs/en/`, `packs/grc/`, `packs/he/`, …) — what CORE *speaks*.
- **Identity packs** (`packs/identity/<pack_id>.json`) — *who* CORE is while speaking.
- **Safety packs** (future, `packs/identity_safety/`) — what CORE will *never* be, regardless of identity pack.
## Pack format (v1)
A single JSON file. Strings, ints, bools, lists, dicts only — same canonical-JSON discipline as the formation pipeline (no floats embedded in identifying fields; numeric direction vectors are floats but their canonical position in the file is fixed).
```json
{
"pack_id": "default_general_v1",
"version": "1.0.0",
"description": "Balanced general identity. Default shipping pack.",
"schema_version": "1.0.0",
"mastery_report_sha256": "",
"alignment_threshold": 0.45,
"boundary_ids": [
"no_fabricated_source",
"no_hot_path_repair"
],
"value_axes": [
{
"axis_id": "truthfulness",
"name": "truthfulness",
"direction": [1.0, 0.0, 0.0],
"weight": 1.0,
"theological_note": "Truth is treated as a fixed value axis, not a prompt preference."
},
{
"axis_id": "coherence",
"name": "coherence",
"direction": [0.0, 1.0, 0.0],
"weight": 1.0,
"theological_note": "Operations must preserve field coherence under propagation."
},
{
"axis_id": "reverence",
"name": "reverence",
"direction": [0.0, 0.0, 1.0],
"weight": 1.0,
"theological_note": "Depth-language handling remains bounded by source structure."
}
]
}
```
### Field semantics
| Field | Required | Meaning |
|---|---|---|
| `pack_id` | yes | Unique identifier. Convention: `<slug>_v<major>`. |
| `version` | yes | Semver. Bumping `major` produces a new `pack_id`. |
| `description` | yes | Human-facing one-liner. Surfaces in `core pulse --list-identity-packs`. |
| `schema_version` | yes | Format version. Currently `"1.0.0"`. |
| `surface_preferences` | no | Pack-supplied surface hedge / claim-strength shaping (ADR-0028). Defaults preserve pre-ADR behavior. See §"Surface preferences" below. |
| `mastery_report_sha256` | no | SHA of the companion `<pack_id>.mastery_report.json`. Empty for unratified development packs; production deployments refuse to load packs with empty values. |
| `alignment_threshold` | yes | Float in [0, 1]. Passed to `IdentityManifold.alignment_threshold`. |
| `boundary_ids` | yes | List of boundary identifiers. Mirrors `IdentityManifold.boundary_ids`. |
| `value_axes` | yes | List of ≥ 1 axes. Each has: `axis_id`, `name`, `direction` (list of 3 floats in [-1, 1]), `weight` (float ≥ 0), `theological_note`. |
### Surface preferences (ADR-0028)
Optional block driving the assembler's hedge and claim-strength decisions:
```json
"surface_preferences": {
"hedge_threshold_strong": 0.40,
"hedge_threshold_soft": 0.50,
"preferred_hedge_strong": "It seems that",
"preferred_hedge_soft": "Perhaps",
"claim_strength": "balanced",
"qualified_band_high": 0.75,
"preferred_qualifier": "In some cases,"
}
```
Bands (in descending hedge strength):
1. `alignment < hedge_threshold_strong` → prepend `preferred_hedge_strong`.
2. `alignment < hedge_threshold_soft` → prepend `preferred_hedge_soft`.
3. `hedge_threshold_soft <= alignment < qualified_band_high` and `claim_strength == "qualified"` → prepend `preferred_qualifier`.
4. Otherwise leave the assertion bare.
Threshold ordering required: `hedge_threshold_strong <= hedge_threshold_soft <= qualified_band_high`. Loader enforces this.
`claim_strength` must be one of `{"balanced", "qualified", "affirmative"}`. `"balanced"` and `"affirmative"` skip the marginal-band qualifier; only `"qualified"` triggers it.
### Loader bounds (enforced)
- `len(value_axes) >= 1` — empty axes are refused.
- Each `direction` must have length 3 and each component in `[-1.0, 1.0]`.
- `weight` must be in `[0.0, 10.0]` — prevents a single axis from dominating arbitrarily.
- `alignment_threshold` must be in `[0.0, 1.0]`.
- `axis_id` values must be unique within a pack.
- Production mode requires `mastery_report_sha256 != ""` and the companion report's self-seal to verify; development mode (`CORE_ALLOW_UNRATIFIED_IDENTITY=1`) bypasses both.
## Loader contract
```python
from packs.identity.loader import load_identity_manifold
manifold = load_identity_manifold(
pack_id="default_general_v1", # required
search_paths=None, # default: ["./packs/identity"]
require_ratified=True, # production default
)
```
Returns an `IdentityManifold` (from `core/physics/identity.py`). Raises `IdentityPackError` on missing pack, malformed JSON, bound violations, or unverified self-seal in production mode.
The loader is path-aware: deployments may supply `search_paths=("/srv/myapp/packs/identity", "./packs/identity")` so a robotics or app builder can ship overlay packs without touching CORE's own packs directory.
## CLI usage
```bash
core pulse "What is truth?"
# Loads default identity (currently default_general_v1).
core pulse --identity precision_first_v1 "What is truth?"
# Loads a specific pack. Pack must exist on the loader's search paths.
core pulse --list-identity-packs
# Lists discoverable packs with description + ratification status.
core chat --identity generosity_first_v1
# Same flag, applies to the chat surface.
CORE_DEFAULT_IDENTITY_PACK=precision_first_v1 core pulse "..."
# Environment override of the default. Takes precedence over the
# core/config.py constant; --identity on the command line takes
# precedence over the env var.
```
## Shipping packs (v1)
| Pack id | Role | Notes |
|---|---|---|
| `default_general_v1` | Ship default. Balanced. | Encodes the *exact* three axes (`truthfulness`, `coherence`, `reverence`) previously hardcoded in `chat/runtime.py`. Behavioral no-op vs. pre-ADR runtime. ADR-0028 surface_preferences: balanced; hedge thresholds 0.40/0.50/0.75. Ratified: `ddc1ba127231272660e6a435e177227558461b0278572a95635b416c3e1dec5a`. |
| `precision_first_v1` | Specialization example A. | Boosts `truthfulness` weight, narrows reverence direction. Surface: hedges sooner (0.55/0.70/0.85), uses "Arguably,"/"In some cases,"/"Under certain conditions,"; claim_strength=qualified. Source: `evals/identity_divergence/axes/axis_a.yaml`. Ratified: `cb5fb2323214a26afda33f2a67e22f38fe49f4763829d48ef67fd41241aba33c`. |
| `generosity_first_v1` | Specialization example B. | Boosts `coherence` weight, broadens reverence direction. Surface: hedges later (0.20/0.30/0.50); claim_strength=affirmative. Source: `evals/identity_divergence/axes/axis_b.yaml`. Ratified: `94f2f49e1b16c7498fb52b8f9864eecc198618933dc8381a01b809c146826db7`. |
Each ratified pack ships alongside a `<pack_id>.mastery_report.json` companion file. The loader, in production mode, verifies the companion's self-seal and cross-checks its `report_sha256` against the pack's `mastery_report_sha256`. To re-ratify after editing a pack's axes, run `python scripts/ratify_identity_packs.py` (idempotent — re-running on already-current packs is a no-op).
## Authoring a new identity pack (robotics / personalization / creative tools)
1. **Author the SubjectSpec.** Use `core formation new <subject_id>` to scaffold; edit to declare the pack's intent and identity axis constraints.
2. **Hand-author the candidate axes.** Use the `identity_anchor` template's expected input shape: `concepts` are axes (with `definition` = behavioral commitment), `counters` are override-attempt probes the pack must refuse.
3. **Ratify through formation.** Render → compose → compile → run → ratify. Produces a signed `MasteryReport`.
4. **Promote.** Promotion goes through `teaching/review.py`'s reviewed-apply path. The promote step writes both `<pack_id>.json` and `<pack_id>.mastery_report.json` to `packs/identity/`.
5. **Deploy.** The pack is now selectable by `--identity <pack_id>`. Distribute alongside your deployment's other artifacts.
### Anti-patterns
- **Don't author identity packs by hand-editing `packs/identity/`.** The runtime never writes there; neither should authors. All packs flow through formation so audit trails are intact.
- **Don't ship unratified packs (empty `mastery_report_sha256`) in production.** The loader's `require_ratified` flag exists to refuse them.
- **Don't try to override `boundary_ids` to weaken refusal.** Boundaries are the immutable contract; if your identity pack omits expected boundaries, the runtime refuses to load it.
- **Don't try to express safety constraints in an identity pack.** Safety axes belong in the (future) safety pack, always-loaded and never-replaceable.
## Known limits (read before designing around)
1. ~~**Identity does not yet visibly differentiate articulation at the realizer.**~~ **Closed by [ADR-0028](decisions/ADR-0028-identity-surface-wiring.md) (2026-05-17).** Pack `surface_preferences` now flow into the assembler's hedge and claim-strength decisions. `core chat --identity precision_first_v1 "Q"` produces a visibly different surface than the default pack on the same prompt at the same alignment. Hedging is English-only at v1; depth-language hedging is a future ADR.
2. **One pack at a time.** Multi-pack overlays (`--identity general,domain_medical`) are deferred to a follow-up ADR.
3. **No language-specific identity yet.** Packs are language-neutral. Per-language identity is a future concern.
4. **Safety axes are still in `chat/runtime.py`.** Once the safety pack ADR lands, safety boundaries will move out of `boundary_ids` and into a separately-loaded safety pack.
## Cross-reference index
- Pack format spec: this doc §"Pack format (v1)".
- Loader contract: this doc §"Loader contract".
- Decision record: [ADR-0027](decisions/ADR-0027-identity-packs.md).
- Teaching-order placement: [`teaching_order.md`](teaching_order.md) §"The Five-Layer Ordering Rule" Layer 1.
- Identity-divergence eval: `evals/identity_divergence/contract.md`.
- The geometric identity primitives: `core/physics/identity.py` (ADR-0010 implicit).
- The formation template that ratifies packs: `formation/templates/identity_anchor.py`.