0b1d9e2b15
The affect console now reconstructs + displays the complete affect-context block
Worldtree assembles into the agent's system prompt — never on any wire, hidden from
regular consumers, surfaced here as the reference-impl's privileged dev view.
- extend build_persona_canon.py to emit mood_directive {occ_directives (15),
pad_band_fallback, salience, pad_band_cutoff, full_only} into the browser canon
(strings were already in the pinned d2-mood-render-canon; regen via Worldtree loader)
- canonPadFallback(pad) + canonEmotionDirective(type): byte-exact mirrors of Worldtree
core/persona/renderer._pad_band_fallback + derive_directive
- renderDirective -> a "CONTEXT INJECTION · reconstructed · hidden from consumers" panel:
mood descriptor [exact] + mood directive [candidate] + relationship directive [exact]
- honest-partial (affect-egress-reference sec 3): affect.emit is type-only (no
intensity), so the salience gate can't be evaluated -> show BOTH the OCC emotion
directive AND the PAD-band fallback with the "injected if intensity >= 0.2" caveat,
never asserting which fires; fallback alone is exact when no dominant_emotion
- vendor + pin affect-egress-consumer-reference.md (tolerate_drift; worldtree-dev
co-signs + pings on change). drift 6/6 green
- contract amended for the new reconstruction fns + honest-partial provenance
Verified: pytest tests/test_web_* (84) + node Playwright (sindra dominant_emotion=joy
-> joy OCC directive candidate + PAD-band fallback both render with exact/candidate tags).
65 lines
3.4 KiB
Python
65 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
||
"""Regenerate src/ratatoskr/web/static/persona_render_canon.json from the vendored
|
||
Worldtree d2 render canons (docs/vendor/worldtree-persona-canon/).
|
||
|
||
The web persona pane renders the CANONICAL affect->NL (mood word + relationship
|
||
directive) BYTE-EXACT to what Worldtree injects into the agent's context. That render
|
||
needs the relation canon parsed into per-band phrase maps; this script reparses the
|
||
vendored raw canons into the flat form the browser JS consumes.
|
||
|
||
Uses Worldtree's OWN loader (core.persona.stance_render.load_canon) as the authoritative
|
||
parser, so the flat form can never drift from Worldtree's parsing semantics. Requires
|
||
Worldtree's venv (pydantic etc.).
|
||
|
||
Run when scripts/canonical_drift.py flags a canon bump:
|
||
PYTHONPATH=~/development/Worldtree ~/development/Worldtree/.venv/bin/python \
|
||
scripts/build_persona_canon.py
|
||
"""
|
||
import json
|
||
from pathlib import Path
|
||
|
||
from core.persona.stance_render import load_canon # Worldtree (authoritative parser)
|
||
|
||
ROOT = Path(__file__).resolve().parent.parent
|
||
VENDOR = ROOT / "docs" / "vendor" / "worldtree-persona-canon"
|
||
OUT = ROOT / "src" / "ratatoskr" / "web" / "static" / "persona_render_canon.json"
|
||
|
||
canon = load_canon(str(VENDOR / "d2-render-canon-v1.json"))
|
||
mood = json.loads((VENDOR / "d2-mood-render-canon-v1.json").read_text())
|
||
out = {
|
||
"_source": "vendored from Worldtree core/persona/canon/{d2-mood-render-canon-v1,d2-render-canon-v1}.json",
|
||
"_generated_by": "scripts/build_persona_canon.py (regen on canonical_drift flag)",
|
||
"_render_path": "deterministic, no LLM; mirrors Worldtree describe_pad + render_d2_canonical byte-exact",
|
||
"mood_grid": mood["describe_pad"]["valence_arousal_grid"],
|
||
# Context-injection reconstruction (affect-egress-consumer-reference §2b/2c): the
|
||
# hidden mood DIRECTIVE. occ_directives = per-OCC-type behavioral string + tier;
|
||
# pad_band_fallback = the P×A-quadrant default when no emotion is salient. The
|
||
# salience gate (emotion_salience) + full_only tiers drive which one fires — but
|
||
# affect.emit is type-only (no intensity), so the consumer shows BOTH candidates.
|
||
"mood_directive": {
|
||
"salience": mood["thresholds"]["emotion_salience"],
|
||
"pad_band_cutoff": mood["thresholds"]["pad_band_cutoff"],
|
||
"full_only": mood["emotion_tiers"]["full_only"],
|
||
"occ_directives": {
|
||
t: {"directive": e["directive"], "tier": e["tier"]}
|
||
for t, e in mood["derive_directive"]["occ_directives"].items()
|
||
},
|
||
"pad_band_fallback": mood["pad_band_fallback"],
|
||
},
|
||
"relation": {
|
||
"trust_cuts": [list(c) for c in canon.trust_cuts],
|
||
"warmth_cuts": [list(c) for c in canon.warmth_cuts],
|
||
"agency_cuts": [list(c) for c in canon.agency_cuts],
|
||
"warmth_phrase": canon.warmth_phrase, "warmth_beh": canon.warmth_beh,
|
||
"agency_phrase": canon.agency_phrase, "agency_beh": canon.agency_beh,
|
||
"history": canon.history,
|
||
"prefix": "Use this graded relationship state: toward target, warmth is ",
|
||
"tbeh": {"low_trust": "verify important claims before relying on them",
|
||
"cold_warmth": "protect boundaries while staying useful",
|
||
"default": "work from ordinary good faith"},
|
||
"cold_warmth_bands": ["distant", "cold", "hostile"], "high_conf_floor": 0.55,
|
||
},
|
||
}
|
||
OUT.write_text(json.dumps(out, indent=1) + "\n")
|
||
print(f"wrote {OUT.relative_to(ROOT)}")
|