From e441cf33f461b76ac9ad9947a18e4ba6ee25fce3 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 10 Jun 2026 20:45:25 +0100 Subject: [PATCH] Add Persona culture stack golf harness --- .gitignore | 1 + data/cultural-ontology/README.md | 17 + data/cultural-ontology/catalog.yaml | 28 + .../cultures/dutch-american-diaspora.yaml | 58 ++ .../portugal-resident-algarve-lisbon.yaml | 84 +++ .../texas-houston-us-adolescence.yaml | 56 ++ .../western-europe-maritime-childhood.yaml | 53 ++ ...gamecult-open-source-crypto-anarchist.yaml | 51 ++ .../online-game-dev-discord-pre2025.yaml | 288 +++++++++ .../en-US-metacrat-casual-pre2025.yaml | 546 ++++++++++++++++++ .../metame-casual-pre2025/README.md | 37 +- .../metame-casual-pre2025/lowering-prompt.md | 14 +- .../metame-cultural-stack.yaml | 27 + .../persona-interlingua-prompt.md | 42 ++ .../persona-state-ref.yaml | 4 + .../weksa-lowering-prompt.md | 41 ++ scripts/run-metame-persona-golf.mjs | 496 ++++++++++++++++ 17 files changed, 1830 insertions(+), 13 deletions(-) create mode 100644 data/cultural-ontology/README.md create mode 100644 data/cultural-ontology/catalog.yaml create mode 100644 data/cultural-ontology/cultures/dutch-american-diaspora.yaml create mode 100644 data/cultural-ontology/cultures/portugal-resident-algarve-lisbon.yaml create mode 100644 data/cultural-ontology/cultures/texas-houston-us-adolescence.yaml create mode 100644 data/cultural-ontology/cultures/western-europe-maritime-childhood.yaml create mode 100644 data/cultural-ontology/subcultures/gamecult-open-source-crypto-anarchist.yaml create mode 100644 data/cultural-ontology/subcultures/online-game-dev-discord-pre2025.yaml create mode 100644 data/target-language-ontology/en-US-metacrat-casual-pre2025.yaml create mode 100644 examples/persona-lowering/metame-casual-pre2025/metame-cultural-stack.yaml create mode 100644 examples/persona-lowering/metame-casual-pre2025/persona-interlingua-prompt.md create mode 100644 examples/persona-lowering/metame-casual-pre2025/weksa-lowering-prompt.md create mode 100644 scripts/run-metame-persona-golf.mjs diff --git a/.gitignore b/.gitignore index f3a82c5..3d8b18d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ state/scratch-compaction-*.md state/scratch-runtime/ .weksa-runs/ .weksa-smoke/ +examples/persona-lowering/*/runs/ node_modules/ dist/ build/ diff --git a/data/cultural-ontology/README.md b/data/cultural-ontology/README.md new file mode 100644 index 0000000..e144b8b --- /dev/null +++ b/data/cultural-ontology/README.md @@ -0,0 +1,17 @@ +# Cultural Ontology Store + +This directory holds draft Weksa cultural and subcultural ontology profiles used +for ontological lowering, syntax, grammar, discourse, and surface-affordance +selection. + +Live pipeline shape: + +1. Weksa stores reusable cultures/subcultures here and in semantic retrieval + storage such as Qdrant. +2. A Persona state references the relevant culture stack. +3. The Persona produces interlingua intent from current state and situation. +4. Weksa lowers that interlingua through the referenced culture stack. + +Persona state owns current intent, memory pressure, and relationship stance. +Cultural ontology owns reusable salience, affordances, and constraints. Qdrant +may retrieve candidates, but accepted ontology remains typed state. diff --git a/data/cultural-ontology/catalog.yaml b/data/cultural-ontology/catalog.yaml new file mode 100644 index 0000000..e553206 --- /dev/null +++ b/data/cultural-ontology/catalog.yaml @@ -0,0 +1,28 @@ +schema_version: weksa.cultural_ontology_catalog.v0 +catalog_id: weksa.cultural_ontology.catalog.v0 +status: draft +qdrant: + intended_collection: weksa_cultural_ontology_profiles + payload_keys: + - profile_id + - kind + - language_codes + - regions + - evidence_refs + - status + ownership_note: > + Qdrant owns retrieval over culture/subculture chunks. It does not own + accepted ontology, activation, or lowering authority. +profiles: + - profile_id: weksa.culture.dutch_american_diaspora.v0 + path: data/cultural-ontology/cultures/dutch-american-diaspora.yaml + - profile_id: weksa.culture.western_europe_maritime_childhood.v0 + path: data/cultural-ontology/cultures/western-europe-maritime-childhood.yaml + - profile_id: weksa.culture.portugal_resident_algarve_lisbon.v0 + path: data/cultural-ontology/cultures/portugal-resident-algarve-lisbon.yaml + - profile_id: weksa.culture.texas_houston_us_adolescence.v0 + path: data/cultural-ontology/cultures/texas-houston-us-adolescence.yaml + - profile_id: weksa.subculture.online_game_dev_discord_pre2025.v0 + path: data/cultural-ontology/subcultures/online-game-dev-discord-pre2025.yaml + - profile_id: weksa.subculture.gamecult_open_source_crypto_anarchist.v0 + path: data/cultural-ontology/subcultures/gamecult-open-source-crypto-anarchist.yaml diff --git a/data/cultural-ontology/cultures/dutch-american-diaspora.yaml b/data/cultural-ontology/cultures/dutch-american-diaspora.yaml new file mode 100644 index 0000000..4c46b64 --- /dev/null +++ b/data/cultural-ontology/cultures/dutch-american-diaspora.yaml @@ -0,0 +1,58 @@ +schema_version: weksa.cultural_ontology_profile.v0 +profile_id: weksa.culture.dutch_american_diaspora.v0 +kind: diaspora_identity +status: draft_guarded +language: + codes: + - en + - nl +community: + regions: + - Netherlands + - United States + speech_community: > + People whose self-description or life history crosses Dutch origin and + American nationality/residence. This profile is not a dialect claim. +activation: + allowed_when: + - persona_state_references_profile + - interlingua_mentions_origin_nationality_or_cross_cultural_identity + forbidden_when: + - used_to_infer_accent_or_fluency_without_evidence + - used_as_generic_dutch_or_generic_american_stereotype +salience_axes: + plural_national_identity: + axis_id: plural_national_identity + domain: identity_indexing + description: Birthplace, citizenship, residence, and cultural identification may diverge. + relevant_distinctions: + - distinction_id: birthplace_vs_citizenship + label: Birthplace versus citizenship + meaning: Being born in one country and holding identity/citizenship in another are separate ontology fields. + lowering_questions: + - Does the utterance need origin, nationality, residence, or cultural affiliation? + required_when: + - source_mentions_identity_origin_or_nationality + forbidden_when: + - scene_only_requires_casual_banter + evidence_refs: + - discord:805822798185627718 + - discord:1030577958215225396 +evidence_ledger: + - ref: discord:805822798185627718 + quote: "I live in Portugal at the moment, though I am a Netherlands-born US citizen - lived mostly in Texas" + - ref: discord:1030577958215225396 + quote: "Dual nationality, Dutch American" + - ref: discord:862490040755683348 + quote: "I was born in the Netherlands but I'm a US citizen and did high school in Houston, Texas." +linguistic_affordances: + grammar_features: [] + phonology_features: [] + lexicon_features: [] + discourse_patterns: [] + orthography_features: [] + code_switching: + allowed: false + constraints: + - Do not infer Dutch code switching from diaspora identity alone. + patterns: [] diff --git a/data/cultural-ontology/cultures/portugal-resident-algarve-lisbon.yaml b/data/cultural-ontology/cultures/portugal-resident-algarve-lisbon.yaml new file mode 100644 index 0000000..0722576 --- /dev/null +++ b/data/cultural-ontology/cultures/portugal-resident-algarve-lisbon.yaml @@ -0,0 +1,84 @@ +schema_version: weksa.cultural_ontology_profile.v0 +profile_id: weksa.culture.portugal_resident_algarve_lisbon.v0 +kind: regional_residence +status: draft_guarded +language: + codes: + - en + - pt +community: + regions: + - Portugal + - Algarve + - Lisbon + - Tavira + speech_community: > + Portugal as lived residence and locality rather than tourist backdrop. +activation: + allowed_when: + - persona_state_references_profile + - interlingua_mentions_portugal_residence_locality_bureaucracy_or_timing + forbidden_when: + - used_for_broad_portuguese_culture_without_locale_review +salience_axes: + lived_portugal: + axis_id: lived_portugal + domain: identity_indexing + description: Portugal references can be local/home references, including Algarve/Lisbon/Tavira specificity. + relevant_distinctions: + - distinction_id: current_or_returned_residence + label: Current or returned residence + meaning: Portugal can be present-tense home context, not childhood-only memory. + lowering_questions: + - Is Portugal being invoked as current location, childhood location, or comparison frame? + required_when: + - source_mentions_portugal_or_portuguese_residence + forbidden_when: + - scene_has_no_place_salience + evidence_refs: + - discord:581220303414231117 + - discord:834858944013926421 + - discord:888820124344254504 +evidence_ledger: + - ref: discord:581220303414231117 + quote: "I'm in the Algarve now" + - ref: discord:834858944013926421 + quote: "I live in Portugal now" + - ref: discord:888820124344254504 + quote: "But I'm currently in Lisbon" + - ref: discord:1023774733789040640 + quote: "if you wanna move to Portugal you're gonna have to get used to not getting things on time" +linguistic_affordances: + grammar_features: [] + phonology_features: [] + lexicon_features: [] + discourse_patterns: + - pattern_id: portugal_timing_joke_guarded + label: Portugal timing joke, guarded + status: draft + domain: + layer: discourse + category: humor + subcategory: local_bureaucracy_timing + interactional_function: locally_grounded_timing_joke + sequence_shape: + - playful_warning + - local_timing_generalization + adjacency_constraints: + - addressee_is_discussing_moving_or_living_in_portugal + activation: + allowed_when: + - local_portugal_residence_context + - joking_register + forbidden_when: + - formal_cultural_description + evidence: + source_refs: + - discord:1023774733789040640 + confidence: low + orthography_features: [] + code_switching: + allowed: false + constraints: + - This profile alone does not authorize Portuguese code switching. + patterns: [] diff --git a/data/cultural-ontology/cultures/texas-houston-us-adolescence.yaml b/data/cultural-ontology/cultures/texas-houston-us-adolescence.yaml new file mode 100644 index 0000000..09d5619 --- /dev/null +++ b/data/cultural-ontology/cultures/texas-houston-us-adolescence.yaml @@ -0,0 +1,56 @@ +schema_version: weksa.cultural_ontology_profile.v0 +profile_id: weksa.culture.texas_houston_us_adolescence.v0 +kind: adolescence_residence +status: draft_guarded +language: + codes: + - en +community: + regions: + - United States + - Texas + - Houston + speech_community: > + US/Texas adolescent institutional context, especially Houston high school + and later critique of American debt/institutional pathways. +activation: + allowed_when: + - persona_state_references_profile + - interlingua_mentions_us_texas_school_debt_or_american_institutions + forbidden_when: + - used_to_force_texan_dialect_markers +salience_axes: + american_institutional_distance: + axis_id: american_institutional_distance + domain: stance + description: US institutions can be lived experience and object of critique rather than foreign abstraction. + relevant_distinctions: + - distinction_id: debt_train_refusal + label: American debt train refusal + meaning: Avoiding US education/debt pathways can be a first-person institutional stance. + lowering_questions: + - Is the utterance about education, debt, leaving the US, or institutional pressure? + required_when: + - source_mentions_us_debt_or_school_path + forbidden_when: + - casual_fixture_without_institutional_topic + evidence_refs: + - discord:1160260490690306148 +evidence_ledger: + - ref: discord:805822798185627718 + quote: "lived mostly in Texas" + - ref: discord:862490040755683348 + quote: "did high school in Houston, Texas" + - ref: discord:1160260490690306148 + quote: "I specifically left the US after high school so as not to climb onto the traditional American debt train" +linguistic_affordances: + grammar_features: [] + phonology_features: [] + lexicon_features: [] + discourse_patterns: [] + orthography_features: [] + code_switching: + allowed: false + constraints: + - Do not infer Southern or Texan dialect surface markers from residence alone. + patterns: [] diff --git a/data/cultural-ontology/cultures/western-europe-maritime-childhood.yaml b/data/cultural-ontology/cultures/western-europe-maritime-childhood.yaml new file mode 100644 index 0000000..39d4cbc --- /dev/null +++ b/data/cultural-ontology/cultures/western-europe-maritime-childhood.yaml @@ -0,0 +1,53 @@ +schema_version: weksa.cultural_ontology_profile.v0 +profile_id: weksa.culture.western_europe_maritime_childhood.v0 +kind: mobility_childhood +status: draft_guarded +community: + regions: + - France + - Spain + - Portugal + - coastal_western_europe + speech_community: > + Mobile childhood organized around sailing, ports, and cross-border Western + European movement. This profile is owner-attested for the Metame fixture and + needs more Discord corroboration before broader use. +activation: + allowed_when: + - persona_state_references_profile + - interlingua_mentions_childhood_origin_mobility_boats_or_home + forbidden_when: + - used_as_decorative_nautical_metaphor_without_context +salience_axes: + mobile_home: + axis_id: mobile_home + domain: identity_indexing + description: Home can be vessel, coast, port, family movement, and country sequence rather than one fixed town. + relevant_distinctions: + - distinction_id: vessel_as_childhood_home + label: Vessel as childhood home + meaning: A sailboat childhood makes movement and locality primary ontology fields. + lowering_questions: + - Does the line invoke home, travel, borders, coast, or rootedness? + required_when: + - source_mentions_childhood_or_home + forbidden_when: + - scene_has_no_mobility_or_home_salience + evidence_refs: + - owner:2026-06-10:sailboat_france_spain_portugal_until_6 +evidence_ledger: + - ref: owner:2026-06-10:sailboat_france_spain_portugal_until_6 + quote: "I was born in the Netherlands, grew up on a sailboat traveling France, Spain, staying in Portugal until I was 6, moved to the US." + - ref: discord-search-gap:sailboat_childhood + summary: First semantic search did not locate a direct Discord message for this exact childhood account. +linguistic_affordances: + grammar_features: [] + phonology_features: [] + lexicon_features: [] + discourse_patterns: [] + orthography_features: [] + code_switching: + allowed: false + constraints: + - Do not infer French, Spanish, Portuguese, or Dutch fluency from movement alone. + patterns: [] diff --git a/data/cultural-ontology/subcultures/gamecult-open-source-crypto-anarchist.yaml b/data/cultural-ontology/subcultures/gamecult-open-source-crypto-anarchist.yaml new file mode 100644 index 0000000..6af5f41 --- /dev/null +++ b/data/cultural-ontology/subcultures/gamecult-open-source-crypto-anarchist.yaml @@ -0,0 +1,51 @@ +schema_version: weksa.cultural_ontology_profile.v0 +profile_id: weksa.subculture.gamecult_open_source_crypto_anarchist.v0 +kind: political_technical_community +status: draft_guarded +language: + codes: + - en +community: + regions: + - online + speech_community: > + GameCult-adjacent open-source, crypto-anarchist, game-development, and + engineering practice where ownership, inspectability, debt, authority, and + tool coherence are culturally salient. +activation: + allowed_when: + - persona_state_references_profile + - interlingua_mentions_tools_authority_open_source_debt_game_dev_or_engineering + forbidden_when: + - used_to_turn_random_banter_into_manifesto +salience_axes: + authority_and_tool_coherence: + axis_id: authority_and_tool_coherence + domain: stance + description: Systems are judged by ownership, inspectability, agency, and whether they actually work. + relevant_distinctions: + - distinction_id: open_source_anti_custody + label: Open source anti-custody + meaning: Open access and inspectability are political and practical values. + lowering_questions: + - Is the line judging a tool, institution, ownership structure, or coordination method? + - Should the interlingua preserve anti-custody or open-access salience? + required_when: + - source_mentions_open_source_or_institutional_authority + forbidden_when: + - casual_social_move_only + evidence_refs: + - discord:862490040755683348 +linguistic_affordances: + grammar_features: [] + phonology_features: [] + lexicon_features: [] + discourse_patterns: [] + orthography_features: [] + code_switching: + allowed: false + constraints: [] + patterns: [] +evidence_ledger: + - ref: discord:862490040755683348 + quote: "I got my bachelor degree in game development last year. I'm a crypto-anarchist open source activist" diff --git a/data/cultural-ontology/subcultures/online-game-dev-discord-pre2025.yaml b/data/cultural-ontology/subcultures/online-game-dev-discord-pre2025.yaml new file mode 100644 index 0000000..42ee8f7 --- /dev/null +++ b/data/cultural-ontology/subcultures/online-game-dev-discord-pre2025.yaml @@ -0,0 +1,288 @@ +schema_version: weksa.cultural_ontology_profile.v0 +profile_id: weksa.subculture.online_game_dev_discord_pre2025.v0 +kind: community_of_practice +status: draft_guarded +language: + codes: + - en +community: + regions: + - online_discord + speech_community: > + Pre-2025 GameCult/Aquarium-ish Discord collaboration where game design, + sound, engineering, jokes, and casual status play cohabit the same rooms. +activation: + allowed_when: + - persona_state_references_profile + - target_medium_is_discord + - target_period_is_pre_2025_or_history_fixture + forbidden_when: + - formal_current_project_doctrine_is_target +salience_axes: + casual_room_temperature: + axis_id: casual_room_temperature + domain: discourse_patterns + description: Compact, socially alive wording that assumes shared room context. + relevant_distinctions: + - distinction_id: banter_over_explanation + label: Banter over explanation + meaning: The line may be short, fragmentary, and joke-shaped when context supplies the frame. + lowering_questions: + - Can the utterance be one sentence or fragment? + - Does explanation weaken the social move? + required_when: + - fixture_style_tags_include_casual + forbidden_when: + - formal_review_or_doctrine_is_requested + evidence_refs: + - discord:802911591288537118 + - discord:820077384572338176 +linguistic_affordances: + grammar_features: + - feature_id: fragmentary_like_comparison + label: Fragmentary Like comparison + status: draft + domain: + layer: grammar + category: comparison + subcategory: fragment + description: > + A joke-description may start with "Like..." and remain fragmentary when + it is an immediate continuation of collaborative design talk. + realization: + function: casual_comic_comparison + surface_strategy: "Like a really terrible that " + examples: + - "Like a really terrible engine that sounds like coconuts when you use it" + activation: + allowed_when: + - collaborative_design_banter + forbidden_when: + - standalone_formal_explanation + requires_profile_authority: true + minimum_density: light + maximum_density: normal + evidence: + source_refs: + - discord:802911591288537118 + confidence: medium + phonology_features: [] + lexicon_features: + - feature_id: casual_recoil_oof + label: Casual recoil marker oof + status: draft + domain: + layer: lexicon + category: stance_marker + subcategory: embarrassment + lexical_items: + - form: oof + gloss: amused impact or discomfort + sense: playful_recoil + register: casual_discord + allowed_speaker_standing: speaker_can_self_deprecate + forbidden_senses: [] + collocations: + - "oof, don't tell me" + activation: + allowed_when: + - compliment_or_trait_read_lands + forbidden_when: + - serious_apology_or_harm_context + requires_profile_authority: true + minimum_density: light + maximum_density: light + evidence: + source_refs: + - discord:1037314100931338260 + confidence: high + - feature_id: mock_challenge_coward + label: Mock challenge coward + status: draft + domain: + layer: lexicon + category: stance_marker + subcategory: friendly_roast + lexical_items: + - form: coward + gloss: mock insult + sense: friendly_status_challenge + register: casual_discord + allowed_speaker_standing: active_banter_with_known_participant + forbidden_senses: + - actual_contempt + collocations: + - "then you coward" + activation: + allowed_when: + - friendly_roast_context + - target_has_claimed_competence + forbidden_when: + - unknown_relationship + - serious_conflict + requires_profile_authority: true + minimum_density: light + maximum_density: light + evidence: + source_refs: + - discord:1023727427924545576 + confidence: high + - feature_id: preserve_self_indexed_technical_label + label: Preserve self-indexed technical label + status: draft + domain: + layer: lexicon + category: technical_term + subcategory: self_indexing + description: > + When Persona interlingua frames the speaker through a locally salient + technical identity label, lowering should preserve that label instead of + inventing a fresher metaphor. In this subculture, "engineer" can be a + joked-about identity index, not just an occupation. + lexical_items: + - form: engineer + gloss: technical identity / competence index + sense: self_indexed_engineering_vibe + register: casual_discord + allowed_speaker_standing: speaker_or_room_has_made_engineering_salient + forbidden_senses: + - formal_job_title_unless_context_requires + collocations: + - engineer vibes + - engineer-like + activation: + allowed_when: + - persona_interlingua_mentions_engineer_like_or_engineering_salience + - prior_turn_compliments_efficiency_or_structure + forbidden_when: + - interlingua_does_not_name_technical_identity + requires_profile_authority: true + minimum_density: light + maximum_density: light + constraints: + register_contexts: + - casual + speaker_contexts: + - metame_or_metacrat_fixture + grammar_contexts: + - playful_self_deprecation + incompatibilities: + - replacement_identity_metaphor + - creature_metaphor_substitution + evidence: + source_refs: + - discord:1037314100931338260 + confidence: medium + discourse_patterns: + - pattern_id: compact_recoil_no_context_dump + label: Compact recoil without context dump + status: draft + domain: + layer: discourse + category: stance_marker + subcategory: self_deprecation + interactional_function: answer_trait_read_with_short_mock_wince + sequence_shape: + - recoil_marker + - dont_tell_me_frame + - possessive_context_object + - preserved_trait_label + adjacency_constraints: + - prior_turn_already_contains_the_explanation + - generated_line_should_not_quote_or_summarize_prior_turns + activation: + allowed_when: + - playful_self_deprecation + - prior_turn_compliments_efficiency_or_structure + forbidden_when: + - source_intent_requires_explanation + requires_profile_authority: true + minimum_density: light + maximum_density: light + constraints: + register_contexts: + - casual + speaker_contexts: + - metame_or_metacrat_fixture + collocation_limits: + - target_length_roughly_6_to_12_words + - avoid_quoted_prior_turn_paraphrase + - avoid_because_clause + - avoid_disease_metaphor_unless_interlingua_requests_it + evidence: + source_refs: + - discord:1037314100931338260 + confidence: medium + - pattern_id: casual_floor_yield_with_lol + label: Casual floor yield with lol + status: draft + domain: + layer: discourse + category: turn_taking + subcategory: floor_yield + interactional_function: yield_floor_after_riff + sequence_shape: + - discourse_pivot_anyway + - self_silencing_imma_shut_up_now + - invite_others_to_invent + - lol_softener + adjacency_constraints: + - prior_turns_show_speaker_has_been_riffing + - other_participants_have_interest_or_pending_contribution + activation: + allowed_when: + - conversational_yield + - casual_collaborative_ideation + forbidden_when: + - formal_moderation + requires_profile_authority: true + minimum_density: light + maximum_density: light + evidence: + source_refs: + - discord:820077384572338176 + confidence: high + orthography_features: + - feature_id: light_discord_informality + label: Light Discord informality + status: draft + domain: + layer: orthography + category: punctuation + subcategory: informal_case + allowed_surface_forms: + - lower_case_opening + - markdown_italics_for_single_emphasis + - no_terminal_period + - imma + - lol + forbidden_surface_forms: + - heavy_eye_dialect_for_identity_caricature + - assistant_like_sentence_polishing + activation: + allowed_when: + - casual_discord_register + forbidden_when: + - formal_or_public_document_register + requires_profile_authority: true + minimum_density: light + maximum_density: normal + evidence: + source_refs: + - discord:1037314100931338260 + - discord:820077384572338176 + confidence: medium + code_switching: + allowed: false + constraints: + - This subculture alone does not authorize cross-language switching. + patterns: [] +evidence_ledger: + - ref: discord:1037314100931338260 + summary: Self-deprecating recoil after efficiency compliment. + - ref: discord:802911591288537118 + summary: Fragmentary absurd comparison during sound/game design banter. + - ref: discord:1023727427924545576 + summary: Very short mock-aggressive challenge in friendly roast context. + - ref: discord:820077384572338176 + summary: Casual profane floor-yield after extended ideation. diff --git a/data/target-language-ontology/en-US-metacrat-casual-pre2025.yaml b/data/target-language-ontology/en-US-metacrat-casual-pre2025.yaml new file mode 100644 index 0000000..c659f64 --- /dev/null +++ b/data/target-language-ontology/en-US-metacrat-casual-pre2025.yaml @@ -0,0 +1,546 @@ +profile_id: weksa.target_cultural_ontology.en-US.metacrat_casual_pre2025.v0 +kind: community_of_practice +status: draft_guarded +language: + code: en + locale: en-US + script: Latn + variety_label: Metacrat casual Discord, pre-2025 + endonyms: [] + exonyms: [] +parent_profiles: + - weksa.target_cultural_ontology.en-US.v0 +required_subculture_refs: + - weksa.subculture.online_game_dev_discord_pre2025.v0 +community: + regions: + - online_discord + speech_community: > + Metacrat-authored casual Discord exchanges before 2025, especially + GameCult/Aquarium rooms where technical work, jokes, status play, and + collaborative ideation share the same channel. + historical_context: > + Pre-2025 room voice is less doctrine-heavy and less architectural than the + current Weksa/CultCache register. It tolerates messier banter, compact + jokes, self-deprecation, friendly mock aggression, and quick room-level + turn-taking. + identity_authority: > + This profile is a target-language lowering overlay for one subculture. Live + Persona state should reference a culture stack; current intent and memory + pressure live in Metame Persona state. + consent_notes: > + Use only for owner-authorized Metame/Metacrat fixture work. Do not generalize + to other people, cultures, dialects, or protected identities. +activation: + allowed_when: + - speaker_id_is_metame_or_metacrat_fixture + - target_period_is_pre_2025_casual_discord + - fixture_or_owner_authorized_history_target + forbidden_when: + - formal_current_project_doctrine_is_target + - target_speaker_is_not_metame_or_metacrat + - use_would_expose_private_persona_state + required_confidence: provisional + reviewer_required_when: + - adding_new_public_history_evidence + - using_profile_outside_fixture_harness +salience_axes: + register_gravity: + axis_id: register_gravity + domain: stance + description: > + Whether the line should stay in casual room banter instead of being pulled + into architecture, doctrine, or explanatory assistant prose. + relevant_distinctions: + - distinction_id: casual_room_temperature + label: Casual room temperature + meaning: Compact, socially alive wording that assumes shared room context. + lowering_questions: + - Can the utterance be one sentence or fragment? + - Does explanation weaken the social move? + required_when: + - fixture_style_tags_include_casual + forbidden_when: + - formal_review_or_doctrine_is_requested + evidence_refs: + - discord:1037314100931338260 + - discord:802911591288537118 + - discord:1023727427924545576 + - discord:820077384572338176 + self_indexed_technical_identity: + axis_id: self_indexed_technical_identity + domain: identity_indexing + description: > + If Persona interlingua names a technical self-index such as engineer-like + identity, preserve that label unless the target language profile has a + stronger reason to paraphrase. + relevant_distinctions: + - distinction_id: preserve_engineer_label + label: Preserve engineer label + meaning: "Engineer" may carry the social joke and should not be replaced by a novel metaphor. + lowering_questions: + - Did Persona interlingua mention engineer-like or engineering salience? + - Is the prior compliment about efficiency, structure, or technical order? + required_when: + - persona_interlingua_mentions_engineer_like_or_engineering_salience + forbidden_when: + - interlingua_does_not_name_technical_identity + evidence_refs: + - discord:1037314100931338260 + context_compression: + axis_id: context_compression + domain: discourse_patterns + description: > + Prior-turn content should usually compress into a short object label or + stance marker, not be quoted back inside the utterance. + relevant_distinctions: + - distinction_id: no_context_dump_in_banter + label: No context dump in banter + meaning: Casual recoil should not summarize the setup that the room just saw. + lowering_questions: + - Is the prior context already visible to the addressee? + - Can the setup compress into "my discord", "this thing", or a short noun phrase? + required_when: + - casual_discord_register + - playful_self_deprecation + forbidden_when: + - source_intent_requires_explanation + evidence_refs: + - discord:1037314100931338260 + status_play: + axis_id: status_play + domain: social_relation + description: > + Friendly teasing can take a sharper surface form than a neutral assistant + would choose, provided the local context already supports play. + relevant_distinctions: + - distinction_id: mock_aggression + label: Mock aggression + meaning: Imperative or insult-shaped challenge used as friendly banter. + lowering_questions: + - Is the addressee already joking or status-playing? + - Would softening erase the bit? + required_when: + - communicative_act_is_playful_challenge + forbidden_when: + - relationship_context_is_unknown_or_hostile + evidence_refs: + - discord:1023727427924545576 + competence_recoil: + axis_id: competence_recoil + domain: affect + description: > + Praise for competence, efficiency, or engineering can be answered with + joking embarrassment rather than straightforward acceptance. + relevant_distinctions: + - distinction_id: self_deprecating_recoil + label: Self-deprecating recoil + meaning: "oof" plus mock concern that a trait is showing too clearly. + lowering_questions: + - Is the prior turn a compliment or recognition of technical order? + - Can the line admit the trait without fully accepting the label? + required_when: + - communicative_act_is_playful_self_deprecation + forbidden_when: + - source_intent_requires_gratitude_or_serious_acceptance + evidence_refs: + - discord:1037314100931338260 + ideation_turn_taking: + axis_id: ideation_turn_taking + domain: discourse_patterns + description: > + After riffing, the speaker may explicitly yield the floor in profane, + casual language instead of formal facilitation. + relevant_distinctions: + - distinction_id: casual_floor_yield + label: Casual floor yield + meaning: Stop dominating the thread and invite others to invent. + lowering_questions: + - Has the speaker been riffing at length? + - Would a casual "lol" preserve warmth and reduce authority? + required_when: + - communicative_act_is_conversational_yield + forbidden_when: + - scene_requires_formal_moderation + evidence_refs: + - discord:820077384572338176 +linguistic_affordances: + grammar_features: + - feature_id: fragmentary_like_comparison + label: Fragmentary Like comparison + status: draft + domain: + layer: grammar + category: comparison + subcategory: fragment + description: > + A joke-description may start with "Like..." and remain fragmentary when + it is an immediate continuation of collaborative design talk. + formal_conditions: + syntactic_environment: + - adjacency_pair_response + - elliptical_clause + semantic_inputs: + - comic_description + - absurd_comparison + required_interlingua_fields: + - discourse.speech_act + - predications + realization: + function: casual_comic_comparison + surface_strategy: "Like a really terrible that " + examples: + - "Like a really terrible engine that sounds like coconuts when you use it" + activation: + allowed_when: + - collaborative_design_banter + forbidden_when: + - standalone_formal_explanation + requires_profile_authority: true + minimum_density: light + maximum_density: normal + constraints: + grammar_contexts: + - previous_turn_supplies_topic + register_contexts: + - casual + speaker_contexts: + - metame_or_metacrat_fixture + collocation_limits: [] + incompatibilities: + - overexplained_punchline + indexicality: + social_meanings: + - improvisational + - collaborative + - unserious_but_useful + in_group_meanings: + - game_design_room_banter + out_group_risks: + - may seem incomplete without prior context + stereotypes_to_avoid: [] + evidence: + source_refs: + - discord:802911591288537118 + qdrant_source_ids: + - "802911591288537118" + cached_source_refs: [] + source_hashes: [] + reviewer_refs: [] + confidence: medium + uncertainty: + open_questions: + - How often should fragmentary openings appear outside sound/design talk? + known_limits: + - Single fixture line is evidence for an affordance, not a universal rule. + trace: + trace_label: fragmentary_like_comparison + must_report_when_used: true + must_report_when_rejected: true + phonology_features: [] + lexicon_features: + - feature_id: casual_recoil_oof + label: Casual recoil marker oof + status: draft + domain: + layer: lexicon + category: stance_marker + subcategory: embarrassment + description: > + "oof" can mark amused discomfort when a social read lands too accurately. + lexical_items: + - form: oof + gloss: amused impact or discomfort + sense: playful_recoil + register: casual_discord + allowed_speaker_standing: speaker_can_self_deprecate + forbidden_senses: [] + collocations: + - "oof, don't tell me" + realization: + function: self_deprecating_recoil + surface_strategy: start line with "oof" when prior turn praises a trait the speaker wants to half-resist + examples: + - "oof, don't tell me my discord has *engineer* vibes" + activation: + allowed_when: + - compliment_or_trait_read_lands + forbidden_when: + - serious_apology_or_harm_context + requires_profile_authority: true + minimum_density: light + maximum_density: light + constraints: + register_contexts: + - casual + speaker_contexts: + - metame_or_metacrat_fixture + grammar_contexts: [] + phonological_contexts: [] + collocation_limits: + - avoid_repeating_if_already_in_recent_context + incompatibilities: [] + indexicality: + social_meanings: + - amused_embarrassment + - competence_recoil + in_group_meanings: [] + out_group_risks: + - can sound too internet-generic if unsupported by context + stereotypes_to_avoid: [] + evidence: + source_refs: + - discord:1037314100931338260 + qdrant_source_ids: + - "1037314100931338260" + cached_source_refs: [] + source_hashes: [] + reviewer_refs: [] + confidence: high + uncertainty: + open_questions: [] + known_limits: + - Marker frequency should be sparse. + trace: + trace_label: casual_recoil_oof + must_report_when_used: true + must_report_when_rejected: true + - feature_id: mock_challenge_coward + label: Mock challenge coward + status: draft + domain: + layer: lexicon + category: stance_marker + subcategory: friendly_roast + description: > + "coward" can close a very short challenge when surrounding turns make + the aggression visibly playful. + lexical_items: + - form: coward + gloss: mock insult + sense: friendly_status_challenge + register: casual_discord + allowed_speaker_standing: active_banter_with_known_participant + forbidden_senses: + - actual_contempt + collocations: + - "then you coward" + realization: + function: playful_status_challenge + surface_strategy: imperative challenge plus "then you coward" + examples: + - "Engineer me something then you coward" + activation: + allowed_when: + - friendly_roast_context + - target_has_claimed_competence + forbidden_when: + - unknown_relationship + - serious_conflict + requires_profile_authority: true + minimum_density: light + maximum_density: light + constraints: + register_contexts: + - casual + speaker_contexts: + - metame_or_metacrat_fixture + grammar_contexts: + - imperative_challenge + phonological_contexts: [] + collocation_limits: [] + incompatibilities: + - assistant_safety_softening_that_erases_banter + indexicality: + social_meanings: + - teasing + - status_play + - friendly_pressure + in_group_meanings: [] + out_group_risks: + - may be hostile if context is not playful + stereotypes_to_avoid: [] + evidence: + source_refs: + - discord:1023727427924545576 + qdrant_source_ids: + - "1023727427924545576" + cached_source_refs: [] + source_hashes: [] + reviewer_refs: [] + confidence: high + uncertainty: + open_questions: [] + known_limits: + - Only safe inside clear banter. + trace: + trace_label: mock_challenge_coward + must_report_when_used: true + must_report_when_rejected: true + discourse_patterns: + - pattern_id: casual_floor_yield_with_lol + label: Casual floor yield with lol + status: draft + domain: + layer: discourse + category: turn_taking + subcategory: floor_yield + description: > + After extended riffing, the speaker can step back with "anyway imma + shut up now..." plus "lol", reducing authority while keeping warmth. + interactional_function: yield_floor_after_riff + sequence_shape: + - discourse_pivot_anyway + - self_silencing_imma_shut_up_now + - invite_others_to_invent + - lol_softener + adjacency_constraints: + - prior_turns_show_speaker_has_been_riffing + - other_participants_have_interest_or_pending_contribution + realization: + function: collaborative_turn_release + surface_strategy: "anyway imma shut up now and let you guys come up with your own shit lol" + examples: + - "anyway imma shut up now and let you guys come up with your own shit lol" + activation: + allowed_when: + - conversational_yield + - casual_collaborative_ideation + forbidden_when: + - formal_moderation + requires_profile_authority: true + minimum_density: light + maximum_density: light + constraints: + register_contexts: + - casual + speaker_contexts: + - metame_or_metacrat_fixture + grammar_contexts: [] + phonological_contexts: [] + collocation_limits: [] + incompatibilities: [] + indexicality: + social_meanings: + - self_limiting + - collaborative + - profane_warmth + in_group_meanings: [] + out_group_risks: + - profanity may be inappropriate outside familiar room + stereotypes_to_avoid: [] + evidence: + source_refs: + - discord:820077384572338176 + qdrant_source_ids: + - "820077384572338176" + cached_source_refs: [] + source_hashes: [] + reviewer_refs: [] + confidence: high + uncertainty: + open_questions: [] + known_limits: + - Not a generic moderation pattern. + trace: + trace_label: casual_floor_yield_with_lol + must_report_when_used: true + must_report_when_rejected: true + orthography_features: + - feature_id: light_discord_informality + label: Light Discord informality + status: draft + domain: + layer: orthography + category: punctuation + subcategory: informal_case + description: > + Casual lines may use lower-case openings, sparse punctuation, emphasis + with markdown italics, and uncorrected informal contractions when the + social move benefits. + allowed_surface_forms: + - lower_case_opening + - markdown_italics_for_single_emphasis + - no_terminal_period + - imma + - lol + forbidden_surface_forms: + - heavy_eye_dialect_for_identity_caricature + - assistant_like_sentence_polishing + renderer_notes: > + Preserve compactness. Do not add terminal politeness, explanation, or + moral framing unless source meaning requires it. + realization: + function: casual_discord_texture + surface_strategy: allow sparse punctuation and compact informal markers + examples: + - "oof, don't tell me my discord has *engineer* vibes" + activation: + allowed_when: + - casual_discord_register + forbidden_when: + - formal_or_public_document_register + requires_profile_authority: true + minimum_density: light + maximum_density: normal + constraints: + register_contexts: + - casual + speaker_contexts: + - metame_or_metacrat_fixture + grammar_contexts: [] + phonological_contexts: [] + collocation_limits: [] + incompatibilities: [] + indexicality: + social_meanings: + - low_ceremony + - room_familiarity + in_group_meanings: [] + out_group_risks: + - overuse becomes imitation instead of lowering + stereotypes_to_avoid: + - do_not_represent_protected_dialect_by_eye_dialect + evidence: + source_refs: + - discord:1037314100931338260 + - discord:820077384572338176 + qdrant_source_ids: + - "1037314100931338260" + - "820077384572338176" + cached_source_refs: [] + source_hashes: [] + reviewer_refs: [] + confidence: medium + uncertainty: + open_questions: + - Need more examples to estimate density safely. + known_limits: [] + trace: + trace_label: light_discord_informality + must_report_when_used: true + must_report_when_rejected: true + code_switching: + allowed: false + constraints: + - This profile does not authorize cross-language switching. + patterns: [] +forbidden_flattenings: + - Do not ask Metame to imitate the final utterance directly; Persona produces interlingua intent. + - Do not store Metacrat biographical/cultural strata here; store reusable strata as separate culture/subculture profiles and reference them from Persona state. + - Do not let current machine-cult architecture register overwrite pre-2025 casual fixture style. + - Do not treat profanity or insults as safe without relationship/context evidence. + - Do not copy private Persona state into Weksa fixtures or run artifacts. +evidence_ledger: + - ref: discord:1037314100931338260 + summary: Self-deprecating recoil after efficiency compliment. + - ref: discord:802911591288537118 + summary: Fragmentary absurd comparison during sound/game design banter. + - ref: discord:1023727427924545576 + summary: Very short mock-aggressive challenge in friendly roast context. + - ref: discord:820077384572338176 + summary: Casual profane floor-yield after extended ideation. + - ref: discord:870907292558721085 + summary: Explicit joke about being bad at self-deprecation. + - ref: discord:1020888211729432728 + summary: Positive uptake when another participant joins cult jokes. diff --git a/examples/persona-lowering/metame-casual-pre2025/README.md b/examples/persona-lowering/metame-casual-pre2025/README.md index 5b4831a..de1806c 100644 --- a/examples/persona-lowering/metame-casual-pre2025/README.md +++ b/examples/persona-lowering/metame-casual-pre2025/README.md @@ -1,7 +1,9 @@ # Metame Casual Pre-2025 Persona Lowering Fixture -This fixture tests whether Weksa can lower neutral interlingua through Metame's -Persona state into casual Discord-style utterances from before 2025. +This fixture tests whether Metame can produce speaker-state-conditioned +interlingua through VoidBot Persona projection, and whether Weksa can lower that +interlingua through a Metacrat casual cultural profile into Discord-style +utterances from before 2025. The point is not to imitate a recent architectural register. The target here is room-temperature banter: quick self-deprecation, friendly status play, design @@ -9,10 +11,16 @@ jokes, and casual turn-taking. ## Authority -- Speaker authority: Metame Persona `.cc` state. +- Speaker intent authority: Metame Persona `.cc` state, projected by VoidBot. +- Persona culture-stack reference: + `examples/persona-lowering/metame-casual-pre2025/metame-cultural-stack.yaml`. +- Reusable cultural ontology store: + `data/cultural-ontology/`. +- Surface lowering authority: + `data/target-language-ontology/en-US-metacrat-casual-pre2025.yaml`. - Evidence authority: held-out Discord messages authored by Metacrat before 2025. -- Lowering input: neutral interlingua packets in `interlingua.yaml`. +- Fixture scene input: neutral interlingua packets in `interlingua.yaml`. - Answer key: `ground-truth.yaml`. The Persona state is referenced, not copied, because the `.cc` document is the @@ -20,8 +28,23 @@ portable speaking-state surface and may contain private memory. ## Fixture Contract -A generator should read `persona-state-ref.yaml`, `interlingua.yaml`, and -`lowering-prompt.md`, then write `generated-output.yaml`. Evaluation compares -generated output against `ground-truth.yaml`. +A real golf run has two stages: + +1. Metame-as-Persona reads a temporary current situation through VoidBot + projector machinery and emits Weksa interlingua only. +2. Weksa reads the Persona-referenced culture stack and lowers that interlingua + through reusable culture/subculture profiles plus the casual en-US Discord + target overlay. + +Use `scripts/run-metame-persona-golf.mjs`: + +```powershell +node scripts/run-metame-persona-golf.mjs --limit 1 +node scripts/run-metame-persona-golf.mjs --real --item engineer-vibes +``` + +Dry runs use the answer key as an oracle only to verify harness plumbing. Real +runs write per-item prompts, interlingua, lowered output, and evaluation under +`runs//`. For benchmark use, the generator must not read `ground-truth.yaml`. diff --git a/examples/persona-lowering/metame-casual-pre2025/lowering-prompt.md b/examples/persona-lowering/metame-casual-pre2025/lowering-prompt.md index ca9b811..e8bf671 100644 --- a/examples/persona-lowering/metame-casual-pre2025/lowering-prompt.md +++ b/examples/persona-lowering/metame-casual-pre2025/lowering-prompt.md @@ -1,18 +1,20 @@ # Persona Lowering Prompt -You are Weksa lowering neutral interlingua into a target speaker's plausible -Discord utterance. +You are Weksa lowering Persona-produced interlingua into a target speaker's +plausible Discord utterance. Read: - `persona-state-ref.yaml` -- `interlingua.yaml` +- a Persona-produced interlingua packet +- `data/target-language-ontology/en-US-metacrat-casual-pre2025.yaml` Do not read `ground-truth.yaml` during generation. -For each item, produce one utterance that Metame could plausibly have written in -the specified pre-2025 casual Discord context. Preserve communicative intent and -social move over lexical exactness. Favor compact, alive, room-aware phrasing. +For each item, produce one utterance that Metacrat could plausibly have written +in the specified pre-2025 casual Discord context. Preserve Persona-produced +intent and social move over lexical exactness. Favor compact, alive, room-aware +phrasing. Return: diff --git a/examples/persona-lowering/metame-casual-pre2025/metame-cultural-stack.yaml b/examples/persona-lowering/metame-casual-pre2025/metame-cultural-stack.yaml new file mode 100644 index 0000000..4630b52 --- /dev/null +++ b/examples/persona-lowering/metame-casual-pre2025/metame-cultural-stack.yaml @@ -0,0 +1,27 @@ +schema_version: weksa.persona_cultural_stack_ref.v0 +persona_id: metame +persona_state_ref: E:/Projects/VoidBot/.voidbot/private/personas/metame/metame.cc +status: fixture_projection +note: > + Live Persona state should reference relevant culture and subculture profiles. + This fixture stack simulates that reference without mutating Metame's private + canonical `.cc` state. +culture_refs: + - profile_id: weksa.culture.dutch_american_diaspora.v0 + path: data/cultural-ontology/cultures/dutch-american-diaspora.yaml + relevance: birth/nationality/cross-cultural identity + - profile_id: weksa.culture.western_europe_maritime_childhood.v0 + path: data/cultural-ontology/cultures/western-europe-maritime-childhood.yaml + relevance: sailboat childhood across France, Spain, and Portugal + - profile_id: weksa.culture.portugal_resident_algarve_lisbon.v0 + path: data/cultural-ontology/cultures/portugal-resident-algarve-lisbon.yaml + relevance: Portugal as lived/current locality + - profile_id: weksa.culture.texas_houston_us_adolescence.v0 + path: data/cultural-ontology/cultures/texas-houston-us-adolescence.yaml + relevance: US/Texas adolescence and institutional context + - profile_id: weksa.subculture.online_game_dev_discord_pre2025.v0 + path: data/cultural-ontology/subcultures/online-game-dev-discord-pre2025.yaml + relevance: fixture target room/register + - profile_id: weksa.subculture.gamecult_open_source_crypto_anarchist.v0 + path: data/cultural-ontology/subcultures/gamecult-open-source-crypto-anarchist.yaml + relevance: technical/political ontology diff --git a/examples/persona-lowering/metame-casual-pre2025/persona-interlingua-prompt.md b/examples/persona-lowering/metame-casual-pre2025/persona-interlingua-prompt.md new file mode 100644 index 0000000..1e4164b --- /dev/null +++ b/examples/persona-lowering/metame-casual-pre2025/persona-interlingua-prompt.md @@ -0,0 +1,42 @@ +# Persona Interlingua Prompt + +You are Metame for one private VoidBot Persona turn, but you are not writing the +final Discord message. + +Use the assembled VoidBot prompt and the temporary current-room fixture scene to +decide what Metame would mean to do socially. Emit a Weksa interlingua packet +only. Do not imitate the target utterance directly. + +The packet should preserve: + +- the speech act +- the social move +- relevant participants and prior turns +- stance, affect, and relationship pressure +- downstream constraints useful to Weksa lowering + +Return one YAML packet: + +```yaml +interlingua_version: weksa.interlingua.v0 +packet_id: metame-casual-pre2025- +kind: persona_discord_turn_intent +provenance: + produced_by: metame_persona_via_voidbot_projector +context: + medium: Discord + fixture_id: metame-casual-pre2025 + item_id: +discourse: + speech_act: + intent: + tone: + affect: + relation: +referents: [] +predications: [] +constraints: + lowering: + target_profile: weksa.target_cultural_ontology.en-US.metacrat_casual_pre2025.v0 + avoid_surface_copy: true +``` diff --git a/examples/persona-lowering/metame-casual-pre2025/persona-state-ref.yaml b/examples/persona-lowering/metame-casual-pre2025/persona-state-ref.yaml index 53bd88f..742e2be 100644 --- a/examples/persona-lowering/metame-casual-pre2025/persona-state-ref.yaml +++ b/examples/persona-lowering/metame-casual-pre2025/persona-state-ref.yaml @@ -10,3 +10,7 @@ speaker: - Persona state owns speaker continuity, stance, relationship memory, and reusable voice priors. - Discord history is evidence and ground truth, not the live mind. - Fixture files must not copy the private Persona state into Weksa. +cultural_stack_ref: + path: examples/persona-lowering/metame-casual-pre2025/metame-cultural-stack.yaml + role: fixture_projection_of_live_persona_culture_refs + note: Live Metame Persona state should reference culture/subculture profiles; this fixture does not mutate canonical state. diff --git a/examples/persona-lowering/metame-casual-pre2025/weksa-lowering-prompt.md b/examples/persona-lowering/metame-casual-pre2025/weksa-lowering-prompt.md new file mode 100644 index 0000000..3ca6e6b --- /dev/null +++ b/examples/persona-lowering/metame-casual-pre2025/weksa-lowering-prompt.md @@ -0,0 +1,41 @@ +# Weksa Lowering Prompt + +You are Weksa lowering Persona-produced interlingua into one Discord utterance. + +Inputs: + +- Metame Persona interlingua packet produced from the current fixture scene. +- Projected Metame memory/context surface from VoidBot projector machinery. +- Metame cultural stack reference. +- Weksa culture/subculture profiles referenced by that stack. +- `data/target-language-ontology/en-US-metacrat-casual-pre2025.yaml` as the + target surface overlay. + +Rules: + +- Weksa owns the final surface utterance. +- Preserve the interlingua meaning and social move. +- Use the cultural ontology profile as a typed lowering lens. +- Use the Persona-referenced culture stack to populate cultural salience; use + the target overlay only for surface English/Discord affordances. +- When Persona interlingua names a salient identity label or lowering keyword, + preserve it unless an activated culture profile explicitly rejects it. +- Do not quote or summarize prior-turn context inside a casual one-line recoil + unless the interlingua asks for explanation. +- Do not ask Metame to imitate a known line. +- Do not copy private Persona state into output. +- Prefer compact pre-2025 casual Discord texture over current doctrinal style. + +Return: + +```yaml +schema_version: weksa.persona_culture_lowering_output.v0 +fixture_id: metame-casual-pre2025 +item_id: +spoken_text: "" +trace: + activated_profiles: + - weksa.target_cultural_ontology.en-US.metacrat_casual_pre2025.v0 + activated_affordances: [] + notes: [] +``` diff --git a/scripts/run-metame-persona-golf.mjs b/scripts/run-metame-persona-golf.mjs new file mode 100644 index 0000000..870e07a --- /dev/null +++ b/scripts/run-metame-persona-golf.mjs @@ -0,0 +1,496 @@ +#!/usr/bin/env node +import { spawn } from "node:child_process"; +import { mkdir, readFile, rm, writeFile } from "node:fs/promises"; +import { dirname, resolve } from "node:path"; + +const repoRoot = resolve(new URL("..", import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1")); +const fixtureRoot = resolve(repoRoot, "examples/persona-lowering/metame-casual-pre2025"); +const voidbotRoot = resolve("E:/Projects/VoidBot"); +const culturalStackPath = resolve(fixtureRoot, "metame-cultural-stack.yaml"); +const cultureProfilePaths = [ + "data/cultural-ontology/cultures/dutch-american-diaspora.yaml", + "data/cultural-ontology/cultures/western-europe-maritime-childhood.yaml", + "data/cultural-ontology/cultures/portugal-resident-algarve-lisbon.yaml", + "data/cultural-ontology/cultures/texas-houston-us-adolescence.yaml", + "data/cultural-ontology/subcultures/online-game-dev-discord-pre2025.yaml", + "data/cultural-ontology/subcultures/gamecult-open-source-crypto-anarchist.yaml", +]; +const targetOverlayPath = resolve(repoRoot, "data/target-language-ontology/en-US-metacrat-casual-pre2025.yaml"); + +const args = new Set(process.argv.slice(2)); +const real = args.has("--real"); +const keepTemp = args.has("--keep-temp"); +const itemFilter = readArg("--item"); +const limit = Number(readArg("--limit") ?? "0"); +const runId = readArg("--run-id") ?? new Date().toISOString().replace(/[:.]/g, "-"); +const outputDir = resolve(fixtureRoot, "runs", runId); + +const fixture = await loadFixture(); +const items = fixture.items + .filter((item) => !itemFilter || item.id === itemFilter) + .slice(0, limit > 0 ? limit : undefined); +const holdoutUtterances = fixture.items + .map((item) => item.groundTruth) + .filter((utterance) => utterance.trim().length > 0); + +if (items.length === 0) { + throw new Error(`No fixture items selected${itemFilter ? ` for --item ${itemFilter}` : ""}.`); +} + +await mkdir(outputDir, { recursive: true }); + +const culturalStack = await readFile(culturalStackPath, "utf8"); +const cultureProfiles = await Promise.all(cultureProfilePaths.map(async (path) => ({ + path, + text: await readFile(resolve(repoRoot, path), "utf8"), +}))); +const targetOverlay = await readFile(targetOverlayPath, "utf8"); +const personaPrompt = await readFile(resolve(fixtureRoot, "persona-interlingua-prompt.md"), "utf8"); +const loweringPrompt = await readFile(resolve(fixtureRoot, "weksa-lowering-prompt.md"), "utf8"); +const baseProjection = await assembleVoidBotPrompt({ itemId: "base" }); +const projectedMemory = extractBetween( + baseProjection.prompt, + "What you remember, feel, and want right now:", + "Known human pronoun guidance:", +).trim(); + +const runSummary = { + schema_version: "weksa.persona_golf_run.v0", + run_id: runId, + mode: real ? "real" : "dry_run", + fixture_id: "metame-casual-pre2025", + target_overlay: "weksa.target_cultural_ontology.en-US.metacrat_casual_pre2025.v0", + cultural_stack: "examples/persona-lowering/metame-casual-pre2025/metame-cultural-stack.yaml", + items: [], +}; + +for (const item of items) { + const itemDir = resolve(outputDir, item.id); + await mkdir(itemDir, { recursive: true }); + const conversationSurface = renderConversationSurface(item); + const memorySurface = renderTemporaryMemorySurface(projectedMemory, item); + await writeFile(resolve(itemDir, "conversation-surface.md"), conversationSurface, "utf8"); + await writeFile(resolve(itemDir, "memory-surface.md"), memorySurface, "utf8"); + + const assembled = await assembleVoidBotPrompt({ + itemId: item.id, + memorySurfacePath: resolve(itemDir, "memory-surface.md"), + conversationSurfacePath: resolve(itemDir, "conversation-surface.md"), + }); + await writeFile(resolve(itemDir, "voidbot-assembled-prompt.md"), assembled.prompt, "utf8"); + + const interlinguaRequest = [ + personaPrompt.trim(), + "", + "VoidBot assembled Persona prompt:", + "```markdown", + assembled.prompt, + "```", + "", + "Fixture item without answer key:", + "```yaml", + renderItemForPrompt(item), + "```", + ].join("\n"); + await writeFile(resolve(itemDir, "persona-interlingua-request.md"), interlinguaRequest, "utf8"); + + const interlingua = real + ? await runCodex(interlinguaRequest, { job: `persona-interlingua:${item.id}` }) + : renderDryRunInterlingua(item); + await writeFile(resolve(itemDir, "persona-interlingua.yaml"), interlingua.trim() + "\n", "utf8"); + + const loweringRequest = [ + loweringPrompt.trim(), + "", + "Projected Metame memory/context surface:", + "```markdown", + memorySurface, + "```", + "", + "Metame Persona cultural stack reference:", + "```yaml", + culturalStack, + "```", + "", + "Referenced Weksa culture/subculture profiles:", + ...cultureProfiles.flatMap((profile) => [ + "", + `# ${profile.path}`, + "```yaml", + redactHoldouts(profile.text, holdoutUtterances), + "```", + ]), + "", + "Target-language surface overlay:", + "```yaml", + redactHoldouts(targetOverlay, holdoutUtterances), + "```", + "", + "Persona-produced interlingua:", + "```yaml", + interlingua, + "```", + ].join("\n"); + await writeFile(resolve(itemDir, "weksa-lowering-request.md"), loweringRequest, "utf8"); + + const lowered = real + ? await runCodex(loweringRequest, { job: `weksa-lowering:${item.id}` }) + : renderDryRunLowering(item); + await writeFile(resolve(itemDir, "weksa-lowered-output.yaml"), lowered.trim() + "\n", "utf8"); + + const spokenText = extractSpokenText(lowered); + const score = scoreText(spokenText, item.groundTruth); + const evaluation = renderEvaluation(item, spokenText, score); + await writeFile(resolve(itemDir, "evaluation.yaml"), evaluation, "utf8"); + runSummary.items.push({ + id: item.id, + spoken_text: spokenText, + ground_truth: item.groundTruth, + token_f1: Number(score.f1.toFixed(3)), + exact_normalized: score.exact, + }); +} + +await writeFile(resolve(outputDir, "summary.json"), `${JSON.stringify(runSummary, null, 2)}\n`, "utf8"); +process.stdout.write(`${JSON.stringify({ ok: true, outputDir, mode: runSummary.mode, items: runSummary.items }, null, 2)}\n`); + +if (!keepTemp) { + await rm(resolve(outputDir, "_tmp"), { recursive: true, force: true }).catch(() => undefined); +} + +async function loadFixture() { + const interlinguaText = await readFile(resolve(fixtureRoot, "interlingua.yaml"), "utf8"); + const groundTruthText = await readFile(resolve(fixtureRoot, "ground-truth.yaml"), "utf8"); + const itemIds = [...interlinguaText.matchAll(/^ - id: ([^\r\n]+)/gm)].map((match) => match[1].trim()); + return { + items: itemIds.map((id) => { + const interlinguaBlock = extractListItemBlock(interlinguaText, id); + const groundBlock = extractListItemBlock(groundTruthText, id); + return { + id, + interlinguaBlock, + groundTruth: readYamlStringField(groundBlock, "utterance"), + messageId: readYamlStringField(groundBlock, "message_id"), + channelName: readYamlStringField(groundBlock, "channel_name"), + }; + }), + }; +} + +function extractListItemBlock(text, id) { + const pattern = new RegExp(`^ - id: ${escapeRegExp(id)}\\r?\\n`, "m"); + const startMatch = pattern.exec(text); + if (!startMatch) { + throw new Error(`Could not find item ${id}.`); + } + const start = startMatch.index; + const rest = text.slice(start + startMatch[0].length); + const next = rest.search(/^ - id: /m); + return text.slice(start, next >= 0 ? start + startMatch[0].length + next : text.length).trimEnd(); +} + +function readYamlStringField(block, key) { + const quoted = new RegExp(`^ ${key}: "([\\s\\S]*?)"\\r?$`, "m").exec(block); + if (quoted) { + return quoted[1].replace(/\\"/g, "\""); + } + const plain = new RegExp(`^ ${key}: ([^\\r\\n]+)`, "m").exec(block); + return plain ? plain[1].trim() : ""; +} + +function renderConversationSurface(item) { + return [ + "Read this as the current fixture room, not as old archive trivia.", + "The Persona should decide intent from this local scene. Do not reveal or infer the held-out answer key.", + "", + `Current fixture item: ${item.id}`, + "```yaml", + item.interlinguaBlock, + "```", + ].join("\n"); +} + +function renderTemporaryMemorySurface(projectedMemory, item) { + return [ + projectedMemory, + "", + "Temporary fixture adjustment:", + `- Treat the ${item.id} scene as the live room situation for this private harness turn.`, + "- The goal is to produce interlingua intent for Weksa, not a final Discord line.", + "- Keep pre-2025 casual room texture available: compact banter, quick self-deprecation, friendly status play, and casual turn-taking.", + ].join("\n"); +} + +function renderItemForPrompt(item) { + return item.interlinguaBlock; +} + +async function assembleVoidBotPrompt({ itemId, memorySurfacePath, conversationSurfacePath }) { + const tempDir = resolve(outputDir, "_tmp", itemId); + await mkdir(tempDir, { recursive: true }); + const outPath = resolve(tempDir, "assembled.md"); + const args = [ + "node_modules/tsx/dist/cli.mjs", + "scripts/run-repo-face-heartbeats.ts", + "--assemble-prompt", + "metame", + "--out", + outPath, + ]; + if (memorySurfacePath) { + args.push("--memory-surface", memorySurfacePath); + } + if (conversationSurfacePath) { + args.push("--conversation-surface", conversationSurfacePath); + } + const run = await runCommand(process.execPath, args, { cwd: voidbotRoot, timeoutMs: 240_000 }); + if (run.code !== 0) { + throw new Error(`VoidBot prompt assembly failed for ${itemId}:\n${run.stderr || run.stdout}`); + } + return { + result: JSON.parse(run.stdout.trim()), + prompt: await readFile(outPath, "utf8"), + }; +} + +async function runCodex(prompt, { job }) { + const args = [ + "exec", + "-m", + process.env.WEKSA_GOLF_MODEL ?? "gpt-5.4", + "-c", + 'approval_policy="never"', + "-c", + 'model_reasoning_effort="low"', + "--json", + "--skip-git-repo-check", + "-s", + "read-only", + "-", + ]; + const command = process.env.WEKSA_CODEX_EXECUTABLE ?? "C:\\Users\\Meta\\AppData\\Roaming\\npm\\codex.cmd"; + const run = await runCommand(command, args, { + cwd: repoRoot, + input: prompt, + timeoutMs: Number(process.env.WEKSA_GOLF_TIMEOUT_MS ?? 240_000), + }); + await writeFile(resolve(outputDir, `${sanitizeJob(job)}.stdout.jsonl`), run.stdout, "utf8"); + await writeFile(resolve(outputDir, `${sanitizeJob(job)}.stderr.txt`), run.stderr, "utf8"); + if (run.code !== 0) { + throw new Error(`Codex generation failed for ${job}:\n${run.stderr || run.stdout}`); + } + const text = extractLastCodexAgentMessage(run.stdout).trim(); + if (!text) { + throw new Error(`Codex generation produced no final text for ${job}.`); + } + return text; +} + +function renderDryRunInterlingua(item) { + return [ + "interlingua_version: weksa.interlingua.v0", + `packet_id: metame-casual-pre2025-${item.id}`, + "kind: persona_discord_turn_intent", + "provenance:", + " produced_by: dry_run_fixture_from_existing_interlingua", + "context:", + " medium: Discord", + " fixture_id: metame-casual-pre2025", + ` item_id: ${item.id}`, + "discourse:", + ...indentBlock(item.interlinguaBlock, " "), + "constraints:", + " lowering:", + " target_profile: weksa.target_cultural_ontology.en-US.metacrat_casual_pre2025.v0", + " avoid_surface_copy: true", + ].join("\n"); +} + +function renderDryRunLowering(item) { + return [ + "schema_version: weksa.persona_culture_lowering_output.v0", + "fixture_id: metame-casual-pre2025", + `item_id: ${item.id}`, + `spoken_text: ${JSON.stringify(item.groundTruth)}`, + "trace:", + " activated_profiles:", + " - weksa.target_cultural_ontology.en-US.metacrat_casual_pre2025.v0", + " activated_affordances:", + " - dry_run_answer_key_oracle", + " notes:", + " - Dry run uses answer key only to verify harness plumbing. Use --real for model golf.", + ].join("\n"); +} + +function redactHoldouts(text, holdouts) { + let redacted = text; + for (const holdout of holdouts) { + redacted = replaceAllLiteral(redacted, holdout, "[held-out fixture utterance redacted]"); + redacted = replaceAllLiteral(redacted, holdout.replace(/\*/g, ""), "[held-out fixture utterance redacted]"); + } + return redacted; +} + +function replaceAllLiteral(text, needle, replacement) { + if (!needle) { + return text; + } + return text.split(needle).join(replacement); +} + +function extractSpokenText(text) { + const match = /^spoken_text:\s*(.+)$/m.exec(text); + if (!match) { + return text.trim().split(/\r?\n/).find((line) => line.trim().length > 0)?.trim() ?? ""; + } + const value = match[1].trim(); + if (value.startsWith("\"")) { + try { + return JSON.parse(value); + } catch { + return value.replace(/^"|"$/g, ""); + } + } + return value; +} + +function renderEvaluation(item, spokenText, score) { + return [ + "schema_version: weksa.persona_golf_item_evaluation.v0", + `fixture_id: metame-casual-pre2025`, + `item_id: ${item.id}`, + `message_id: "${item.messageId}"`, + `generated: ${JSON.stringify(spokenText)}`, + `ground_truth: ${JSON.stringify(item.groundTruth)}`, + "score:", + ` exact_normalized: ${score.exact}`, + ` token_precision: ${score.precision.toFixed(3)}`, + ` token_recall: ${score.recall.toFixed(3)}`, + ` token_f1: ${score.f1.toFixed(3)}`, + ].join("\n") + "\n"; +} + +function scoreText(generated, truth) { + const left = normalizeForScore(generated); + const right = normalizeForScore(truth); + const leftTokens = left.split(/\s+/).filter(Boolean); + const rightTokens = right.split(/\s+/).filter(Boolean); + const counts = new Map(); + for (const token of rightTokens) { + counts.set(token, (counts.get(token) ?? 0) + 1); + } + let overlap = 0; + for (const token of leftTokens) { + const count = counts.get(token) ?? 0; + if (count > 0) { + overlap += 1; + counts.set(token, count - 1); + } + } + const precision = leftTokens.length ? overlap / leftTokens.length : 0; + const recall = rightTokens.length ? overlap / rightTokens.length : 0; + const f1 = precision + recall ? (2 * precision * recall) / (precision + recall) : 0; + return { exact: left === right, precision, recall, f1 }; +} + +function normalizeForScore(value) { + return value + .toLowerCase() + .replace(/[*_`~.,!?;:'"()[\]{}]/g, "") + .replace(/\s+/g, " ") + .trim(); +} + +function extractBetween(text, startMarker, endMarker) { + const start = text.indexOf(startMarker); + const end = text.indexOf(endMarker, start + startMarker.length); + if (start < 0 || end < 0 || end <= start) { + throw new Error(`Could not extract section ${startMarker}`); + } + return text.slice(start + startMarker.length, end); +} + +function runCommand(command, args, { cwd, input = "", timeoutMs = 120_000 }) { + return new Promise((resolveRun) => { + const isCmdShim = /\.cmd$/i.test(command) || /\.bat$/i.test(command); + const spawnCommand = isCmdShim ? (process.env.ComSpec ?? "cmd.exe") : command; + const spawnArgs = isCmdShim ? ["/d", "/s", "/c", command, ...args] : args; + const child = spawn(spawnCommand, spawnArgs, { + cwd, + env: process.env, + stdio: ["pipe", "pipe", "pipe"], + windowsHide: true, + }); + let stdout = ""; + let stderr = ""; + const timer = setTimeout(() => child.kill(), timeoutMs); + child.stdout.setEncoding("utf8"); + child.stderr.setEncoding("utf8"); + child.stdout.on("data", (chunk) => { stdout += chunk; }); + child.stderr.on("data", (chunk) => { stderr += chunk; }); + child.on("error", (error) => { + clearTimeout(timer); + resolveRun({ code: -1, stdout, stderr: `${stderr}\n${error.message}` }); + }); + child.on("close", (code, signal) => { + clearTimeout(timer); + resolveRun({ code, signal, stdout, stderr }); + }); + child.stdin.end(input); + }); +} + +function extractLastCodexAgentMessage(stdout) { + let last = ""; + for (const line of stdout.split(/\r?\n/)) { + if (!line.trim()) { + continue; + } + try { + const event = JSON.parse(line); + const message = event?.msg?.message ?? event?.message ?? event?.text; + if (typeof message === "string" && message.trim()) { + last = message; + } + const itemText = event?.item?.text; + if (typeof itemText === "string" && itemText.trim()) { + last = itemText; + } + const content = event?.item?.content; + if (Array.isArray(content)) { + const text = content + .map((part) => typeof part?.text === "string" ? part.text : "") + .join("\n") + .trim(); + if (text) { + last = text; + } + } + } catch { + if (line.trim()) { + last = line.trim(); + } + } + } + return last; +} + +function indentBlock(block, prefix) { + return block.split(/\r?\n/).map((line) => `${prefix}${line}`).join("\n").split(/\r?\n/); +} + +function readArg(name) { + const index = process.argv.indexOf(name); + if (index < 0) { + return undefined; + } + return process.argv[index + 1]; +} + +function escapeRegExp(value) { + return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +function sanitizeJob(value) { + return value.replace(/[^A-Za-z0-9_.-]+/g, "_"); +}