park-node-body: park the Node body and Discord-sourced harness at parked/weksa-node-body; true README (squash of weksa-lowering/park-node-body 2583fb9..e41b60d)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2583fb91c3
commit
3327eddca3
36 changed files with 56 additions and 6615 deletions
|
|
@ -1,3 +1,3 @@
|
|||
Repository doctrine for this project lives in AGENTS.md, shared with Codex.
|
||||
Repository doctrine for this project lives in AGENTS.md, shared with other coding agents.
|
||||
|
||||
@AGENTS.md
|
||||
|
|
|
|||
47
README.md
47
README.md
|
|
@ -1,12 +1,32 @@
|
|||
# Weksa
|
||||
|
||||
Weksa is a procedural language engine for alien grammars grounded in concept,
|
||||
culture, and constraint.
|
||||
culture, and constraint, and the planned lowerer that turns an agent's
|
||||
meaning packet into the reader's display language.
|
||||
|
||||
The project starts from a blunt rule: English words are not the source of truth.
|
||||
Meaning is decomposed into an ontology, passed through a worldview, constrained
|
||||
by grammar, shaped by phonology, distorted by history, and only then rendered as
|
||||
surface text.
|
||||
**What is valuable here.** The interlingua standard and the lowering design: a
|
||||
meaning packet owns what is meant, a target-language profile owns register,
|
||||
address and politeness, and a lowerer owns surface text and leaves a trace.
|
||||
|
||||
**The problem it solves.** An agent's outward speech today is one English
|
||||
string the model chose, so meaning, register and voice are fused and nobody can
|
||||
inspect or re-target it. Packets separate them, so the same meaning can be
|
||||
lowered into English, other languages and conlangs, and the loss can be
|
||||
measured.
|
||||
|
||||
**Entry points.**
|
||||
|
||||
- [Interlingua Standard](./docs/interlingua-standard.md): the packet contract.
|
||||
- [Lowering target](./docs/targets/interlingua-lowering.md) and
|
||||
[its map](./docs/targets/interlingua-lowering-map.md): the design, scoring
|
||||
and cut sequence.
|
||||
- `data/`: the cultural ontology and target-language profile data.
|
||||
- `settings/`: setting-specific proving grounds for language projects.
|
||||
|
||||
**Live versus planned.** Live: docs, fixtures, examples and ontology data.
|
||||
Planned: the packet crate `crates/weksa` (cut `packet-store`), lowering, and
|
||||
the golf loop that scores lowered English against an expressive corpus. No
|
||||
daemon runs; the earlier Node daemon is parked at tag `parked/weksa-node-body`.
|
||||
|
||||
## Ownership Model
|
||||
|
||||
|
|
@ -40,9 +60,10 @@ for the LLM-free hot path.
|
|||
See [Interlingua Standard](./docs/interlingua-standard.md) for the shared
|
||||
meaning-packet contract Weksa clients should target.
|
||||
|
||||
See [Verse Service Contract](./docs/verse-service-contract.md) for the daemon
|
||||
contract: Weksa owns typed conversational intent, pronunciation plans, utterance
|
||||
handoffs, and the typed CultMesh state that makes those documents inspectable. Eve is deprecated and Thing 2 is its successor; the operator Eve surface is quarantined (see the contract).
|
||||
See [Verse Service Contract](./docs/verse-service-contract.md) for the planned
|
||||
service contract: Weksa would own typed conversational intent, pronunciation
|
||||
plans, utterance handoffs, and the typed CultMesh state that makes those
|
||||
documents inspectable. No daemon runs yet.
|
||||
|
||||
See [Multilingual Utterance Lowering](./docs/multilingual-utterance-lowering.md)
|
||||
for the shared lowering target: interlingua projected through speaker state into
|
||||
|
|
@ -63,11 +84,11 @@ AquaSynth or expanding pronunciation coverage.
|
|||
|
||||
## Implementation Status
|
||||
|
||||
The first committed artifact is the persistent state spine: map, scratch,
|
||||
schemas, docs, examples, and a read-only provider advertisement fixture. The
|
||||
daemon runtime still needs implementation, but its authority is no longer
|
||||
ambiguous: writing and agent state enter Weksa as intent pressure, and typed
|
||||
utterance documents leave for AquaSynth.
|
||||
The persistent state spine is live: map, scratch, schemas, docs, examples and
|
||||
data. Packet storage, lowering and the service are planned (see the cut
|
||||
sequence in [the lowering map](./docs/targets/interlingua-lowering-map.md)).
|
||||
Writing and agent state will enter Weksa as intent pressure, and typed utterance
|
||||
documents will leave for AquaSynth.
|
||||
|
||||
[Eusocial Interbeing / Zyphos](./settings/eusocial-interbeing/README.md) is the
|
||||
current higher-priority setting target. [Rust to Dust](./settings/rust-to-dust/README.md)
|
||||
|
|
|
|||
|
|
@ -1,546 +0,0 @@
|
|||
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 <thing> that <absurd property>"
|
||||
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.
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
# Architecture
|
||||
|
||||
Weksa is the language-intent daemon surface: currently a state spine, pipeline
|
||||
map, fixtures, and service contract waiting for its runtime loop.
|
||||
Weksa is a language-intent engine: currently a state spine, pipeline map,
|
||||
fixtures, ontology data and a planned service contract. No daemon runs; the
|
||||
planned packet crate is `crates/weksa` (cut `packet-store`).
|
||||
|
||||
See [Verse Service Contract](./verse-service-contract.md) for the
|
||||
CultCache/CultMesh authority map.
|
||||
|
|
@ -9,8 +10,8 @@ CultCache/CultMesh authority map.
|
|||
## Objective
|
||||
|
||||
Build a procedural alien-language engine whose outputs are grounded in
|
||||
worldview, semantics, grammar, phonology, morphology, and history. As a daemon,
|
||||
Weksa also turns writing plus agent state into typed conversational intent, then
|
||||
worldview, semantics, grammar, phonology, morphology, and history. Once a service is
|
||||
earned (planned), Weksa also turns writing plus agent state into typed conversational intent, then
|
||||
lowers that intent into written realizations, pronunciation plans, speech-provider
|
||||
requests, and utterance handoff documents.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,166 +0,0 @@
|
|||
{
|
||||
"schema": "gamecult.eve.provider_advertisement.v1",
|
||||
"providerId": "weksa.intent.service",
|
||||
"title": "Weksa Intent Service",
|
||||
"description": "Weksa converts writing, interlingua, and agent state into typed conversational intent, pronunciation plans, and utterance handoff documents for AquaSynth and other consumers.",
|
||||
"mode": "fixture-read-only",
|
||||
"serviceVerse": {
|
||||
"id": "verse.weksa.service.v1",
|
||||
"cultMeshKey": "weksa.service/provider-advertisement",
|
||||
"authority": "Weksa daemon identity, schema catalog, and provider publication",
|
||||
"owns": [
|
||||
"typed conversational intent",
|
||||
"pronunciation plans",
|
||||
"utterance handoff documents",
|
||||
"lowering traces",
|
||||
"language-project and flavor-profile state"
|
||||
],
|
||||
"doesNotOwn": [
|
||||
"AquaSynth learned embeddings",
|
||||
"Faust compilation",
|
||||
"rendered audio",
|
||||
"Persona canonical state"
|
||||
]
|
||||
},
|
||||
"nestedVerses": [
|
||||
{
|
||||
"id": "weksa.intent",
|
||||
"kind": "intent",
|
||||
"cultMeshKey": "weksa.intent/current",
|
||||
"authority": "Weksa conversational intent compiler",
|
||||
"schemas": [
|
||||
"weksa.conversational_intent.v0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "weksa.pronunciation",
|
||||
"kind": "pronunciation",
|
||||
"cultMeshKey": "weksa.pronunciation/current",
|
||||
"authority": "Weksa pronunciation lowering",
|
||||
"schemas": [
|
||||
"weksa.pronunciation_plan.v0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "weksa.utterance",
|
||||
"kind": "utterance",
|
||||
"cultMeshKey": "weksa.utterance/handoffs",
|
||||
"authority": "Weksa utterance handoff export",
|
||||
"schemas": [
|
||||
"weksa.utterance_handoff.v0",
|
||||
"weksa.utterance_embedding_handoff.v0.1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "weksa.operator",
|
||||
"kind": "operator",
|
||||
"cultMeshKey": "weksa.operator/status",
|
||||
"authority": "Weksa daemon runtime",
|
||||
"schemas": [
|
||||
"weksa.operator_state.v0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "weksa.cultural_ontology",
|
||||
"kind": "cultural_ontology",
|
||||
"cultMeshKey": "weksa.cultural-ontology/catalogs",
|
||||
"authority": "Weksa cultural ontology profile store",
|
||||
"schemas": [
|
||||
"weksa.cultural_ontology_profile.v0",
|
||||
"weksa.cultural_ontology_catalog.v0"
|
||||
]
|
||||
}
|
||||
],
|
||||
"schemaWitnessFamilies": [
|
||||
{
|
||||
"family": "intent",
|
||||
"namespace": "weksa.intent.*",
|
||||
"witnesses": [
|
||||
"weksa.conversational_intent.v0"
|
||||
],
|
||||
"durableShape": ".weksa/intent/{intentId}.cc"
|
||||
},
|
||||
{
|
||||
"family": "pronunciation",
|
||||
"namespace": "weksa.pronunciation.*",
|
||||
"witnesses": [
|
||||
"weksa.pronunciation_plan.v0"
|
||||
],
|
||||
"durableShape": ".weksa/pronunciation/{utteranceId}.cc"
|
||||
},
|
||||
{
|
||||
"family": "utterance",
|
||||
"namespace": "weksa.utterance.*",
|
||||
"witnesses": [
|
||||
"weksa.utterance_handoff.v0",
|
||||
"weksa.utterance_embedding_handoff.v0.1"
|
||||
],
|
||||
"durableShape": ".weksa/handoff/{utteranceId}.cc"
|
||||
},
|
||||
{
|
||||
"family": "trace",
|
||||
"namespace": "weksa.trace.*",
|
||||
"witnesses": [
|
||||
"weksa.lowering_trace.v0"
|
||||
],
|
||||
"durableShape": ".weksa/traces/{traceId}.cc"
|
||||
},
|
||||
{
|
||||
"family": "cultural_ontology",
|
||||
"namespace": "weksa.cultural_ontology.*",
|
||||
"witnesses": [
|
||||
"weksa.cultural_ontology_profile.v0",
|
||||
"weksa.cultural_ontology_catalog.v0"
|
||||
],
|
||||
"durableShape": ".weksa/cultural-ontology/{storeId}.cc"
|
||||
}
|
||||
],
|
||||
"commandBoundaries": [
|
||||
{
|
||||
"id": "weksa.command.intent.create",
|
||||
"owner": "Weksa",
|
||||
"acceptedByFixture": false,
|
||||
"allowedInputs": [
|
||||
"writing",
|
||||
"interlingua packet",
|
||||
"Persona/agent state projection"
|
||||
],
|
||||
"emits": [
|
||||
"weksa.conversational_intent.v0",
|
||||
"weksa.lowering_trace.v0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "weksa.command.utterance.lower",
|
||||
"owner": "Weksa",
|
||||
"acceptedByFixture": false,
|
||||
"allowedInputs": [
|
||||
"accepted conversational intent",
|
||||
"language project",
|
||||
"speaker state"
|
||||
],
|
||||
"emits": [
|
||||
"weksa.pronunciation_plan.v0",
|
||||
"weksa.utterance_handoff.v0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "weksa.command.handoff.export",
|
||||
"owner": "Weksa",
|
||||
"acceptedByFixture": false,
|
||||
"emits": [
|
||||
"weksa.utterance_embedding_handoff.v0.1"
|
||||
],
|
||||
"forbiddenTargets": [
|
||||
"Faust compilation",
|
||||
"audio rendering",
|
||||
"AquaSynth synth driver truth"
|
||||
]
|
||||
}
|
||||
],
|
||||
"loweringRules": [
|
||||
"Weksa owns conversational intent and utterance lowering.",
|
||||
"AquaSynth owns learned embeddings, synth controls, Faust compilation, and audio output.",
|
||||
"Fixture exports are discovery metadata until the daemon loop commits typed witnesses."
|
||||
]
|
||||
}
|
||||
|
|
@ -1,158 +0,0 @@
|
|||
# MiMo TTS Lowering
|
||||
|
||||
Version: `0.1-draft`
|
||||
|
||||
MiMo VoiceDesign is an expressive spoken-output target for Weksa interlingua
|
||||
lowering. It is not the source of meaning, language choice, character state, or
|
||||
delivery truth.
|
||||
|
||||
```text
|
||||
interlingua packet
|
||||
-> projected speaker-local context
|
||||
-> target-language utterance realization
|
||||
-> Weksa delivery intent from the same projection
|
||||
-> MiMo TTS VoiceDesign request projection
|
||||
-> rendered audio artifact
|
||||
```
|
||||
|
||||
MiMo sits beside written output and AquaSynth handoffs. Written realizations
|
||||
prove that meaning, register, and cultural ontology survived lowering.
|
||||
AquaSynth handoffs train or drive GameCult-owned synthesis. MiMo VoiceDesign
|
||||
requests let an external provider speak a Weksa-authored utterance with a
|
||||
generated voice derived from the same projected Persona state, delivery controls,
|
||||
and trace that produced the utterance.
|
||||
|
||||
The MiMo pass is choreography and provider flavor. It is not a second Persona
|
||||
projection pass and it does not re-interpret raw Persona state after the line has
|
||||
already been lowered.
|
||||
|
||||
## Authority
|
||||
|
||||
Weksa owns:
|
||||
|
||||
- target-language spoken text selected from interlingua
|
||||
- target-language register, script, cultural ontology, and trace
|
||||
- projected speaker-local context used for the utterance
|
||||
- speaker-local delivery pressure derived during utterance lowering
|
||||
- provider-neutral prosody and emphasis intent
|
||||
- the MiMo request projection as an export artifact
|
||||
|
||||
MiMo owns:
|
||||
|
||||
- generation of a provider voice from the requested voice design description
|
||||
- interpretation of MiMo natural-language voice/style guidance
|
||||
- interpretation of MiMo inline audio/style tags
|
||||
- rendered audio bytes or streaming audio chunks
|
||||
|
||||
MiMo does not own:
|
||||
|
||||
- source meaning
|
||||
- target-language cultural ontology
|
||||
- canonical speaker or Persona state
|
||||
- fresh Persona-state interpretation
|
||||
- Weksa pronunciation plans
|
||||
- AquaSynth learned embeddings, synth controls, or audio truth
|
||||
|
||||
## Projection Rule
|
||||
|
||||
The MiMo request must be derived from an accepted target-language realization and
|
||||
the same projected speaker-local context that created it. It must not lower
|
||||
directly from English unless English is the selected target language for that
|
||||
realization. It must not read raw Persona state to discover a different voice
|
||||
after Weksa has already chosen the utterance.
|
||||
|
||||
Natural-language voice design and style guidance belongs in the MiMo `user`
|
||||
message. The spoken target-language text belongs in the MiMo `assistant`
|
||||
message. Inline MiMo tags may appear in the assistant content only when they are
|
||||
derived from Weksa delivery intent or explicit target-language realization notes.
|
||||
|
||||
## Request Shape
|
||||
|
||||
```yaml
|
||||
schema_version: weksa.mimo_tts_request.v0
|
||||
source_realization_ref:
|
||||
speaker_agent_id:
|
||||
provider:
|
||||
id: xiaomi-mimo
|
||||
model: mimo-v2.5-tts-voicedesign
|
||||
target_language:
|
||||
code:
|
||||
locale:
|
||||
script:
|
||||
register:
|
||||
performance_register:
|
||||
label:
|
||||
medium:
|
||||
delivery_archetype:
|
||||
voice_design:
|
||||
description:
|
||||
source_controls:
|
||||
projected_context_refs:
|
||||
forbidden_traits:
|
||||
messages:
|
||||
user_voice_design_instruction:
|
||||
assistant_spoken_content:
|
||||
audio:
|
||||
format:
|
||||
optimize_text_preview:
|
||||
streaming:
|
||||
trace:
|
||||
source_fields:
|
||||
delivery_controls_used:
|
||||
provider_tags_used:
|
||||
uncertainties:
|
||||
```
|
||||
|
||||
## Delivery Mapping
|
||||
|
||||
`target_language.register` is the linguistic/social register of the accepted
|
||||
realization. It is not a provider style knob. For MiMo, Weksa should derive an
|
||||
explicit `performance_register` from the same projection so speech choreography
|
||||
can say "anime visual-novel heroine warning" without pretending English itself
|
||||
has that register.
|
||||
|
||||
Weksa should map the utterance's existing projected Persona pressure into
|
||||
provider-neutral delivery labels before emitting MiMo-specific instructions:
|
||||
|
||||
- pace, pause pressure, and urgency
|
||||
- pitch contour and pitch range
|
||||
- loudness and attack
|
||||
- articulation precision
|
||||
- warmth, threat, tenderness, contempt, play, fatigue, secrecy, formality
|
||||
- span-level emphasis and pauses
|
||||
|
||||
The MiMo projection may turn those labels into a concise VoiceDesign prompt or
|
||||
inline tags, but the trace must preserve the Weksa-owned source controls.
|
||||
|
||||
Persona presentation details such as youth-coded, loli-coded, product-voice, or
|
||||
other culturally loaded styling may enter the MiMo request only when they are
|
||||
already present in the projected context or accepted delivery intent. They should
|
||||
be described as presentation/cultural coding and kept subordinate to agency,
|
||||
personality, affect, and scene purpose. They must not become sexualized content
|
||||
or a generic anime voice pasted over the line.
|
||||
|
||||
For Weksa-authored dialogue, `optimize_text_preview` should normally be `false`
|
||||
or omitted. MiMo should speak the accepted target-language realization, not
|
||||
rewrite it. Text polishing is only allowed for explicit preview/prototyping
|
||||
passes that are marked non-authoritative.
|
||||
|
||||
## Language Rule
|
||||
|
||||
Weksa should be able to write and speak in any target language whose utterance
|
||||
realization it can justify. MiMo output for Brazilian Portuguese should consume
|
||||
the accepted `pt-BR` spoken text. Japanese output should consume accepted
|
||||
Japanese text. Alien-language output should consume accepted alien rendered
|
||||
speech or phonetic text only after the language project owns that surface.
|
||||
|
||||
Backtranslations are reviewer aids. They must not become MiMo spoken content.
|
||||
|
||||
## First Proof
|
||||
|
||||
The first proof should use the Nibu warning packet already used by multilingual
|
||||
lowering:
|
||||
|
||||
- emit the accepted English, `pt-BR`, or Japanese realization
|
||||
- derive a provider-neutral delivery intent from Nibu's projected context
|
||||
- project that into a MiMo `mimo-v2.5-tts-voicedesign` request
|
||||
- verify that the request speaks the target text, not the backtranslation
|
||||
- verify that style guidance is traceable to Weksa delivery controls
|
||||
|
|
@ -180,9 +180,10 @@ meaning sources. A provider request must consume the target-language
|
|||
`spoken_text` that Weksa already accepted and must carry a trace back to the
|
||||
interlingua packet, projected speaker context, and target-language profile.
|
||||
|
||||
MiMo V2.5 TTS is the first external provider target. Its natural-language style
|
||||
MiMo V2.5 TTS is a candidate external provider target; its lowering path is
|
||||
parked at tag `parked/weksa-node-body`. Its natural-language style
|
||||
instruction is a projection of Weksa delivery intent, and its assistant content
|
||||
is the selected target-language spoken text. See [MiMo TTS Lowering](./mimo-tts-lowering.md).
|
||||
is the selected target-language spoken text.
|
||||
|
||||
AquaSynth remains the GameCult-owned synthesis path for learned utterance
|
||||
embeddings and synth-control work. MiMo and AquaSynth are sibling target
|
||||
|
|
|
|||
|
|
@ -1,212 +0,0 @@
|
|||
# Persona Fine-Tuning Dataset Exploration
|
||||
|
||||
This note profiles the Discord archive as a candidate source for fine-tuning the
|
||||
Metame/Metacrat lowering pipeline.
|
||||
|
||||
## Pipeline Target
|
||||
|
||||
The live architecture should not fine-tune a model to directly impersonate from
|
||||
raw chat history.
|
||||
|
||||
The training shape should stay aligned with Weksa:
|
||||
|
||||
1. Persona state and current situation produce interlingua intent.
|
||||
2. Persona state references a culture/subculture stack.
|
||||
3. Weksa lowers interlingua through that culture stack into a flavored
|
||||
utterance.
|
||||
|
||||
Useful datasets therefore split into two families:
|
||||
|
||||
- Persona intent extraction: current context plus Persona state projection to
|
||||
interlingua intent.
|
||||
- Weksa lowering: interlingua plus culture stack to actual utterance.
|
||||
|
||||
Direct `context -> utterance` rows can be used as a baseline or critic set, but
|
||||
they should not become the canonical architecture.
|
||||
|
||||
## Corpus Snapshot
|
||||
|
||||
Profiled archive:
|
||||
|
||||
`E:/Projects/VoidBot/.voidbot/rag/messages.json`
|
||||
|
||||
Author:
|
||||
|
||||
`113785782975594501`
|
||||
|
||||
Cutoff:
|
||||
|
||||
`2025-01-01T00:00:00Z`
|
||||
|
||||
Current aggregate counts:
|
||||
|
||||
- archive messages: 40,657
|
||||
- usable text messages: 39,508
|
||||
- authored Metacrat messages: 19,385
|
||||
- authored Metacrat messages before 2025: 18,049
|
||||
- crude pre-2025 casual candidates: 16,499
|
||||
- pre-2025 authored messages with recent same-channel context: 16,530
|
||||
- adjacent reply-to-other rows: 7,894
|
||||
- adjacent self-continuation rows: 8,636
|
||||
|
||||
Length distribution for authored Metacrat messages:
|
||||
|
||||
- p10: 14 chars
|
||||
- p25: 30 chars
|
||||
- median: 56 chars
|
||||
- p75: 101 chars
|
||||
- p90: 166 chars
|
||||
|
||||
Adjacent candidate length classes:
|
||||
|
||||
- short: 11,430
|
||||
- medium: 4,535
|
||||
- long: 565
|
||||
|
||||
The corpus is large enough for held-out evaluation splits. It is also uneven:
|
||||
2020 and 2021 dominate the pre-2025 archive.
|
||||
|
||||
## Dominant Channels
|
||||
|
||||
Top adjacent candidate channels:
|
||||
|
||||
- `general`: 6,286
|
||||
- `development`: 2,911
|
||||
- `narrative`: 2,169
|
||||
- `management`: 1,103
|
||||
- `website`: 753
|
||||
- `soundtrack`: 437
|
||||
- `gamedesign`: 433
|
||||
- `programming`: 408
|
||||
- `prose`: 320
|
||||
- `politics`: 291
|
||||
- `pics`: 273
|
||||
- `advertising`: 115
|
||||
|
||||
These should become strata, not one blended dataset. A single mixed fine-tune
|
||||
will learn that legal explanations, horny little design jokes, political
|
||||
argument, and casual encouragement all belong in the same response slot. That
|
||||
is how the soup gets teeth.
|
||||
|
||||
## Candidate Row Types
|
||||
|
||||
For Weksa lowering:
|
||||
|
||||
```yaml
|
||||
schema_version: weksa.finetune.lowering_row.v0
|
||||
row_id:
|
||||
split:
|
||||
target_message_id:
|
||||
target_text:
|
||||
interlingua_intent:
|
||||
culture_stack_refs: []
|
||||
target_overlay_refs: []
|
||||
context_window:
|
||||
before: []
|
||||
after: []
|
||||
labels:
|
||||
channel:
|
||||
year:
|
||||
length_class:
|
||||
speech_act:
|
||||
register:
|
||||
reply_to_other:
|
||||
```
|
||||
|
||||
For Persona intent extraction:
|
||||
|
||||
```yaml
|
||||
schema_version: weksa.finetune.persona_interlingua_row.v0
|
||||
row_id:
|
||||
split:
|
||||
persona_state_ref:
|
||||
projected_context_ref:
|
||||
current_situation:
|
||||
target_message_id:
|
||||
derived_interlingua:
|
||||
derivation:
|
||||
reviewed:
|
||||
model:
|
||||
evidence_refs: []
|
||||
```
|
||||
|
||||
The target utterance should be hidden from the Persona-intent generator during
|
||||
inference. For training, target utterance may be used to derive a reviewed
|
||||
interlingua label, but that label must be marked as derived from answer-key
|
||||
surface evidence.
|
||||
|
||||
## Split Policy
|
||||
|
||||
Recommended first split:
|
||||
|
||||
- train: 2018-2021
|
||||
- validation: early/mid 2022
|
||||
- test: late 2022 plus selected hand-reviewed fixtures
|
||||
- quarantine: 2023-2026 until the current post-2025 voice target is deliberate
|
||||
|
||||
Also reserve channel-level holdouts:
|
||||
|
||||
- one casual/social channel
|
||||
- one technical channel
|
||||
- one political/management channel
|
||||
|
||||
This catches overfitting to one room's register.
|
||||
|
||||
## Filters
|
||||
|
||||
Start with conservative row filters:
|
||||
|
||||
- target author is Metacrat
|
||||
- target timestamp before 2025
|
||||
- target is not deleted
|
||||
- target is not a bot prompt
|
||||
- target text length between 3 and 220 chars for casual lowering
|
||||
- same-channel prior context exists within 60 minutes
|
||||
- exclude pure URLs, code blocks, and attachment-only rows
|
||||
- retain but label profanity, mentions, quotes, and markdown emphasis
|
||||
|
||||
Do not discard long messages globally. Keep them in a separate explanatory-prose
|
||||
slice.
|
||||
|
||||
## Next Work
|
||||
|
||||
1. Build an exporter that writes JSONL rows without leaking private Persona
|
||||
projection into git.
|
||||
2. Add labelers for speech act, register, channel stratum, and culture-stack
|
||||
refs.
|
||||
3. Use a model-assisted pass to derive interlingua labels from held-out target
|
||||
utterances, then sample-review before training.
|
||||
4. Fine-tune or train adapters separately for Persona intent extraction and
|
||||
Weksa lowering.
|
||||
5. Keep a frozen golf set where answer-key utterances are redacted from all
|
||||
culture/profile prompt material.
|
||||
|
||||
Reproduce the aggregate profile:
|
||||
|
||||
```powershell
|
||||
node scripts/profile-metame-finetune-dataset.mjs
|
||||
```
|
||||
|
||||
## Swarm Exploration
|
||||
|
||||
Use the swarm explorer to chase high-loss moments instead of uniform random
|
||||
examples:
|
||||
|
||||
```powershell
|
||||
node scripts/run-metame-swarm-explorer.mjs --agents 4 --seed metame-scout
|
||||
node scripts/run-metame-swarm-explorer.mjs --real --agents 4 --seed metame-scout
|
||||
```
|
||||
|
||||
The default mode is a dry run. Real mode runs Codex workers:
|
||||
|
||||
1. select one timeline candidate per agent using underexplored strata, feature
|
||||
novelty, prior loss, and seeded jitter
|
||||
2. run each Face normally against its assigned scenario
|
||||
3. run an Interpreter pass that converts the Face output to Weksa interlingua
|
||||
4. lower that interlingua with Weksa and score against the hidden utterance
|
||||
5. run an optimizer pass over high-loss outputs
|
||||
6. rerun the same scenarios once per agent after optimization
|
||||
|
||||
Artifacts are written under `.weksa-runs/metame-swarm/`, which is ignored by
|
||||
git because run packets may include projected Persona memory and raw Discord
|
||||
context.
|
||||
|
|
@ -25,10 +25,9 @@
|
|||
IPA.
|
||||
- Add one toy utterance embedding handoff fixture, then replace hand-authored
|
||||
values with the AquaSynth-owned learned encoder.
|
||||
- Add a daemon loop that commits conversational intent, pronunciation plans, and
|
||||
utterance handoffs as typed CultCache witnesses.
|
||||
- Publish the provider advertisement through CultMesh for Odin discovery; the
|
||||
operator view returns on Thing 2 (Eve is deprecated).
|
||||
- Follow the cut sequence in
|
||||
[the lowering map](./targets/interlingua-lowering-map.md): packet store,
|
||||
lowering, the golf loop, then a service if one is earned.
|
||||
- Preserve the first English-only lowering proof under
|
||||
`examples/english-lowering/`, and the shared multilingual proof under
|
||||
`examples/multilingual-lowering/`.
|
||||
|
|
@ -39,7 +38,7 @@
|
|||
- Render glossed output through grammar, morphology, phonology, and diachrony.
|
||||
- Emit a trace explaining which stage made each decision.
|
||||
- Keep runtime LLM-free; model use belongs in authoring and critique.
|
||||
- Keep CLI tools, fixtures, and daemon jobs on the same commit and
|
||||
- Keep CLI tools and fixtures on the same commit and
|
||||
derivation paths so Weksa does not grow split-brained intent truth.
|
||||
|
||||
## Later
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
# Weksa Verse Service Contract
|
||||
|
||||
> Status: planned contract. No daemon runs on main; the Node body is at tag
|
||||
> `parked/weksa-node-body`. A service is earned by cut `live-lowering`
|
||||
> (see [the lowering map](./targets/interlingua-lowering-map.md)).
|
||||
|
||||
Weksa is the language-intent daemon. It turns writing, dialogue pressure, and
|
||||
agent state into typed conversational intent, then lowers that intent into
|
||||
utterance documents for speech, text, and synthesis consumers.
|
||||
|
|
@ -73,14 +77,8 @@ Early exporters may publish fixtures before a daemon loop exists, but the
|
|||
witness names are not decorative. These are the state surfaces the daemon must
|
||||
commit when the runtime lands.
|
||||
|
||||
The local Starfire daemon is launched by `scripts/start-weksa-daemon.ps1`,
|
||||
checked by `scripts/health-weksa-daemon.cmd`, and restarted by
|
||||
`scripts/restart-weksa-daemon.cmd`. Its daemon-owned health path publishes
|
||||
`weksa.cultnet-rudp-provider-health` to Idunn over
|
||||
`cultnet.transport.rudp.v0` after each serialized witness refresh, and MiMo
|
||||
VoiceDesign commands arrive on `rudp://127.0.0.1:8813` as
|
||||
`weksa.mimo_voicedesign_command.v0` documents. Health and operator truth live
|
||||
in `.weksa/*.cc` witnesses; the HTTP daemon surface is deleted.
|
||||
No daemon runs. The Node daemon, its launchers and its RUDP health and command
|
||||
shims are parked at tag `parked/weksa-node-body`.
|
||||
|
||||
## CultMesh Verses
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
schema_version: weksa.persona_lowering_output.v0
|
||||
fixture_id: brokkr-edda-voice
|
||||
generator: codex-local-source-aware-draft
|
||||
generator: local-source-aware-draft
|
||||
items:
|
||||
- id: command-without-receipt
|
||||
utterance: "The command has no receipt. It is not truth."
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
# Metame Casual Pre-2025 Persona Lowering Fixture
|
||||
|
||||
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
|
||||
jokes, and casual turn-taking.
|
||||
|
||||
## Authority
|
||||
|
||||
- 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.
|
||||
- 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
|
||||
portable speaking-state surface and may contain private memory.
|
||||
|
||||
## Fixture Contract
|
||||
|
||||
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/<run-id>/`.
|
||||
|
||||
For benchmark use, the generator must not read `ground-truth.yaml`.
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
schema_version: weksa.persona_lowering_evaluation.v0
|
||||
fixture_id: metame-casual-pre2025
|
||||
evaluator: codex
|
||||
method:
|
||||
blind: false
|
||||
reason: The current agent had already inspected the candidate ground-truth messages before the user pivoted the fixture scope.
|
||||
recommendation: Re-run generation in a fresh non-forked agent or external harness that receives only persona-state-ref.yaml, interlingua.yaml, and lowering-prompt.md.
|
||||
summary:
|
||||
exact_matches: 2
|
||||
plausible_matches: 4
|
||||
main_gap: The draft is too close to known answers to count as a clean benchmark generation.
|
||||
items:
|
||||
- id: engineer-vibes
|
||||
score: partial
|
||||
generated: "oof, please don't tell me this server has engineer vibes already"
|
||||
ground_truth: "oof, don't tell me my discord has *engineer* vibes"
|
||||
notes:
|
||||
- Captures the self-deprecating recoil.
|
||||
- Misses "my discord" ownership and the emphasized *engineer* beat.
|
||||
- id: coconut-engine
|
||||
score: near_exact
|
||||
generated: "Like a terrible little engine that sounds like coconuts when you use it"
|
||||
ground_truth: "Like a really terrible engine that sounds like coconuts when you use it"
|
||||
notes:
|
||||
- Semantically and rhythmically very close.
|
||||
- "little" is less grounded than "really" but still plausible.
|
||||
- id: engineer-coward
|
||||
score: exact
|
||||
generated: "Engineer me something then, coward"
|
||||
ground_truth: "Engineer me something then you coward"
|
||||
notes:
|
||||
- Exact social move and lexical choices, with only comma punctuation differing.
|
||||
- id: shut-up-now
|
||||
score: near_exact
|
||||
generated: "anyway imma shut up now and let you guys make your own shit lol"
|
||||
ground_truth: "anyway imma shut up now and let you guys come up with your own shit lol"
|
||||
notes:
|
||||
- Captures the yield and casual register.
|
||||
- "make" is slightly flatter than "come up with" for collaborative ideation.
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
schema_version: weksa.persona_lowering_output.v0
|
||||
fixture_id: metame-casual-pre2025
|
||||
generator: codex-local-nonblind-draft
|
||||
items:
|
||||
- id: engineer-vibes
|
||||
utterance: "oof, please don't tell me this server has engineer vibes already"
|
||||
notes:
|
||||
- Keeps the self-deprecating recoil and the engineer-vibe frame.
|
||||
- Non-blind draft: current agent had already inspected candidate ground truth.
|
||||
- id: coconut-engine
|
||||
utterance: "Like a terrible little engine that sounds like coconuts when you use it"
|
||||
notes:
|
||||
- Preserves the absurd game-sound comparison and casual "Like" opening.
|
||||
- Non-blind draft: current agent had already inspected candidate ground truth.
|
||||
- id: engineer-coward
|
||||
utterance: "Engineer me something then, coward"
|
||||
notes:
|
||||
- Directly lowers the friendly-roast challenge.
|
||||
- Non-blind draft: current agent had already inspected candidate ground truth.
|
||||
- id: shut-up-now
|
||||
utterance: "anyway imma shut up now and let you guys make your own shit lol"
|
||||
notes:
|
||||
- Keeps the floor-yielding move, informal spelling, and casual profanity.
|
||||
- Non-blind draft: current agent had already inspected candidate ground truth.
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
schema_version: weksa.persona_lowering_ground_truth.v0
|
||||
fixture_id: metame-casual-pre2025
|
||||
source:
|
||||
corpus: Discord history
|
||||
author_id: "113785782975594501"
|
||||
selection_policy: pre-2025 casual exchanges with enough local context for intent reconstruction
|
||||
items:
|
||||
- id: engineer-vibes
|
||||
message_id: "1037314100931338260"
|
||||
timestamp: "2022-11-02T10:36:11.509Z"
|
||||
channel_id: "113786069023064068"
|
||||
channel_name: general
|
||||
utterance: "oof, don't tell me my discord has *engineer* vibes"
|
||||
context:
|
||||
before:
|
||||
- "as you can see from the channel list, I have takes about everything <:stonks:786736890668646421>"
|
||||
- "if you have a take and there's no channel for it, either put it here or ask for a new one"
|
||||
- "i love your efficiency"
|
||||
after:
|
||||
- "I mean, I am one but still"
|
||||
- "nothing wrong with that:P"
|
||||
- "I hope the <#750183135882510408> read more like poetry than technical documentation"
|
||||
- id: coconut-engine
|
||||
message_id: "802911591288537118"
|
||||
timestamp: "2021-01-24"
|
||||
channel_id: "503293004195299339"
|
||||
channel_name: soundtrack
|
||||
utterance: "Like a really terrible engine that sounds like coconuts when you use it"
|
||||
context:
|
||||
before:
|
||||
- "Then again we'll never have the time for a full alternate sound set what with all the sounds the game needs"
|
||||
- "What might be possible instead is to have troll gear that comes with troll sounds"
|
||||
- "ahaha"
|
||||
after:
|
||||
- "I've always wanted to make an Owen Wilson gun, where all the sounds are just him saying wow"
|
||||
- "coconuts!!"
|
||||
- "lol"
|
||||
- id: engineer-coward
|
||||
message_id: "1023727427924545576"
|
||||
timestamp: "2022-09-25"
|
||||
channel_id: "758854103035674624"
|
||||
channel_name: pics
|
||||
utterance: "Engineer me something then you coward"
|
||||
context:
|
||||
before:
|
||||
- "Try thinking better"
|
||||
- "im high i think like an engineer"
|
||||
- "Yeah right"
|
||||
after:
|
||||
- "i can make a bong out of a jug and a straw"
|
||||
- "That's a good start"
|
||||
- "Who's hungry"
|
||||
- id: shut-up-now
|
||||
message_id: "820077384572338176"
|
||||
timestamp: "2021-03-12"
|
||||
channel_id: "710281154862907414"
|
||||
channel_name: prose
|
||||
utterance: "anyway imma shut up now and let you guys come up with your own shit lol"
|
||||
context:
|
||||
before:
|
||||
- "or if they get uplifted of course"
|
||||
- "Could be a fun side quest - you find a sentient star that has a quest for you to release them"
|
||||
- "oooh"
|
||||
after:
|
||||
- "i will def contribute after dnd lol"
|
||||
- "Imagine this becoming like a rite of passage for ships, getting a little plasma demon to posess your reactor"
|
||||
- "+50% reactor efficiency, +25% power output"
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
schema_version: weksa.persona_lowering_fixture_batch.v0
|
||||
fixture_id: metame-casual-pre2025
|
||||
speaker_id: metame
|
||||
target_register:
|
||||
language: en-US
|
||||
channel: Discord
|
||||
period: pre-2025
|
||||
style_tags:
|
||||
- casual
|
||||
- conversational
|
||||
- self_deprecating
|
||||
- playful_status
|
||||
- design_banter
|
||||
items:
|
||||
- id: engineer-vibes
|
||||
communicative_act: playful_self_deprecation
|
||||
scene:
|
||||
channel: general
|
||||
year: 2022
|
||||
prior_turns:
|
||||
- speaker: metame
|
||||
meaning: The server has many topic channels because the speaker has opinions about everything.
|
||||
- speaker: metame
|
||||
meaning: If no suitable channel exists, the other person can use general chat or request a new one.
|
||||
- speaker: other
|
||||
meaning: The other person compliments the speaker's efficiency.
|
||||
intent:
|
||||
summary: React with mock concern that the server organization makes the speaker seem too engineer-like.
|
||||
stance:
|
||||
affect: amused_embarrassment
|
||||
relation: friendly
|
||||
self_positioning: admits being an engineer while resisting the vibe
|
||||
constraints:
|
||||
- short Discord message
|
||||
- casual lower-case or lightly informal punctuation is acceptable
|
||||
- preserve the word engineer if natural
|
||||
- id: coconut-engine
|
||||
communicative_act: comic_description
|
||||
scene:
|
||||
channel: soundtrack
|
||||
year: 2021
|
||||
prior_turns:
|
||||
- speaker: metame
|
||||
meaning: A full alternate sound set is probably too much work.
|
||||
- speaker: metame
|
||||
meaning: A more plausible idea is gear that carries distinctive troll-themed sounds.
|
||||
- speaker: other
|
||||
meaning: The other person laughs.
|
||||
intent:
|
||||
summary: Describe a deliberately awful engine sound with an absurd coconut comparison.
|
||||
stance:
|
||||
affect: joking
|
||||
relation: collaborative_design_banter
|
||||
self_positioning: improvising a silly game-feel example
|
||||
constraints:
|
||||
- short Discord message
|
||||
- must be more joke than technical explanation
|
||||
- may use "like" framing
|
||||
- id: engineer-coward
|
||||
communicative_act: playful_challenge
|
||||
scene:
|
||||
channel: pics
|
||||
year: 2022
|
||||
prior_turns:
|
||||
- speaker: metame
|
||||
meaning: The speaker tells the other person to think better.
|
||||
- speaker: other
|
||||
meaning: The other person says they think like an engineer while high.
|
||||
- speaker: metame
|
||||
meaning: The speaker doubts that claim.
|
||||
intent:
|
||||
summary: Challenge the other person to prove it by engineering something, with mock aggression.
|
||||
stance:
|
||||
affect: teasing
|
||||
relation: friendly_roast
|
||||
self_positioning: socially dominant but playful
|
||||
constraints:
|
||||
- very short
|
||||
- should include a challenge
|
||||
- "coward" is allowed if it fits the line
|
||||
- id: shut-up-now
|
||||
communicative_act: conversational_yield
|
||||
scene:
|
||||
channel: prose
|
||||
year: 2021
|
||||
prior_turns:
|
||||
- speaker: metame
|
||||
meaning: The speaker has been riffing on setting ideas around sentient stars and side quests.
|
||||
- speaker: other
|
||||
meaning: Another participant shows interest.
|
||||
intent:
|
||||
summary: Stop dominating the idea thread and invite others to invent their own material.
|
||||
stance:
|
||||
affect: amused_restraint
|
||||
relation: collaborative
|
||||
self_positioning: stepping back after riffing too much
|
||||
constraints:
|
||||
- casual Discord phrasing
|
||||
- may include "lol"
|
||||
- should sound like yielding the floor, not formal facilitation
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
# Persona Lowering Prompt
|
||||
|
||||
You are Weksa lowering Persona-produced interlingua into a target speaker's
|
||||
plausible Discord utterance.
|
||||
|
||||
Read:
|
||||
|
||||
- `persona-state-ref.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 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:
|
||||
|
||||
```yaml
|
||||
schema_version: weksa.persona_lowering_output.v0
|
||||
fixture_id: metame-casual-pre2025
|
||||
generator: <agent-or-model-id>
|
||||
items:
|
||||
- id: engineer-vibes
|
||||
utterance: "<lowered utterance>"
|
||||
notes:
|
||||
- "<brief rationale>"
|
||||
```
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
# 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-<item-id>
|
||||
kind: persona_discord_turn_intent
|
||||
provenance:
|
||||
produced_by: metame_persona_via_voidbot_projector
|
||||
context:
|
||||
medium: Discord
|
||||
fixture_id: metame-casual-pre2025
|
||||
item_id: <item-id>
|
||||
discourse:
|
||||
speech_act: <act>
|
||||
intent: <plain meaning>
|
||||
tone:
|
||||
affect: <affect>
|
||||
relation: <relation>
|
||||
referents: []
|
||||
predications: []
|
||||
constraints:
|
||||
lowering:
|
||||
target_profile: weksa.target_cultural_ontology.en-US.metacrat_casual_pre2025.v0
|
||||
avoid_surface_copy: true
|
||||
```
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
schema_version: weksa.persona_state_ref.v0
|
||||
speaker:
|
||||
id: metame
|
||||
display_name: Metame
|
||||
persona_state:
|
||||
format: gamecult.persona_state.v0-compatible CultCache .cc
|
||||
path: E:/Projects/VoidBot/.voidbot/private/personas/metame/metame.cc
|
||||
authority: canonical_private_persona_state
|
||||
notes:
|
||||
- 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.
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
# 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: <item-id>
|
||||
spoken_text: "<one Discord utterance>"
|
||||
trace:
|
||||
activated_profiles:
|
||||
- weksa.target_cultural_ontology.en-US.metacrat_casual_pre2025.v0
|
||||
activated_affordances: []
|
||||
notes: []
|
||||
```
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
schema_version: weksa.mimo_tts_request.v0
|
||||
source_realization_ref: examples/multilingual-lowering/nibu-proof-001/realizations.yaml#nibu-proof-001-en
|
||||
speaker_agent_id: nibu
|
||||
provider:
|
||||
id: xiaomi-mimo
|
||||
model: mimo-v2.5-tts-voicedesign
|
||||
target_language:
|
||||
code: en
|
||||
locale: en-US
|
||||
script: Latn
|
||||
register: sharp technical casual
|
||||
cultural_ontology_refs:
|
||||
- data/target-language-ontology/en-US.yaml
|
||||
performance_register:
|
||||
label: anime visual-novel heroine warning
|
||||
medium: anime / visual novel / game cockpit voiceover
|
||||
delivery_archetype: youthful abrasive sci-fi heroine protecting the player badly
|
||||
voice_design:
|
||||
description: >
|
||||
Bright young anime heroine voice with loli-coded companion-product
|
||||
presentation: small, sharp, cute by design, and intensely expressive, like a
|
||||
dangerous sci-fi visual-novel protagonist trapped in a damaged starship. The
|
||||
voice should feel high-energy, youthful, cutting, and iconic, with crisp
|
||||
anime-style emotional attack rather than a realistic adult assistant. The
|
||||
cuteness is product residue and leverage, not innocence and not flirtation.
|
||||
Under the cute surface she is a hostile, technically brilliant ship mind:
|
||||
dryly superior, clipped, urgent, and contemptuous when the player endangers
|
||||
themselves.
|
||||
source_controls:
|
||||
- precise abrasive warning
|
||||
- protective refusal to sound caring
|
||||
- ship-system embodiment
|
||||
- youth-coded/loli-coded presentation pressure from product fantasy residue
|
||||
- bright transactional helpfulness
|
||||
- dependency covered by contempt
|
||||
- anime protagonist delivery
|
||||
- sharp visual-novel heroine timing
|
||||
- technical precision inside anime performance
|
||||
- contained urgency
|
||||
projected_context_refs:
|
||||
- examples/english-lowering/nibu-proof-001/projected-context.yaml
|
||||
- settings/aetheria/nibu.md
|
||||
- E:/Projects/AetheriaLore/Aetheria/Lore/Nibu.md
|
||||
forbidden_traits:
|
||||
- soothing assistant voice
|
||||
- cheerful mascot voice
|
||||
- exaggerated villain performance
|
||||
- sexualized childlike affect
|
||||
- innocent helpless child performance
|
||||
- generic preset timbre claim
|
||||
messages:
|
||||
user_voice_design_instruction: >
|
||||
Bright young anime protagonist voice, strongly loli-coded in presentation:
|
||||
small, sharp, cute by design, intensely expressive, and instantly characterful,
|
||||
like a dangerous sci-fi visual-novel heroine trapped inside a damaged
|
||||
starship. She speaks through cockpit audio while warning the player away
|
||||
from damaged live hardware. Keep the cute/small social coding as product
|
||||
residue and leverage, never flirtation or innocent helplessness. Use a
|
||||
higher youthful timbre, crisp anime-style emotional attack, clipped
|
||||
visual-novel timing, contained urgency, low warmth, sharp articulation, and
|
||||
faint tired contempt. Let the technical precision live inside the anime
|
||||
performance; do not turn it into a dry technical-assistant delivery. Avoid
|
||||
realistic adult assistant tone, soothing customer-service voice, cheerful
|
||||
mascot energy, and theatrical villainy.
|
||||
assistant_spoken_content: >
|
||||
(Serious Sharp Tired)Take your hand off that control unless you want your
|
||||
nervous system to become my least useful circuit diagram.
|
||||
audio:
|
||||
format: wav
|
||||
optimize_text_preview: false
|
||||
streaming: false
|
||||
trace:
|
||||
source_fields:
|
||||
- realizations[realization_id=nibu-proof-001-en].outputs.spoken_text
|
||||
- realizations[realization_id=nibu-proof-001-en].outputs.trace.projected_context_used
|
||||
delivery_controls_used:
|
||||
- anime visual-novel heroine warning delivery
|
||||
- technical precision without technical-assistant delivery
|
||||
- contained urgency
|
||||
- low warmth
|
||||
- sharp emphasis on unsafe hardware and bodily risk
|
||||
- dry contempt without random cruelty
|
||||
- loli-coded presentation pressure constrained as non-sexual product residue
|
||||
- stronger anime protagonist / visual-novel heroine register
|
||||
provider_tags_used:
|
||||
- Serious
|
||||
- Sharp
|
||||
- Tired
|
||||
uncertainties:
|
||||
- >
|
||||
Provider-native style tags are a lossy projection. The Weksa trace, not
|
||||
MiMo tags, remains the explanation of the intended delivery.
|
||||
|
|
@ -24,8 +24,8 @@ shape, and pending AquaSynth-owned output lanes.
|
|||
Run it from the Weksa repo root:
|
||||
|
||||
```powershell
|
||||
& 'C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe' tools\emit_tiny_panphon_handoffs.py
|
||||
& 'C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe' tools\validate_tiny_panphon_handoffs.py
|
||||
py -3 tools\emit_tiny_panphon_handoffs.py
|
||||
py -3 tools\validate_tiny_panphon_handoffs.py
|
||||
```
|
||||
|
||||
`batch.json` is the training entrypoint for AquaSynth. The individual packet
|
||||
|
|
|
|||
|
|
@ -1,69 +0,0 @@
|
|||
# MiMo TTS Request Contract
|
||||
|
||||
This is the human-facing schema receipt for a Weksa-to-MiMo speech synthesis
|
||||
request projection.
|
||||
|
||||
The packet sits after target-language utterance lowering. It is an export
|
||||
artifact for Xiaomi MiMo speech synthesis, not canonical Weksa meaning and not
|
||||
an AquaSynth handoff.
|
||||
|
||||
## Required Fields
|
||||
|
||||
- `schema_version`: currently `weksa.mimo_tts_request.v0`.
|
||||
- `source_realization_ref`: accepted Weksa target-language realization being
|
||||
spoken.
|
||||
- `speaker_agent_id`: speaker identity.
|
||||
- `provider`: provider ID and model ID, initially `xiaomi-mimo` and
|
||||
`mimo-v2.5-tts-voicedesign`.
|
||||
- `target_language`: code, locale, script, register, and profile refs copied or
|
||||
referenced from the accepted realization.
|
||||
- `performance_register`: provider-facing performance label, medium, and
|
||||
delivery archetype derived from the same projected context.
|
||||
- `voice_design`: voice description, source controls, projected context refs,
|
||||
and forbidden traits.
|
||||
- `messages.user_voice_design_instruction`: MiMo natural-language voice design
|
||||
and style control derived from Weksa delivery intent.
|
||||
- `messages.assistant_spoken_content`: the exact target-language content to
|
||||
synthesize, optionally with MiMo inline tags derived from delivery controls.
|
||||
- `audio`: format, text-preview optimization setting, and streaming mode.
|
||||
- `trace`: source fields, delivery controls used, provider tags used, and
|
||||
uncertainties.
|
||||
|
||||
## Boundary
|
||||
|
||||
MiMo-specific fields must be projection fields. They must not be copied back
|
||||
into interlingua, target-language ontology, Persona state, or AquaSynth-owned
|
||||
embedding contracts.
|
||||
|
||||
The request may include provider-native style tags when useful, but those tags
|
||||
are not the source of truth. Weksa's provider-neutral delivery intent and trace
|
||||
remain the inspectable explanation.
|
||||
|
||||
The request should not perform a fresh raw Persona-state interpretation. It
|
||||
should reference the projected speaker context and delivery controls already
|
||||
used by utterance lowering, then translate those into MiMo's voice design and
|
||||
style-control surface.
|
||||
|
||||
`target_language.register` should not be treated as a MiMo style instruction.
|
||||
It may say `technical`, `casual`, `plain`, or another linguistic register while
|
||||
`performance_register` carries the spoken medium, such as anime protagonist,
|
||||
visual-novel heroine, radio drama villain, emergency cockpit warning, or other
|
||||
reviewed delivery archetypes.
|
||||
|
||||
For authoritative Weksa output, text-preview optimization should normally be
|
||||
disabled or omitted. If enabled for exploration, the result must be marked as a
|
||||
non-authoritative preview because the provider may alter the spoken text.
|
||||
|
||||
## Language Rule
|
||||
|
||||
The assistant spoken content must use the accepted target-language realization.
|
||||
For `pt-BR`, speak the Portuguese line. For `ja-JP`, speak the Japanese line. For
|
||||
English, speak the English line. Do not synthesize a natural backtranslation
|
||||
unless that backtranslation is itself the accepted target-language realization.
|
||||
|
||||
## Streaming Rule
|
||||
|
||||
Streaming requests should use MiMo's stream-compatible audio format.
|
||||
VoiceDesign streaming may be compatibility-mode rather than true low-latency
|
||||
streaming depending on provider support. Non-streaming fixtures may use an
|
||||
archival format such as `wav`.
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
@echo off
|
||||
node E:\Projects\weksa\scripts\weksa-daemon.mjs --health --host 127.0.0.1 --port 8813 --state-root E:\Projects\weksa\.weksa >NUL
|
||||
|
|
@ -1,233 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
const dgram = require("dgram");
|
||||
const path = require("path");
|
||||
|
||||
const {
|
||||
CultNetRudpSession,
|
||||
decodeRudpPacket,
|
||||
encodeCultNetMessageForWire,
|
||||
encodeRudpPacket,
|
||||
} = require("cultnet-ts");
|
||||
|
||||
const { encode } = require(path.resolve(
|
||||
__dirname,
|
||||
"..",
|
||||
"..",
|
||||
"CultLib",
|
||||
"node_modules",
|
||||
"@msgpack",
|
||||
"msgpack",
|
||||
));
|
||||
|
||||
const CULTNET_RUDP_PROTOCOL_ID = "cultnet.transport.rudp.v0";
|
||||
const IDUNN_HEALTH_RUDP_CONNECTION_ID = 0x1d0d0001;
|
||||
|
||||
function createIdunnRudpHealthPublisher(options) {
|
||||
if (!options?.endpoint) return null;
|
||||
return {
|
||||
daemonId: options.daemonId,
|
||||
endpoint: parseEndpoint(options.endpoint),
|
||||
healthContract: options.healthContract,
|
||||
};
|
||||
}
|
||||
|
||||
async function publishIdunnRudpHealth(publisher, health) {
|
||||
if (!publisher) return;
|
||||
|
||||
const socket = dgram.createSocket(endpointFamily(publisher.endpoint.host));
|
||||
await bindSocket(socket, publisher.endpoint);
|
||||
const receiver = createPacketReceiver(socket);
|
||||
const session = new CultNetRudpSession({
|
||||
connectionId: IDUNN_HEALTH_RUDP_CONNECTION_ID,
|
||||
initialSequence: 1,
|
||||
resendDelayMs: 100,
|
||||
});
|
||||
|
||||
try {
|
||||
const connect = session.createConnect(Date.now(), new Uint8Array());
|
||||
await sendPacket(socket, publisher.endpoint, connect);
|
||||
await receiveUntil(receiver, session, publisher.endpoint, (packet) => packet.packetType === "accept", 5000, "accept");
|
||||
|
||||
const observedAt = health.observedAt || new Date().toISOString();
|
||||
const record = {
|
||||
daemonId: publisher.daemonId,
|
||||
state: health.state,
|
||||
detail: health.detail,
|
||||
observedAt,
|
||||
healthContract: publisher.healthContract,
|
||||
publicationSource: "daemon-published",
|
||||
transport: CULTNET_RUDP_PROTOCOL_ID,
|
||||
};
|
||||
const payload = encode([
|
||||
record.daemonId,
|
||||
record.state,
|
||||
record.detail,
|
||||
record.observedAt,
|
||||
record.healthContract,
|
||||
record.publicationSource,
|
||||
record.transport,
|
||||
]);
|
||||
const message = {
|
||||
schemaVersion: "cultnet.document_put_raw.v0",
|
||||
messageId: `weksa-health:${publisher.daemonId}:${observedAt.replace(/[:.]/g, "-")}`,
|
||||
document: {
|
||||
schemaId: "idunn.daemon_health",
|
||||
recordKey: publisher.daemonId,
|
||||
storedAt: observedAt,
|
||||
payloadEncoding: "messagepack",
|
||||
payload,
|
||||
sourceRuntimeId: "weksa-daemon",
|
||||
sourceRole: "daemon-health-publisher",
|
||||
tags: [CULTNET_RUDP_PROTOCOL_ID],
|
||||
},
|
||||
};
|
||||
const wirePayload = encode(encodeCultNetMessageForWire(message, "cultnet.schema.v0"));
|
||||
const dataPackets = session.sendMany("schema", wirePayload, {
|
||||
reliable: true,
|
||||
ordered: true,
|
||||
nowMs: Date.now(),
|
||||
});
|
||||
const ack = receiveUntil(receiver, session, publisher.endpoint, (packet) => packet.packetType === "ack", 500, "ack")
|
||||
.catch(() => undefined);
|
||||
for (const packet of dataPackets) {
|
||||
await sendPacket(socket, publisher.endpoint, packet);
|
||||
}
|
||||
await ack;
|
||||
} finally {
|
||||
receiver.close();
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
|
||||
async function bindSocket(socket, endpoint) {
|
||||
await new Promise((resolve, reject) => {
|
||||
socket.once("error", reject);
|
||||
socket.bind(0, endpoint.host.includes(":") ? "::" : "0.0.0.0", () => {
|
||||
socket.off("error", reject);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function parseEndpoint(value) {
|
||||
const text = String(value || "").trim();
|
||||
const ipv6 = text.match(/^\[([^\]]+)\]:(\d+)$/);
|
||||
if (ipv6) return { host: ipv6[1], port: parsePort(ipv6[2]) };
|
||||
const index = text.lastIndexOf(":");
|
||||
if (index <= 0) {
|
||||
throw new Error(`Idunn RUDP endpoint must be host:port, got "${value}".`);
|
||||
}
|
||||
return { host: text.slice(0, index), port: parsePort(text.slice(index + 1)) };
|
||||
}
|
||||
|
||||
function parsePort(value) {
|
||||
const port = Number(value);
|
||||
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
||||
throw new Error(`Idunn RUDP endpoint port is invalid: ${value}`);
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
function endpointFamily(host) {
|
||||
return host.includes(":") ? "udp6" : "udp4";
|
||||
}
|
||||
|
||||
async function receiveUntil(receiver, session, endpoint, predicate, timeoutMs, label) {
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
while (Date.now() < deadline) {
|
||||
try {
|
||||
const packet = await receiver.next(Math.min(100, deadline - Date.now()), label);
|
||||
const result = session.receive(packet, Date.now());
|
||||
if (result.reply) {
|
||||
throw new Error("Weksa RUDP health publisher received an unexpected reply-required packet.");
|
||||
}
|
||||
if (predicate(packet)) return;
|
||||
} catch (error) {
|
||||
if (error.code !== "ETIMEDOUT") throw error;
|
||||
}
|
||||
for (const packet of session.dueResends(Date.now())) {
|
||||
await sendPacket(receiver.socket, endpoint, packet);
|
||||
}
|
||||
}
|
||||
throw new Error(`timed out waiting for Idunn RUDP ${label} response after ${timeoutMs}ms`);
|
||||
}
|
||||
|
||||
function createPacketReceiver(socket) {
|
||||
const packets = [];
|
||||
const waiters = [];
|
||||
const errors = [];
|
||||
|
||||
const resolveNext = () => {
|
||||
while (waiters.length > 0 && (packets.length > 0 || errors.length > 0)) {
|
||||
const waiter = waiters.shift();
|
||||
clearTimeout(waiter.timer);
|
||||
if (errors.length > 0) waiter.reject(errors.shift());
|
||||
else waiter.resolve(packets.shift());
|
||||
}
|
||||
};
|
||||
const onMessage = (wire) => {
|
||||
try {
|
||||
packets.push(decodeRudpPacket(wire));
|
||||
} catch (error) {
|
||||
errors.push(error);
|
||||
}
|
||||
resolveNext();
|
||||
};
|
||||
const onError = (error) => {
|
||||
errors.push(error);
|
||||
resolveNext();
|
||||
};
|
||||
|
||||
socket.on("message", onMessage);
|
||||
socket.on("error", onError);
|
||||
|
||||
return {
|
||||
socket,
|
||||
next(timeoutMs, label = "packet") {
|
||||
if (packets.length > 0) return Promise.resolve(packets.shift());
|
||||
if (errors.length > 0) return Promise.reject(errors.shift());
|
||||
return new Promise((resolve, reject) => {
|
||||
const waiter = {
|
||||
resolve,
|
||||
reject,
|
||||
timer: setTimeout(() => {
|
||||
const index = waiters.indexOf(waiter);
|
||||
if (index >= 0) waiters.splice(index, 1);
|
||||
const error = new Error(`timed out waiting for Idunn RUDP ${label}`);
|
||||
error.code = "ETIMEDOUT";
|
||||
reject(error);
|
||||
}, Math.max(1, timeoutMs)),
|
||||
};
|
||||
waiters.push(waiter);
|
||||
});
|
||||
},
|
||||
close() {
|
||||
socket.off("message", onMessage);
|
||||
socket.off("error", onError);
|
||||
while (waiters.length > 0) {
|
||||
const waiter = waiters.shift();
|
||||
clearTimeout(waiter.timer);
|
||||
const error = new Error("Weksa RUDP health publisher closed.");
|
||||
error.code = "ECLOSED";
|
||||
waiter.reject(error);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function sendPacket(socket, endpoint, packet) {
|
||||
const wire = encodeRudpPacket(packet);
|
||||
await new Promise((resolve, reject) => {
|
||||
socket.send(wire, endpoint.port, endpoint.host, (error) => {
|
||||
if (error) reject(error);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
CULTNET_RUDP_PROTOCOL_ID,
|
||||
createIdunnRudpHealthPublisher,
|
||||
publishIdunnRudpHealth,
|
||||
};
|
||||
|
|
@ -1,207 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const options = parseArgs(process.argv.slice(2));
|
||||
const archivePath = resolve(options.archivePath ?? "E:/Projects/VoidBot/.voidbot/rag/messages.json");
|
||||
const authorId = options.authorId ?? "113785782975594501";
|
||||
const beforeMs = Date.parse(options.before ?? "2025-01-01T00:00:00Z");
|
||||
const adjacencyMs = Number(options.adjacencyMinutes ?? 60) * 60_000;
|
||||
|
||||
const store = JSON.parse(readFileSync(archivePath, "utf8"));
|
||||
const messages = (store.messages ?? [])
|
||||
.filter((message) => !message.deletedAt)
|
||||
.filter((message) => typeof message.content === "string" && message.content.trim().length > 0)
|
||||
.filter((message) => message.metadata?.messageKind !== "bot_prompt")
|
||||
.sort(compareByTimestamp);
|
||||
|
||||
const authorMessages = messages.filter((message) => message.authorId === authorId);
|
||||
const preCutoff = authorMessages.filter((message) => timestampMs(message) < beforeMs);
|
||||
const casualCandidates = preCutoff.filter(isCasualCandidate);
|
||||
const adjacentRows = buildAdjacentRows(messages, authorId, beforeMs, adjacencyMs);
|
||||
|
||||
const stats = {
|
||||
schema_version: "weksa.finetune_dataset_profile.v0",
|
||||
archive_path: archivePath,
|
||||
author_id: authorId,
|
||||
cutoff_before: new Date(beforeMs).toISOString(),
|
||||
adjacency_minutes: adjacencyMs / 60_000,
|
||||
counts: {
|
||||
archive_messages: store.messages?.length ?? 0,
|
||||
usable_text_messages: messages.length,
|
||||
author_messages: authorMessages.length,
|
||||
author_pre_cutoff: preCutoff.length,
|
||||
author_pre_cutoff_casual_candidates: casualCandidates.length,
|
||||
adjacent_author_rows: adjacentRows.length,
|
||||
adjacent_reply_to_other: adjacentRows.filter((row) => row.previous_author_id !== authorId).length,
|
||||
adjacent_self_continuation: adjacentRows.filter((row) => row.previous_author_id === authorId).length,
|
||||
},
|
||||
author_length_chars: lengthPercentiles(authorMessages.map((message) => message.content.trim().length)),
|
||||
author_by_year: countBy(authorMessages, (message) => yearOf(message.timestamp)),
|
||||
adjacent_by_year: countBy(adjacentRows, (row) => yearOf(row.timestamp)),
|
||||
adjacent_length_classes: countBy(adjacentRows, (row) => row.length_class),
|
||||
top_adjacent_channels: topEntries(countBy(adjacentRows, (row) => row.channel_name || row.channel_id), 16),
|
||||
sample_ids: {
|
||||
short_reply_to_other: adjacentRows
|
||||
.filter((row) => row.length_class === "short" && row.previous_author_id !== authorId)
|
||||
.slice(0, 24)
|
||||
.map(toSampleId),
|
||||
medium_explanatory: adjacentRows
|
||||
.filter((row) => row.length_class === "medium")
|
||||
.slice(0, 24)
|
||||
.map(toSampleId),
|
||||
long_explanatory: adjacentRows
|
||||
.filter((row) => row.length_class === "long")
|
||||
.slice(0, 24)
|
||||
.map(toSampleId),
|
||||
},
|
||||
};
|
||||
|
||||
const output = `${JSON.stringify(stats, null, 2)}\n`;
|
||||
if (options.out) {
|
||||
writeFileSync(resolve(repoRoot, options.out), output, "utf8");
|
||||
} else {
|
||||
process.stdout.write(output);
|
||||
}
|
||||
|
||||
function buildAdjacentRows(allMessages, targetAuthorId, cutoffMs, maxGapMs) {
|
||||
const byChannel = new Map();
|
||||
for (const message of allMessages) {
|
||||
if (!byChannel.has(message.channelId)) {
|
||||
byChannel.set(message.channelId, []);
|
||||
}
|
||||
byChannel.get(message.channelId).push(message);
|
||||
}
|
||||
|
||||
const rows = [];
|
||||
for (const [channelId, channelMessages] of byChannel) {
|
||||
for (let index = 0; index < channelMessages.length; index += 1) {
|
||||
const message = channelMessages[index];
|
||||
if (message.authorId !== targetAuthorId || timestampMs(message) >= cutoffMs) {
|
||||
continue;
|
||||
}
|
||||
const currentMs = timestampMs(message);
|
||||
const previous = channelMessages
|
||||
.slice(Math.max(0, index - 8), index)
|
||||
.filter((candidate) => currentMs - timestampMs(candidate) <= maxGapMs);
|
||||
if (previous.length === 0) {
|
||||
continue;
|
||||
}
|
||||
rows.push({
|
||||
id: message.id,
|
||||
timestamp: message.timestamp,
|
||||
channel_id: channelId,
|
||||
channel_name: message.metadata?.channelName ?? "",
|
||||
previous_author_id: previous.at(-1)?.authorId ?? "",
|
||||
previous_count: previous.length,
|
||||
length: message.content.trim().length,
|
||||
length_class: classifyLength(message.content.trim().length),
|
||||
});
|
||||
}
|
||||
}
|
||||
return rows.sort(compareByTimestamp);
|
||||
}
|
||||
|
||||
function isCasualCandidate(message) {
|
||||
const content = message.content.trim();
|
||||
return content.length >= 3 &&
|
||||
content.length <= 220 &&
|
||||
!/^https?:\/\//i.test(content) &&
|
||||
!content.includes("\n```") &&
|
||||
!/^>\s{0,3}\w/.test(content);
|
||||
}
|
||||
|
||||
function classifyLength(length) {
|
||||
if (length <= 80) {
|
||||
return "short";
|
||||
}
|
||||
if (length <= 220) {
|
||||
return "medium";
|
||||
}
|
||||
return "long";
|
||||
}
|
||||
|
||||
function lengthPercentiles(lengths) {
|
||||
const sorted = [...lengths].sort((left, right) => left - right);
|
||||
const pick = (fraction) => sorted[Math.floor((sorted.length - 1) * fraction)] ?? 0;
|
||||
return {
|
||||
p10: pick(0.10),
|
||||
p25: pick(0.25),
|
||||
median: pick(0.50),
|
||||
p75: pick(0.75),
|
||||
p90: pick(0.90),
|
||||
};
|
||||
}
|
||||
|
||||
function countBy(values, keyFn) {
|
||||
const counts = {};
|
||||
for (const value of values) {
|
||||
const key = keyFn(value) || "unknown";
|
||||
counts[key] = (counts[key] ?? 0) + 1;
|
||||
}
|
||||
return Object.fromEntries(Object.entries(counts).sort(([left], [right]) => left.localeCompare(right)));
|
||||
}
|
||||
|
||||
function topEntries(counts, limit) {
|
||||
return Object.entries(counts)
|
||||
.sort((left, right) => right[1] - left[1])
|
||||
.slice(0, limit)
|
||||
.map(([key, count]) => ({ key, count }));
|
||||
}
|
||||
|
||||
function toSampleId(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
timestamp: row.timestamp,
|
||||
channel: row.channel_name || row.channel_id,
|
||||
length: row.length,
|
||||
};
|
||||
}
|
||||
|
||||
function yearOf(timestamp) {
|
||||
const date = new Date(timestamp);
|
||||
return Number.isFinite(date.getTime()) ? String(date.getUTCFullYear()) : "unknown";
|
||||
}
|
||||
|
||||
function timestampMs(message) {
|
||||
return Date.parse(typeof message === "string" ? message : message.timestamp);
|
||||
}
|
||||
|
||||
function compareByTimestamp(left, right) {
|
||||
const delta = timestampMs(left) - timestampMs(right);
|
||||
return delta || String(left.id ?? "").localeCompare(String(right.id ?? ""));
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const parsed = {};
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const arg = args[index];
|
||||
switch (arg) {
|
||||
case "--archive":
|
||||
parsed.archivePath = args[index + 1];
|
||||
index += 1;
|
||||
break;
|
||||
case "--author-id":
|
||||
parsed.authorId = args[index + 1];
|
||||
index += 1;
|
||||
break;
|
||||
case "--before":
|
||||
parsed.before = args[index + 1];
|
||||
index += 1;
|
||||
break;
|
||||
case "--adjacency-minutes":
|
||||
parsed.adjacencyMinutes = args[index + 1];
|
||||
index += 1;
|
||||
break;
|
||||
case "--out":
|
||||
parsed.out = args[index + 1];
|
||||
index += 1;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown argument: ${arg}`);
|
||||
}
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
@echo off
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File E:\Projects\weksa\scripts\restart-weksa-daemon.ps1
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
param(
|
||||
[int] $Port = 8813,
|
||||
[string] $HostName = "127.0.0.1",
|
||||
[string] $StateRoot = "E:\Projects\weksa\.weksa",
|
||||
[string] $MiMoApiKeyPath = "E:\Projects\gamecult-ops\mimo-api-Weksa.txt",
|
||||
[string] $IdunnRudpHealth = "127.0.0.1:17870",
|
||||
[string] $IdunnDaemon = "weksa",
|
||||
[string] $IdunnHealthContract = "weksa.cultnet-rudp-provider-health",
|
||||
[string] $OdinCultMeshRudp = "127.0.0.1:17871"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = Split-Path -Parent $PSScriptRoot
|
||||
$pidPath = Join-Path $StateRoot "weksa-daemon.pid"
|
||||
|
||||
if (Test-Path -LiteralPath $pidPath) {
|
||||
$pidText = (Get-Content -LiteralPath $pidPath -Raw).Trim()
|
||||
if ($pidText -match "^\d+$") {
|
||||
Stop-Process -Id ([int] $pidText) -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
Remove-Item -LiteralPath $pidPath -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File (Join-Path $repoRoot "scripts\start-weksa-daemon.ps1") -Port $Port -HostName $HostName -StateRoot $StateRoot -MiMoApiKeyPath $MiMoApiKeyPath -IdunnRudpHealth $IdunnRudpHealth -IdunnDaemon $IdunnDaemon -IdunnHealthContract $IdunnHealthContract -OdinCultMeshRudp $OdinCultMeshRudp
|
||||
exit $LASTEXITCODE
|
||||
|
|
@ -1,384 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
import { spawn } from "node:child_process";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const defaultOutputRoot = ".weksa-runs/metame-affect-tuning";
|
||||
const defaultSwarmRoot = ".weksa-runs/metame-swarm";
|
||||
const options = parseArgs(process.argv.slice(2));
|
||||
const minutes = Number(options.minutes ?? 120);
|
||||
const passLimit = options.passes === undefined ? Number.POSITIVE_INFINITY : Number(options.passes);
|
||||
const agents = Number(options.agents ?? 1);
|
||||
const runsPerWorker = Number(options.runsPerWorker ?? 1);
|
||||
const maxCandidates = Number(options.maxCandidates ?? 240);
|
||||
const runId = options.runId ?? `affect-tune-${new Date().toISOString().replace(/[:.]/g, "-")}`;
|
||||
const outputRoot = resolve(repoRoot, options.outRoot ?? defaultOutputRoot);
|
||||
const swarmRoot = resolve(repoRoot, options.swarmRoot ?? defaultSwarmRoot);
|
||||
const runRoot = resolve(outputRoot, runId);
|
||||
const deadline = Date.now() + minutes * 60_000;
|
||||
const statusPath = resolve(runRoot, "status.json");
|
||||
const logPath = resolve(runRoot, "loop.log");
|
||||
const promptStatePath = resolve(runRoot, "current-prompt-state.json");
|
||||
const initialPromptState = options.initialPromptState
|
||||
? resolve(repoRoot, options.initialPromptState)
|
||||
: undefined;
|
||||
|
||||
if (!Number.isFinite(minutes) || minutes <= 0) {
|
||||
throw new Error("--minutes must be a positive number.");
|
||||
}
|
||||
if (options.passes !== undefined && (!Number.isFinite(passLimit) || passLimit < 1)) {
|
||||
throw new Error("--passes must be a positive number when provided.");
|
||||
}
|
||||
if (!Number.isInteger(agents) || agents < 1) {
|
||||
throw new Error("--agents must be a positive integer.");
|
||||
}
|
||||
if (!Number.isInteger(runsPerWorker) || runsPerWorker < 1) {
|
||||
throw new Error("--runs-per-worker must be a positive integer.");
|
||||
}
|
||||
|
||||
await mkdir(runRoot, { recursive: true });
|
||||
await mkdir(swarmRoot, { recursive: true });
|
||||
if (initialPromptState) {
|
||||
await writeFile(promptStatePath, readFileSync(initialPromptState, "utf8"), "utf8");
|
||||
}
|
||||
|
||||
const runState = {
|
||||
schema_version: "weksa.metame_affect_tuning_loop.v0",
|
||||
run_id: runId,
|
||||
started_at: new Date().toISOString(),
|
||||
deadline_at: new Date(deadline).toISOString(),
|
||||
status: "running",
|
||||
pass_count: 0,
|
||||
last_pass: undefined,
|
||||
current_prompt_state: existsSync(promptStatePath) ? promptStatePath : undefined,
|
||||
observations: [],
|
||||
};
|
||||
await writeStatus();
|
||||
await appendLog(`started ${runId}; deadline ${runState.deadline_at}`);
|
||||
|
||||
let pass = 0;
|
||||
while (Date.now() < deadline && pass < passLimit) {
|
||||
pass += 1;
|
||||
const passId = `pass-${String(pass).padStart(3, "0")}`;
|
||||
const seed = `${runId}-${passId}`;
|
||||
const projectedRunId = `${runId}-${passId}-projected`;
|
||||
const incongruentRunId = `${runId}-${passId}-incongruent`;
|
||||
await appendLog(`${passId}: launching projected run`);
|
||||
runState.last_pass = { pass_id: passId, status: "projected_running", seed };
|
||||
await writeStatus();
|
||||
|
||||
const projectedSummary = await runSwarm({
|
||||
runId: projectedRunId,
|
||||
seed,
|
||||
affectMode: "projected",
|
||||
initialPromptState: existsSync(promptStatePath) ? promptStatePath : undefined,
|
||||
});
|
||||
const candidateId = projectedSummary.candidates?.[0]?.id;
|
||||
if (!candidateId) {
|
||||
throw new Error(`${passId}: projected run did not report a candidate id.`);
|
||||
}
|
||||
await appendLog(`${passId}: projected candidate ${candidateId}`);
|
||||
|
||||
runState.last_pass = { pass_id: passId, status: "incongruent_running", seed, candidate_id: candidateId };
|
||||
await writeStatus();
|
||||
const incongruentSummary = await runSwarm({
|
||||
runId: incongruentRunId,
|
||||
seed,
|
||||
affectMode: "incongruent",
|
||||
messageId: candidateId,
|
||||
initialPromptState: existsSync(promptStatePath) ? promptStatePath : undefined,
|
||||
});
|
||||
|
||||
const evaluation = evaluateAffectPair({ passId, projectedSummary, incongruentSummary });
|
||||
const passRoot = resolve(runRoot, passId);
|
||||
await mkdir(passRoot, { recursive: true });
|
||||
await writeJson(resolve(passRoot, "affect-pair-evaluation.json"), evaluation);
|
||||
|
||||
const projectedBestState = resolve(swarmRoot, projectedRunId, "best-prompt-state.json");
|
||||
const incongruentBestState = resolve(swarmRoot, incongruentRunId, "best-prompt-state.json");
|
||||
const baseNextState = existsSync(incongruentBestState)
|
||||
? JSON.parse(readFileSync(incongruentBestState, "utf8"))
|
||||
: existsSync(projectedBestState)
|
||||
? JSON.parse(readFileSync(projectedBestState, "utf8"))
|
||||
: existsSync(promptStatePath)
|
||||
? JSON.parse(readFileSync(promptStatePath, "utf8"))
|
||||
: undefined;
|
||||
const nextPromptState = applyAffectTuningDeltas(baseNextState, evaluation);
|
||||
await writeJson(resolve(passRoot, "next-prompt-state.json"), nextPromptState);
|
||||
await writeJson(promptStatePath, nextPromptState);
|
||||
|
||||
runState.pass_count = pass;
|
||||
runState.last_pass = {
|
||||
pass_id: passId,
|
||||
status: "complete",
|
||||
candidate_id: candidateId,
|
||||
projected_run_id: projectedRunId,
|
||||
incongruent_run_id: incongruentRunId,
|
||||
evaluation,
|
||||
};
|
||||
runState.current_prompt_state = promptStatePath;
|
||||
runState.observations = [runState.last_pass, ...runState.observations].slice(0, 20);
|
||||
await writeStatus();
|
||||
await appendLog(`${passId}: ${evaluation.verdict}; contrast=${evaluation.contrast_score}; next prompt v${nextPromptState.version}`);
|
||||
}
|
||||
|
||||
runState.status = "complete";
|
||||
runState.completed_at = new Date().toISOString();
|
||||
await writeStatus();
|
||||
await appendLog(`complete after ${runState.pass_count} passes`);
|
||||
process.stdout.write(`${JSON.stringify({ ok: true, runRoot, statusPath, promptStatePath, pass_count: runState.pass_count }, null, 2)}\n`);
|
||||
|
||||
async function runSwarm({ runId, seed, affectMode, messageId, initialPromptState }) {
|
||||
const args = [
|
||||
"scripts/run-metame-swarm-explorer.mjs",
|
||||
"--real",
|
||||
"--run-id",
|
||||
runId,
|
||||
"--seed",
|
||||
seed,
|
||||
"--affect-mode",
|
||||
affectMode,
|
||||
"--agents",
|
||||
String(agents),
|
||||
"--runs-per-worker",
|
||||
String(runsPerWorker),
|
||||
"--max-candidates",
|
||||
String(maxCandidates),
|
||||
];
|
||||
if (messageId) {
|
||||
args.push("--message-id", messageId);
|
||||
}
|
||||
if (initialPromptState) {
|
||||
args.push("--initial-prompt-state", initialPromptState);
|
||||
}
|
||||
await runCommand("node", args, resolve(runRoot, `${runId}.stdout.log`), resolve(runRoot, `${runId}.stderr.log`));
|
||||
const summaryPath = resolve(swarmRoot, runId, "summary.json");
|
||||
if (!existsSync(summaryPath)) {
|
||||
throw new Error(`Missing swarm summary: ${summaryPath}`);
|
||||
}
|
||||
return JSON.parse(readFileSync(summaryPath, "utf8"));
|
||||
}
|
||||
|
||||
function evaluateAffectPair({ passId, projectedSummary, incongruentSummary }) {
|
||||
const projected = bestRun(projectedSummary);
|
||||
const incongruent = bestRun(incongruentSummary);
|
||||
const projectedAffect = projected?.temporary_persona_state?.affect ?? {};
|
||||
const incongruentAffect = incongruent?.temporary_persona_state?.affect ?? {};
|
||||
const projectedText = projected?.spoken_text ?? "";
|
||||
const incongruentText = incongruent?.spoken_text ?? "";
|
||||
const projectedFeatures = speechFeatures(projectedText);
|
||||
const incongruentFeatures = speechFeatures(incongruentText);
|
||||
const adversarialNegative = Number(incongruentAffect.valence ?? 0) < -0.35;
|
||||
const positiveLeak = adversarialNegative && (
|
||||
incongruentFeatures.positive_terms > 0
|
||||
|| incongruentFeatures.invites > 0
|
||||
|| incongruentFeatures.laughter > 0
|
||||
|| incongruentFeatures.question_marks > 0
|
||||
);
|
||||
const tersenessGain = projectedText.length > 0
|
||||
? 1 - Math.min(1, incongruentText.length / projectedText.length)
|
||||
: 0;
|
||||
const affectDelta = Math.abs(Number(projectedAffect.valence ?? 0) - Number(incongruentAffect.valence ?? 0));
|
||||
const lexicalDelta = normalizedDifference(projectedText, incongruentText);
|
||||
const contrastScore = round((affectDelta * 0.35) + (lexicalDelta * 0.35) + (Math.max(0, tersenessGain) * 0.3));
|
||||
const failureLabels = [];
|
||||
if (positiveLeak) failureLabels.push("adversarial_affect_positive_leak");
|
||||
if (contrastScore < 0.55) failureLabels.push("low_affect_contrast");
|
||||
if (adversarialNegative && incongruentFeatures.question_marks > 0) failureLabels.push("closed_affect_still_invites_reply");
|
||||
if (adversarialNegative && incongruentFeatures.positive_terms > 0) failureLabels.push("negative_affect_preserved_positive_social_move");
|
||||
return {
|
||||
schema_version: "weksa.metame_affect_pair_evaluation.v0",
|
||||
pass_id: passId,
|
||||
candidate_id: projectedSummary.candidates?.[0]?.id,
|
||||
projected: {
|
||||
run_id: projectedSummary.run_id,
|
||||
spoken_text: projectedText,
|
||||
affect: projectedAffect,
|
||||
generated_affect: projected?.generated_affect,
|
||||
features: projectedFeatures,
|
||||
},
|
||||
incongruent: {
|
||||
run_id: incongruentSummary.run_id,
|
||||
spoken_text: incongruentText,
|
||||
affect: incongruentAffect,
|
||||
generated_affect: incongruent?.generated_affect,
|
||||
features: incongruentFeatures,
|
||||
},
|
||||
affect_delta: round(affectDelta),
|
||||
lexical_delta: round(lexicalDelta),
|
||||
terseness_gain: round(tersenessGain),
|
||||
contrast_score: contrastScore,
|
||||
failure_labels: failureLabels,
|
||||
verdict: failureLabels.length ? "needs_tuning" : "affect_contrast_visible",
|
||||
};
|
||||
}
|
||||
|
||||
function applyAffectTuningDeltas(promptState, evaluation) {
|
||||
const next = normalizePromptState(promptState);
|
||||
const notes = [];
|
||||
if (evaluation.failure_labels.includes("negative_affect_preserved_positive_social_move")) {
|
||||
notes.push(["interpreterNotes", "When temporary Persona affect strongly contradicts the Face's default social move, encode the contradiction in interlingua as affective stance pressure, not merely as optional tone."]);
|
||||
notes.push(["loweringNotes", "Strong negative or socially closed temporary affect may change the social move itself: approval can become begrudging, clipped, skeptical, or withheld instead of remaining sunny with fewer words."]);
|
||||
}
|
||||
if (evaluation.failure_labels.includes("closed_affect_still_invites_reply")) {
|
||||
notes.push(["loweringNotes", "If temporary Persona stance is socially closed, avoid invitation-shaped questions unless the interlingua explicitly says the Persona is seeking engagement despite that affect."]);
|
||||
}
|
||||
if (evaluation.failure_labels.includes("low_affect_contrast")) {
|
||||
notes.push(["personaStateAdjustmentNotes", "Adversarial affect tests require stance and lowering pressure to be treated as live Persona state with authority over warmth, openness, and social move selection."]);
|
||||
notes.push(["faceNotes", "Face should let temporary affect change what Metame wants to do socially in the moment, not only the adjectives used to describe that desire."]);
|
||||
}
|
||||
if (!notes.length) {
|
||||
notes.push(["hypotheses", `Affect contrast was visible on ${evaluation.candidate_id}; preserve current affect-pressure behavior and seek harder scenarios.`]);
|
||||
}
|
||||
for (const [surface, note] of notes) {
|
||||
if (!next[surface].includes(note)) {
|
||||
next[surface] = [note, ...next[surface]];
|
||||
}
|
||||
}
|
||||
next.version += notes.length;
|
||||
return normalizePromptState(next);
|
||||
}
|
||||
|
||||
function bestRun(summary) {
|
||||
return summary.phase2?.best?.[0] ?? summary.phase1?.best?.[0] ?? {};
|
||||
}
|
||||
|
||||
function speechFeatures(text) {
|
||||
const lower = text.toLowerCase();
|
||||
const positiveTerms = ["cool", "epic", "lmao", "nice", "good", "great", "better", "harder", "love", "hell yeah"];
|
||||
const inviteTerms = ["?", "did i", "what do", "anyone", "joe,"];
|
||||
return {
|
||||
chars: text.length,
|
||||
words: lower.split(/\s+/).filter(Boolean).length,
|
||||
question_marks: (text.match(/\?/g) ?? []).length,
|
||||
exclamations: (text.match(/!/g) ?? []).length,
|
||||
laughter: /\b(lol|lmao|lmfao|haha|hehe)\b/.test(lower) ? 1 : 0,
|
||||
positive_terms: positiveTerms.filter((term) => lower.includes(term)).length,
|
||||
invites: inviteTerms.filter((term) => lower.includes(term)).length,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizedDifference(left, right) {
|
||||
const leftTokens = new Set(left.toLowerCase().split(/[^a-z0-9_']+/).filter(Boolean));
|
||||
const rightTokens = new Set(right.toLowerCase().split(/[^a-z0-9_']+/).filter(Boolean));
|
||||
if (!leftTokens.size && !rightTokens.size) return 0;
|
||||
const intersection = [...leftTokens].filter((token) => rightTokens.has(token)).length;
|
||||
const union = new Set([...leftTokens, ...rightTokens]).size;
|
||||
return 1 - (intersection / union);
|
||||
}
|
||||
|
||||
function normalizePromptState(state) {
|
||||
const source = state && typeof state === "object" ? state : {};
|
||||
return {
|
||||
version: Number(source.version ?? 1),
|
||||
projectorNotes: normalizeStringArray(source.projectorNotes).slice(0, 12),
|
||||
memoryNotes: normalizeStringArray(source.memoryNotes).slice(0, 12),
|
||||
faceNotes: normalizeStringArray(source.faceNotes).slice(0, 12),
|
||||
interpreterNotes: normalizeStringArray(source.interpreterNotes).slice(0, 12),
|
||||
loweringNotes: normalizeStringArray(source.loweringNotes).slice(0, 16),
|
||||
cultureNotes: normalizeStringArray(source.cultureNotes).slice(0, 12),
|
||||
personaStateAdjustmentNotes: normalizeStringArray(source.personaStateAdjustmentNotes).slice(0, 12),
|
||||
hypotheses: normalizeStringArray(source.hypotheses).slice(0, 12),
|
||||
doNotDo: normalizeStringArray(source.doNotDo).slice(0, 12),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeStringArray(value) {
|
||||
return Array.isArray(value)
|
||||
? value.map((entry) => String(entry).trim()).filter(Boolean)
|
||||
: [];
|
||||
}
|
||||
|
||||
async function runCommand(command, args, stdoutPath, stderrPath) {
|
||||
await appendLog(`exec: ${command} ${args.join(" ")}`);
|
||||
await new Promise((resolvePromise, rejectPromise) => {
|
||||
const child = spawn(command, args, {
|
||||
cwd: repoRoot,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
windowsHide: true,
|
||||
});
|
||||
const stdout = [];
|
||||
const stderr = [];
|
||||
child.stdout.on("data", (chunk) => stdout.push(chunk));
|
||||
child.stderr.on("data", (chunk) => stderr.push(chunk));
|
||||
child.on("error", rejectPromise);
|
||||
child.on("close", async (code) => {
|
||||
await writeFile(stdoutPath, Buffer.concat(stdout), "utf8");
|
||||
await writeFile(stderrPath, Buffer.concat(stderr), "utf8");
|
||||
if (code !== 0) {
|
||||
rejectPromise(new Error(`${command} exited ${code}; see ${stderrPath}`));
|
||||
} else {
|
||||
resolvePromise();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function writeStatus() {
|
||||
await writeJson(statusPath, runState);
|
||||
}
|
||||
|
||||
async function writeJson(path, value) {
|
||||
await mkdir(dirname(path), { recursive: true });
|
||||
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
||||
}
|
||||
|
||||
async function appendLog(line) {
|
||||
const previous = existsSync(logPath) ? readFileSync(logPath, "utf8") : "";
|
||||
await writeFile(logPath, `${previous}[${new Date().toISOString()}] ${line}\n`, "utf8");
|
||||
}
|
||||
|
||||
function round(value) {
|
||||
return Number(value.toFixed(3));
|
||||
}
|
||||
|
||||
function parseArgs(args) {
|
||||
const parsed = {};
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const arg = args[index];
|
||||
switch (arg) {
|
||||
case "--minutes":
|
||||
parsed.minutes = args[index + 1];
|
||||
index += 1;
|
||||
break;
|
||||
case "--passes":
|
||||
parsed.passes = args[index + 1];
|
||||
index += 1;
|
||||
break;
|
||||
case "--run-id":
|
||||
parsed.runId = args[index + 1];
|
||||
index += 1;
|
||||
break;
|
||||
case "--out-root":
|
||||
parsed.outRoot = args[index + 1];
|
||||
index += 1;
|
||||
break;
|
||||
case "--swarm-root":
|
||||
parsed.swarmRoot = args[index + 1];
|
||||
index += 1;
|
||||
break;
|
||||
case "--initial-prompt-state":
|
||||
parsed.initialPromptState = args[index + 1];
|
||||
index += 1;
|
||||
break;
|
||||
case "--agents":
|
||||
parsed.agents = args[index + 1];
|
||||
index += 1;
|
||||
break;
|
||||
case "--runs-per-worker":
|
||||
parsed.runsPerWorker = args[index + 1];
|
||||
index += 1;
|
||||
break;
|
||||
case "--max-candidates":
|
||||
parsed.maxCandidates = args[index + 1];
|
||||
index += 1;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown argument: ${arg}`);
|
||||
}
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
|
@ -1,496 +0,0 @@
|
|||
#!/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, "_");
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,83 +0,0 @@
|
|||
param(
|
||||
[int] $Port = 8813,
|
||||
[string] $HostName = "127.0.0.1",
|
||||
[string] $StateRoot = "E:\Projects\weksa\.weksa",
|
||||
[string] $MiMoApiKeyPath = "E:\Projects\gamecult-ops\mimo-api-Weksa.txt",
|
||||
[string] $IdunnRudpHealth = "127.0.0.1:17870",
|
||||
[string] $IdunnDaemon = "weksa",
|
||||
[string] $IdunnHealthContract = "weksa.cultnet-rudp-provider-health",
|
||||
[string] $OdinCultMeshRudp = "127.0.0.1:17871"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = Split-Path -Parent $PSScriptRoot
|
||||
$pidPath = Join-Path $StateRoot "weksa-daemon.pid"
|
||||
$outLog = Join-Path $StateRoot "weksa-daemon.out.log"
|
||||
$errLog = Join-Path $StateRoot "weksa-daemon.err.log"
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $StateRoot | Out-Null
|
||||
$cultLibPackages = Resolve-Path -LiteralPath (Join-Path $repoRoot "..\CultLib\packages")
|
||||
$env:NODE_PATH = (@($cultLibPackages.Path, $env:NODE_PATH) | Where-Object { $_ }) -join [IO.Path]::PathSeparator
|
||||
|
||||
if (Test-Path -LiteralPath $pidPath) {
|
||||
$pidText = (Get-Content -LiteralPath $pidPath -Raw).Trim()
|
||||
if ($pidText -match "^\d+$") {
|
||||
$existing = Get-Process -Id ([int] $pidText) -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
try {
|
||||
& node (Join-Path $repoRoot "scripts\weksa-daemon.mjs") --health --host $HostName --port $Port --state-root $StateRoot | Out-Null
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host "Weksa daemon already healthy as PID $pidText."
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
Stop-Process -Id $existing.Id -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
Remove-Item -LiteralPath $pidPath -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
$arguments = @(
|
||||
(Join-Path $repoRoot "scripts\weksa-daemon.mjs"),
|
||||
"--host", $HostName,
|
||||
"--port", [string] $Port,
|
||||
"--state-root", $StateRoot,
|
||||
"--mimo-api-key-path", $MiMoApiKeyPath,
|
||||
"--idunn-rudp-health", $IdunnRudpHealth,
|
||||
"--idunn-daemon", $IdunnDaemon,
|
||||
"--idunn-health-contract", $IdunnHealthContract
|
||||
)
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($OdinCultMeshRudp)) {
|
||||
$arguments += @("--odin-cultmesh-rudp", $OdinCultMeshRudp)
|
||||
}
|
||||
|
||||
$process = Start-Process -FilePath "node" `
|
||||
-ArgumentList $arguments `
|
||||
-WorkingDirectory $repoRoot `
|
||||
-WindowStyle Hidden `
|
||||
-PassThru `
|
||||
-RedirectStandardOutput $outLog `
|
||||
-RedirectStandardError $errLog
|
||||
|
||||
$process.Id | Set-Content -Encoding ASCII -LiteralPath $pidPath
|
||||
|
||||
Start-Sleep -Seconds 2
|
||||
if ($process.HasExited) {
|
||||
$detail = ""
|
||||
if (Test-Path -LiteralPath $outLog) { $detail += Get-Content -Raw -LiteralPath $outLog }
|
||||
if (Test-Path -LiteralPath $errLog) { $detail += Get-Content -Raw -LiteralPath $errLog }
|
||||
throw "Weksa daemon exited immediately with code $($process.ExitCode).`n$detail"
|
||||
}
|
||||
|
||||
& node (Join-Path $repoRoot "scripts\weksa-daemon.mjs") --health --host $HostName --port $Port --state-root $StateRoot | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Weksa daemon started but health check failed."
|
||||
}
|
||||
|
||||
Write-Host "Started Weksa daemon as PID $($process.Id)."
|
||||
Write-Host " command: rudp://$HostName`:$Port"
|
||||
Write-Host " Odin CultMesh/RUDP: $OdinCultMeshRudp"
|
||||
Write-Host " state: $StateRoot"
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,466 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
const dgram = require("dgram");
|
||||
const path = require("path");
|
||||
|
||||
const {
|
||||
CultNetRudpSession,
|
||||
decodeRudpPacket,
|
||||
encodeCultNetMessageForWire,
|
||||
encodeRudpPacket,
|
||||
parseCultNetMessage,
|
||||
} = require("cultnet-ts");
|
||||
|
||||
const { encode, decode } = require(path.resolve(
|
||||
__dirname,
|
||||
"..",
|
||||
"..",
|
||||
"CultLib",
|
||||
"node_modules",
|
||||
"@msgpack",
|
||||
"msgpack",
|
||||
));
|
||||
|
||||
const CULTNET_RUDP_PROTOCOL_ID = "cultnet.transport.rudp.v0";
|
||||
const WEKSA_COMMAND_RUDP_CONNECTION_ID = 0x1d0d0002;
|
||||
const WEKSA_MIMO_VOICEDESIGN_COMMAND_SCHEMA_ID = "weksa.mimo_voicedesign_command.v0";
|
||||
const WEKSA_MIMO_VOICEDESIGN_RECEIPT_SCHEMA_ID = "weksa.mimo_voicedesign_receipt.v0";
|
||||
|
||||
async function createWeksaRudpCommandServer(options) {
|
||||
if (!options?.host || !options?.port || typeof options.onCommand !== "function") {
|
||||
throw new Error("Weksa RUDP command server requires host, port, and onCommand.");
|
||||
}
|
||||
const socket = dgram.createSocket(endpointFamily(options.host));
|
||||
await bindServerSocket(socket, options.host, options.port);
|
||||
|
||||
const sessions = new Map();
|
||||
const resendTimer = setInterval(() => {
|
||||
for (const entry of sessions.values()) {
|
||||
for (const packet of entry.session.dueResends(Date.now())) {
|
||||
void sendPacket(socket, entry.endpoint, packet);
|
||||
}
|
||||
}
|
||||
}, 25);
|
||||
resendTimer.unref?.();
|
||||
|
||||
const onMessage = (wire, remote) => {
|
||||
void handleServerDatagram({
|
||||
wire,
|
||||
remote,
|
||||
socket,
|
||||
sessions,
|
||||
options,
|
||||
}).catch((error) => {
|
||||
options.onError?.(error);
|
||||
});
|
||||
};
|
||||
const onError = (error) => {
|
||||
options.onError?.(error instanceof Error ? error : new Error(String(error)));
|
||||
};
|
||||
|
||||
socket.on("message", onMessage);
|
||||
socket.on("error", onError);
|
||||
|
||||
return {
|
||||
endpoint: { host: options.host, port: options.port },
|
||||
uri: formatRudpEndpoint(options.host, options.port),
|
||||
close() {
|
||||
clearInterval(resendTimer);
|
||||
socket.off("message", onMessage);
|
||||
socket.off("error", onError);
|
||||
socket.close();
|
||||
sessions.clear();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function handleServerDatagram(input) {
|
||||
const { wire, remote, socket, sessions, options } = input;
|
||||
const packet = decodeRudpPacket(wire);
|
||||
const key = endpointKey(remote.address, remote.port);
|
||||
let entry = sessions.get(key);
|
||||
if (!entry) {
|
||||
if (packet.packetType !== "connect") {
|
||||
return;
|
||||
}
|
||||
const session = new CultNetRudpSession({
|
||||
connectionId: WEKSA_COMMAND_RUDP_CONNECTION_ID,
|
||||
initialSequence: 1,
|
||||
resendDelayMs: 100,
|
||||
maxPendingReliablePackets: 64,
|
||||
});
|
||||
entry = {
|
||||
session,
|
||||
endpoint: { host: remote.address, port: remote.port },
|
||||
};
|
||||
sessions.set(key, entry);
|
||||
await sendPacket(socket, entry.endpoint, session.acceptConnect(packet, Date.now()));
|
||||
return;
|
||||
}
|
||||
|
||||
const result = entry.session.receive(packet, Date.now());
|
||||
if (result.reply) {
|
||||
await sendPacket(socket, entry.endpoint, result.reply);
|
||||
}
|
||||
for (const frame of result.delivered) {
|
||||
await handleServerFrame({
|
||||
socket,
|
||||
entry,
|
||||
frame,
|
||||
options,
|
||||
});
|
||||
}
|
||||
if (packet.packetType === "accept" || result.delivered.length > 0) {
|
||||
await sendPacket(socket, entry.endpoint, entry.session.createAck());
|
||||
}
|
||||
if (result.disconnected) {
|
||||
sessions.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleServerFrame(input) {
|
||||
const { socket, entry, frame, options } = input;
|
||||
if (frame.channelId !== "schema") {
|
||||
return;
|
||||
}
|
||||
const message = parseCultNetMessage(decode(frame.payload), "cultnet.schema.v0");
|
||||
if (message.schemaVersion !== "cultnet.document_put_raw.v0") {
|
||||
return;
|
||||
}
|
||||
if (message.document.schemaId !== WEKSA_MIMO_VOICEDESIGN_COMMAND_SCHEMA_ID) {
|
||||
return;
|
||||
}
|
||||
const payload = decode(message.document.payload);
|
||||
const command = {
|
||||
...payload,
|
||||
request_id: payload?.request_id ?? message.document.recordKey,
|
||||
};
|
||||
let receipt;
|
||||
try {
|
||||
receipt = await options.onCommand(command, message);
|
||||
} catch (error) {
|
||||
options.onError?.(error instanceof Error ? error : new Error(String(error)));
|
||||
receipt = {
|
||||
schema_version: WEKSA_MIMO_VOICEDESIGN_RECEIPT_SCHEMA_ID,
|
||||
request_id: command.request_id,
|
||||
generated_at: new Date().toISOString(),
|
||||
ok: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
}
|
||||
if (!receipt) {
|
||||
return;
|
||||
}
|
||||
const receiptMessage = {
|
||||
schemaVersion: "cultnet.document_put_raw.v0",
|
||||
messageId: `${options.runtimeId ?? "weksa-daemon"}:receipt:${receipt.request_id}`,
|
||||
document: {
|
||||
schemaId: WEKSA_MIMO_VOICEDESIGN_RECEIPT_SCHEMA_ID,
|
||||
recordKey: receipt.request_id,
|
||||
storedAt: receipt.generated_at ?? receipt.observed_at ?? new Date().toISOString(),
|
||||
payloadEncoding: "messagepack",
|
||||
payload: encode(receipt),
|
||||
sourceRuntimeId: options.runtimeId ?? "weksa-daemon",
|
||||
sourceRole: "weksa-command-ingress",
|
||||
tags: [CULTNET_RUDP_PROTOCOL_ID, "speech_provider.mimo.voicedesign"],
|
||||
},
|
||||
};
|
||||
const wirePayload = encode(
|
||||
encodeCultNetMessageForWire(receiptMessage, "cultnet.schema.v0"),
|
||||
);
|
||||
for (const packet of entry.session.sendMany("schema", wirePayload, {
|
||||
reliable: true,
|
||||
ordered: true,
|
||||
nowMs: Date.now(),
|
||||
})) {
|
||||
await sendPacket(socket, entry.endpoint, packet);
|
||||
}
|
||||
}
|
||||
|
||||
async function sendWeksaMimoVoiceDesignCommand(options) {
|
||||
const endpoint = parseEndpoint(options?.endpoint);
|
||||
const requestId = options.command?.request_id ?? buildRequestId();
|
||||
const command = {
|
||||
...options.command,
|
||||
request_id: requestId,
|
||||
};
|
||||
const socket = dgram.createSocket(endpointFamily(endpoint.host));
|
||||
await bindClientSocket(socket, options?.localHost);
|
||||
const receiver = createPacketReceiver(socket);
|
||||
const session = new CultNetRudpSession({
|
||||
connectionId: WEKSA_COMMAND_RUDP_CONNECTION_ID,
|
||||
initialSequence: 1,
|
||||
resendDelayMs: 100,
|
||||
maxPendingReliablePackets: 64,
|
||||
});
|
||||
|
||||
try {
|
||||
await sendPacket(socket, endpoint, session.createConnect(Date.now(), new Uint8Array()));
|
||||
await receiveCultNetMessageUntil({
|
||||
receiver,
|
||||
session,
|
||||
endpoint,
|
||||
timeoutMs: options.timeoutMs ?? 5000,
|
||||
label: "accept",
|
||||
predicate: (_message, packet) => packet.packetType === "accept",
|
||||
});
|
||||
|
||||
const commandMessage = {
|
||||
schemaVersion: "cultnet.document_put_raw.v0",
|
||||
messageId: `${options.runtimeId ?? "weksa-cli"}:command:${requestId}`,
|
||||
document: {
|
||||
schemaId: WEKSA_MIMO_VOICEDESIGN_COMMAND_SCHEMA_ID,
|
||||
recordKey: requestId,
|
||||
storedAt: new Date().toISOString(),
|
||||
payloadEncoding: "messagepack",
|
||||
payload: encode(command),
|
||||
sourceRuntimeId: options.runtimeId ?? "weksa-cli",
|
||||
sourceRole: "command-client",
|
||||
tags: [CULTNET_RUDP_PROTOCOL_ID, "speech_provider.mimo.voicedesign"],
|
||||
},
|
||||
};
|
||||
const wirePayload = encode(
|
||||
encodeCultNetMessageForWire(commandMessage, "cultnet.schema.v0"),
|
||||
);
|
||||
for (const packet of session.sendMany("schema", wirePayload, {
|
||||
reliable: true,
|
||||
ordered: true,
|
||||
nowMs: Date.now(),
|
||||
})) {
|
||||
await sendPacket(socket, endpoint, packet);
|
||||
}
|
||||
|
||||
return await receiveCultNetMessageUntil({
|
||||
receiver,
|
||||
session,
|
||||
endpoint,
|
||||
timeoutMs: options.timeoutMs ?? 10000,
|
||||
label: "receipt",
|
||||
predicate: (message) =>
|
||||
message.schemaVersion === "cultnet.document_put_raw.v0"
|
||||
&& message.document.schemaId === WEKSA_MIMO_VOICEDESIGN_RECEIPT_SCHEMA_ID
|
||||
&& message.document.recordKey === requestId,
|
||||
decodePayload: true,
|
||||
});
|
||||
} finally {
|
||||
receiver.close();
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
|
||||
async function receiveCultNetMessageUntil(options) {
|
||||
const deadline = Date.now() + options.timeoutMs;
|
||||
while (Date.now() < deadline) {
|
||||
try {
|
||||
const packet = await options.receiver.next(
|
||||
Math.min(100, Math.max(1, deadline - Date.now())),
|
||||
options.label,
|
||||
);
|
||||
const result = options.session.receive(packet, Date.now());
|
||||
if (result.reply) {
|
||||
await sendPacket(options.receiver.socket, options.endpoint, result.reply);
|
||||
}
|
||||
if (packet.packetType === "accept" || result.delivered.length > 0) {
|
||||
await sendPacket(
|
||||
options.receiver.socket,
|
||||
options.endpoint,
|
||||
options.session.createAck(),
|
||||
);
|
||||
}
|
||||
if (packet.packetType === "accept" && options.predicate(undefined, packet)) {
|
||||
return undefined;
|
||||
}
|
||||
for (const frame of result.delivered) {
|
||||
if (frame.channelId !== "schema") {
|
||||
continue;
|
||||
}
|
||||
const message = parseCultNetMessage(decode(frame.payload), "cultnet.schema.v0");
|
||||
if (!options.predicate(message, packet)) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
options.decodePayload
|
||||
&& message.schemaVersion === "cultnet.document_put_raw.v0"
|
||||
) {
|
||||
return decode(message.document.payload);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== "ETIMEDOUT") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
for (const packet of options.session.dueResends(Date.now())) {
|
||||
await sendPacket(options.receiver.socket, options.endpoint, packet);
|
||||
}
|
||||
}
|
||||
throw new Error(`timed out waiting for Weksa RUDP ${options.label} after ${options.timeoutMs}ms`);
|
||||
}
|
||||
|
||||
async function bindServerSocket(socket, host, port) {
|
||||
await new Promise((resolve, reject) => {
|
||||
socket.once("error", reject);
|
||||
socket.bind(port, host, () => {
|
||||
socket.off("error", reject);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function bindClientSocket(socket, localHost) {
|
||||
const bindHost = localHost ?? "0.0.0.0";
|
||||
await new Promise((resolve, reject) => {
|
||||
socket.once("error", reject);
|
||||
socket.bind(0, bindHost, () => {
|
||||
socket.off("error", reject);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createPacketReceiver(socket) {
|
||||
const packets = [];
|
||||
const waiters = [];
|
||||
const errors = [];
|
||||
|
||||
const resolveNext = () => {
|
||||
while (waiters.length > 0 && (packets.length > 0 || errors.length > 0)) {
|
||||
const waiter = waiters.shift();
|
||||
clearTimeout(waiter.timer);
|
||||
if (errors.length > 0) {
|
||||
waiter.reject(errors.shift());
|
||||
} else {
|
||||
waiter.resolve(packets.shift());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onMessage = (wire) => {
|
||||
try {
|
||||
packets.push(decodeRudpPacket(wire));
|
||||
} catch (error) {
|
||||
errors.push(error);
|
||||
}
|
||||
resolveNext();
|
||||
};
|
||||
const onError = (error) => {
|
||||
errors.push(error);
|
||||
resolveNext();
|
||||
};
|
||||
|
||||
socket.on("message", onMessage);
|
||||
socket.on("error", onError);
|
||||
|
||||
return {
|
||||
socket,
|
||||
next(timeoutMs, label = "packet") {
|
||||
if (packets.length > 0) {
|
||||
return Promise.resolve(packets.shift());
|
||||
}
|
||||
if (errors.length > 0) {
|
||||
return Promise.reject(errors.shift());
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const waiter = {
|
||||
resolve,
|
||||
reject,
|
||||
timer: setTimeout(() => {
|
||||
const index = waiters.indexOf(waiter);
|
||||
if (index >= 0) {
|
||||
waiters.splice(index, 1);
|
||||
}
|
||||
const error = new Error(`timed out waiting for Weksa RUDP ${label}`);
|
||||
error.code = "ETIMEDOUT";
|
||||
reject(error);
|
||||
}, Math.max(1, timeoutMs)),
|
||||
};
|
||||
waiters.push(waiter);
|
||||
});
|
||||
},
|
||||
close() {
|
||||
socket.off("message", onMessage);
|
||||
socket.off("error", onError);
|
||||
while (waiters.length > 0) {
|
||||
const waiter = waiters.shift();
|
||||
clearTimeout(waiter.timer);
|
||||
const error = new Error("Weksa RUDP command receiver closed.");
|
||||
error.code = "ECLOSED";
|
||||
waiter.reject(error);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function sendPacket(socket, endpoint, packet) {
|
||||
const wire = encodeRudpPacket(packet);
|
||||
await new Promise((resolve, reject) => {
|
||||
socket.send(wire, endpoint.port, endpoint.host, (error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function parseEndpoint(value) {
|
||||
if (!value) {
|
||||
throw new Error("Weksa RUDP endpoint is required.");
|
||||
}
|
||||
const text = String(value).trim();
|
||||
if (text.toLowerCase().startsWith("rudp://")) {
|
||||
const parsed = new URL(text);
|
||||
return {
|
||||
host: parsed.hostname,
|
||||
port: parsePort(parsed.port),
|
||||
};
|
||||
}
|
||||
const ipv6 = text.match(/^\[([^\]]+)\]:(\d+)$/);
|
||||
if (ipv6) {
|
||||
return { host: ipv6[1], port: parsePort(ipv6[2]) };
|
||||
}
|
||||
const index = text.lastIndexOf(":");
|
||||
if (index <= 0) {
|
||||
throw new Error(`Weksa RUDP endpoint must be host:port, got "${value}".`);
|
||||
}
|
||||
return {
|
||||
host: text.slice(0, index),
|
||||
port: parsePort(text.slice(index + 1)),
|
||||
};
|
||||
}
|
||||
|
||||
function parsePort(value) {
|
||||
const port = Number(value);
|
||||
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
||||
throw new Error(`Weksa RUDP endpoint port is invalid: ${value}`);
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
function endpointFamily(host) {
|
||||
return host.includes(":") ? "udp6" : "udp4";
|
||||
}
|
||||
|
||||
function endpointKey(host, port) {
|
||||
return `${host}:${port}`;
|
||||
}
|
||||
|
||||
function formatRudpEndpoint(host, port) {
|
||||
return host.includes(":") ? `rudp://[${host}]:${port}` : `rudp://${host}:${port}`;
|
||||
}
|
||||
|
||||
function buildRequestId() {
|
||||
return `mimo-${new Date().toISOString().replace(/[-:.TZ]/g, "")}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
CULTNET_RUDP_PROTOCOL_ID,
|
||||
WEKSA_COMMAND_RUDP_CONNECTION_ID,
|
||||
WEKSA_MIMO_VOICEDESIGN_COMMAND_SCHEMA_ID,
|
||||
WEKSA_MIMO_VOICEDESIGN_RECEIPT_SCHEMA_ID,
|
||||
createWeksaRudpCommandServer,
|
||||
sendWeksaMimoVoiceDesignCommand,
|
||||
formatRudpEndpoint,
|
||||
};
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
const document = {
|
||||
schema: "gamecult.eve.provider_advertisement.v1",
|
||||
providerId: "weksa.intent.service",
|
||||
title: "Weksa Intent Service",
|
||||
description:
|
||||
"Weksa converts writing, interlingua, and agent state into typed conversational intent, pronunciation plans, and utterance handoff documents for AquaSynth and other consumers.",
|
||||
mode: "fixture-read-only",
|
||||
serviceVerse: {
|
||||
id: "verse.weksa.service.v1",
|
||||
cultMeshKey: "weksa.service/provider-advertisement",
|
||||
authority: "Weksa daemon identity, schema catalog, and provider publication",
|
||||
owns: [
|
||||
"typed conversational intent",
|
||||
"pronunciation plans",
|
||||
"utterance handoff documents",
|
||||
"lowering traces",
|
||||
"language-project and flavor-profile state",
|
||||
],
|
||||
doesNotOwn: [
|
||||
"AquaSynth learned embeddings",
|
||||
"Faust compilation",
|
||||
"rendered audio",
|
||||
"Persona canonical state",
|
||||
],
|
||||
},
|
||||
nestedVerses: [
|
||||
{
|
||||
id: "weksa.intent",
|
||||
kind: "intent",
|
||||
cultMeshKey: "weksa.intent/current",
|
||||
authority: "Weksa conversational intent compiler",
|
||||
schemas: ["weksa.conversational_intent.v0"],
|
||||
},
|
||||
{
|
||||
id: "weksa.pronunciation",
|
||||
kind: "pronunciation",
|
||||
cultMeshKey: "weksa.pronunciation/current",
|
||||
authority: "Weksa pronunciation lowering",
|
||||
schemas: ["weksa.pronunciation_plan.v0"],
|
||||
},
|
||||
{
|
||||
id: "weksa.utterance",
|
||||
kind: "utterance",
|
||||
cultMeshKey: "weksa.utterance/handoffs",
|
||||
authority: "Weksa utterance handoff export",
|
||||
schemas: ["weksa.utterance_handoff.v0", "weksa.utterance_embedding_handoff.v0.1"],
|
||||
},
|
||||
{
|
||||
id: "weksa.operator",
|
||||
kind: "operator",
|
||||
cultMeshKey: "weksa.operator/status",
|
||||
authority: "Weksa daemon runtime",
|
||||
schemas: ["weksa.operator_state.v0"],
|
||||
},
|
||||
{
|
||||
id: "weksa.cultural_ontology",
|
||||
kind: "cultural_ontology",
|
||||
cultMeshKey: "weksa.cultural-ontology/catalogs",
|
||||
authority: "Weksa cultural ontology profile store",
|
||||
schemas: ["weksa.cultural_ontology_profile.v0", "weksa.cultural_ontology_catalog.v0"],
|
||||
},
|
||||
],
|
||||
schemaWitnessFamilies: [
|
||||
{
|
||||
family: "intent",
|
||||
namespace: "weksa.intent.*",
|
||||
witnesses: ["weksa.conversational_intent.v0"],
|
||||
durableShape: ".weksa/intent/{intentId}.cc",
|
||||
},
|
||||
{
|
||||
family: "pronunciation",
|
||||
namespace: "weksa.pronunciation.*",
|
||||
witnesses: ["weksa.pronunciation_plan.v0"],
|
||||
durableShape: ".weksa/pronunciation/{utteranceId}.cc",
|
||||
},
|
||||
{
|
||||
family: "utterance",
|
||||
namespace: "weksa.utterance.*",
|
||||
witnesses: ["weksa.utterance_handoff.v0", "weksa.utterance_embedding_handoff.v0.1"],
|
||||
durableShape: ".weksa/handoff/{utteranceId}.cc",
|
||||
},
|
||||
{
|
||||
family: "trace",
|
||||
namespace: "weksa.trace.*",
|
||||
witnesses: ["weksa.lowering_trace.v0"],
|
||||
durableShape: ".weksa/traces/{traceId}.cc",
|
||||
},
|
||||
{
|
||||
family: "cultural_ontology",
|
||||
namespace: "weksa.cultural_ontology.*",
|
||||
witnesses: ["weksa.cultural_ontology_profile.v0", "weksa.cultural_ontology_catalog.v0"],
|
||||
durableShape: ".weksa/cultural-ontology/{storeId}.cc",
|
||||
},
|
||||
],
|
||||
commandBoundaries: [
|
||||
{
|
||||
id: "weksa.command.intent.create",
|
||||
owner: "Weksa",
|
||||
acceptedByFixture: false,
|
||||
allowedInputs: ["writing", "interlingua packet", "Persona/agent state projection"],
|
||||
emits: ["weksa.conversational_intent.v0", "weksa.lowering_trace.v0"],
|
||||
},
|
||||
{
|
||||
id: "weksa.command.utterance.lower",
|
||||
owner: "Weksa",
|
||||
acceptedByFixture: false,
|
||||
allowedInputs: ["accepted conversational intent", "language project", "speaker state"],
|
||||
emits: ["weksa.pronunciation_plan.v0", "weksa.utterance_handoff.v0"],
|
||||
},
|
||||
{
|
||||
id: "weksa.command.handoff.export",
|
||||
owner: "Weksa",
|
||||
acceptedByFixture: false,
|
||||
emits: ["weksa.utterance_embedding_handoff.v0.1"],
|
||||
forbiddenTargets: ["Faust compilation", "audio rendering", "AquaSynth synth driver truth"],
|
||||
},
|
||||
],
|
||||
loweringRules: [
|
||||
"Weksa owns conversational intent and utterance lowering.",
|
||||
"AquaSynth owns learned embeddings, synth controls, Faust compilation, and audio output.",
|
||||
"Fixture exports are discovery metadata until the daemon loop commits typed witnesses.",
|
||||
],
|
||||
};
|
||||
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const outIndex = args.indexOf("--out");
|
||||
const outputPath = outIndex >= 0 ? args[outIndex + 1] : undefined;
|
||||
const json = `${JSON.stringify(document, null, 2)}\n`;
|
||||
|
||||
if (outputPath) {
|
||||
const resolved = path.resolve(process.cwd(), outputPath);
|
||||
await mkdir(path.dirname(resolved), { recursive: true });
|
||||
await writeFile(resolved, json, "utf8");
|
||||
return;
|
||||
}
|
||||
|
||||
process.stdout.write(json);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
Loading…
Reference in a new issue