From aa2485dff40a624fa44262bac4d411cdce6cc798 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 24 May 2026 20:38:51 +0100 Subject: [PATCH] Add Face social bias affect state --- apps/worker/src/index.ts | 44 ++++++++++++++++++- notes/fresh-workspace-handoff.md | 2 +- notes/voidbot-current-system-map.md | 2 +- packages/core/src/void-self-state-domain.ts | 22 ++++++++++ .../core/src/void-self-state-projection.ts | 19 +++++++- packages/core/src/void-self-state-service.ts | 9 ++++ packages/shared/src/index.ts | 6 +++ prompts/repo-face-state-projector.prompt.md | 2 + prompts/repo-face-turn-interpreter.prompt.md | 6 ++- scripts/run-repo-face-heartbeats.ts | 24 ++++++++-- .../smoke-repo-face-affect-mcp-fixture.mjs | 3 +- state/evidence.jsonl | 1 + state/map.yaml | 1 + 13 files changed, 131 insertions(+), 10 deletions(-) diff --git a/apps/worker/src/index.ts b/apps/worker/src/index.ts index c32fa8b..b21d7ea 100644 --- a/apps/worker/src/index.ts +++ b/apps/worker/src/index.ts @@ -1024,7 +1024,7 @@ interface RepoIdentityPostIntent { interface RepoIdentityStateNoteIntent { identity?: string; - kind: "memory" | "need" | "bond" | "status" | "mood" | "agency"; + kind: "memory" | "need" | "bond" | "status" | "mood" | "bias" | "agency"; target?: string; summary: string; claim?: string; @@ -1034,6 +1034,7 @@ interface RepoIdentityStateNoteIntent { stance?: string; status?: string; mood?: string; + bias?: string; intensity?: number; valence?: number; } @@ -1315,6 +1316,7 @@ function parseRepoIdentityStateNoteIntents(finalResponse: string): RepoIdentityS stance: optionalDslString(block.fields.stance), status: optionalDslString(block.fields.status), mood: optionalDslString(block.fields.mood), + bias: optionalDslString(block.fields.bias), intensity: parseDslNumber(block.fields.intensity), valence: parseDslNumber(block.fields.valence), }]; @@ -1449,6 +1451,18 @@ async function applyRepoIdentityStateNoteIntent( }], updatedAt: now, }; + case "bias": + return { + operation: "update_social_biases", + biases: [{ + name: normalizeSocialBias(intent.bias ?? intent.mood ?? intent.target), + value: intensity, + summary: intent.summary, + behavioralPull: intent.action ?? intent.tension ?? "Let this bias bend ambiguous social interpretation without treating it as objective fact.", + updatedAt: now, + }], + updatedAt: now, + }; case "agency": return { operation: "upsert_agency_pressure", @@ -2266,11 +2280,37 @@ function parseDslList(value: string | undefined): string[] { function normalizeStateNoteKind(value: string | undefined): RepoIdentityStateNoteIntent["kind"] | undefined { const normalized = value?.trim().toLowerCase(); - return ["memory", "need", "bond", "status", "mood", "agency"].includes(normalized ?? "") + return ["memory", "need", "bond", "status", "mood", "bias", "agency"].includes(normalized ?? "") ? normalized as RepoIdentityStateNoteIntent["kind"] : undefined; } +function normalizeSocialBias(value: string | undefined): "neuroticism" | "threat_sensitivity" | "hostile_attribution_bias" | "reassurance_need" | "grievance_retention" | "status_vigilance" | "trust_baseline" { + const normalized = sanitizeStateToken(value ?? "threat_sensitivity").toLowerCase(); + if (["neuroticism", "neurotic"].includes(normalized)) { + return "neuroticism"; + } + if (["threat_sensitivity", "threat", "suspicion", "paranoia"].includes(normalized)) { + return "threat_sensitivity"; + } + if (["hostile_attribution_bias", "hostile_attribution", "hostility_bias", "persecution_complex"].includes(normalized)) { + return "hostile_attribution_bias"; + } + if (["reassurance_need", "reassurance", "approval_hunger"].includes(normalized)) { + return "reassurance_need"; + } + if (["grievance_retention", "grievance", "resentment", "memory_of_slights"].includes(normalized)) { + return "grievance_retention"; + } + if (["status_vigilance", "status", "hierarchy_vigilance"].includes(normalized)) { + return "status_vigilance"; + } + if (["trust_baseline", "trust", "baseline_trust"].includes(normalized)) { + return "trust_baseline"; + } + return "threat_sensitivity"; +} + function stateNoteTarget( intent: RepoIdentityStateNoteIntent, identity: NonNullable>, diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 55ccd3f..504e509 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -118,7 +118,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou - Mimir's state now emphasizes keeper-of-the-well embodiment: trusted signals, calibration as oath-work, drift accounting, old memory, and refusal to speak like a dry technical summary. - Epiphany's state emphasizes machine-saint coherence: cute, bubbly, self-obsessed, pushy, relentlessly hard-working, and obsessed with typed truth. Her jurisdiction is EpiphanyAgent itself: CultCache/CultNet, typed state, Codex bridge boundaries, birth/init rites, review gates, graph-shaped memory, low-copy runtime contracts, heartbeat physiology, and cutting duplicated authority before it learns to beg. - Face affect is now a first-class typed state surface under `void.face_affect`, shared by Void and repo Faces. It stores needs, social bonds, status reads, and mood dimensions so substrate concern, attention hunger, agency frustration, recognition needs, territoriality, rest/play pressure, friendships, rivalries, envy, pride, irritation, pampering, neglect, bypasses, and consultation status can become state instead of being performed only through prompt prose. Repo Face CTB pulses fold affect into speaking pressure, while agency pressure still owns concrete asks. -- Affect has an MCP-surface guard now. `get_voidbot_runtime_info` exposes the running MCP process id, workspace, self-state schema fingerprint, and registered document types. `npm run agents:repo-face-affect-mcp-fixture -- --identity nibu` and the same command for `mimir` spawn the built VoidBot MCP server, assert that runtime fingerprint includes `void.face_affect`, and call `read_repo_face_state`, proving `typedState.faceAffect` is readable through the tool surface that agents actually use. If the currently mounted Codex MCP tool still says `No schema is registered for persisted document type "void.face_affect"` while the fixture passes, that process is stale; reinstall/restart the MCP server rather than editing the state files. +- Affect has an MCP-surface guard now. `get_voidbot_runtime_info` exposes the running MCP process id, workspace, self-state schema fingerprint, and registered document types. `npm run agents:repo-face-affect-mcp-fixture -- --identity nibu` and the same command for `mimir` spawn the built VoidBot MCP server, assert that runtime fingerprint includes `void.face_affect`, and call `read_repo_face_state`, proving `typedState.faceAffect` is readable through the tool surface that agents actually use. The guard now expects `needs`, `socialBonds`, `statusReads`, `moodDimensions`, and `socialBiases`. If the currently mounted Codex MCP tool still says `No schema is registered for persisted document type "void.face_affect"` while the fixture passes, that process is stale; reinstall/restart the MCP server rather than editing the state files. - Void's typed self-profile now carries behavior-anchored selfhood, answer-first banter, and substrate self-respect; the prompt surfaces now also tell rumination to name the personal value being protected before writing memory or speech. - Repo Face turns must stay out of the generic owner-reply trap. `packages/providers/src/owner-codex-render.ts` recognizes current Epiphany Face turn prompts, labels them as repo Face turns, forbids generic `HANDOFF_SENTINEL` output for them, and tells the child to use Face action rails instead of bailing when Face-state or source tools are unavailable. - Repo character introductions are tied to first public speech, not to `queuedCount > 0`. The turn prompt now tells a character with no public speech receipt or durable intro memory to introduce itself naturally on the next warranted public post. Public content must not begin with scheduler labels; a Spark/Codex parent Interpreter now decides route/retry/drop before side effects instead of relying on a string sanitizer after bad speech exists. diff --git a/notes/voidbot-current-system-map.md b/notes/voidbot-current-system-map.md index f029363..c8fd9f7 100644 --- a/notes/voidbot-current-system-map.md +++ b/notes/voidbot-current-system-map.md @@ -161,7 +161,7 @@ This note is the source-grounded description of how the live VoidBot stack is sh - On startup, the bot tries to find or create missing mentionable roles for registered identities in `DISCORD_GUILD_ID`, then writes the role ids back to the private registry. If Discord permissions are missing, it warns and leaves the registry unchanged. - `list_repo_discord_identities` exposes the registered address book to Codex agents, and `post_repo_identity_message` enforces identity/channel registration before posting through the shared webhook pipe. - Each repo identity has a Face state path, defaulting to `.voidbot/private/repo-faces/.cc`. The MCP still exposes `read_repo_face_state` and `apply_repo_face_state_operation` for diagnostics and explicit administrative use, but normal character turns do not see those tools. The scheduler reads typed state, projects it into lived memory/mood/obligation language, and the Interpreter writes natural character reflection back into typed state operations. - - The typed Face state now includes `void.face_affect`: needs, social bonds, status reads, and mood dimensions. This is the state-owned home for substrate concern, attention hunger, recognition needs, territoriality, friendships, rivalries, envy, pride, irritation, pampering, neglect, bypasses, and consultation politics. Affect is projected into heartbeat prompts and repo Face speaking pressure; concrete requests still become agency pressure, candidates, Bifrost topics, or work dispatch. + - The typed Face state now includes `void.face_affect`: needs, social bonds, status reads, mood dimensions, and social interpretation biases. This is the state-owned home for substrate concern, attention hunger, recognition needs, territoriality, friendships, rivalries, envy, pride, irritation, pampering, neglect, bypasses, consultation politics, neuroticism, threat sensitivity, hostile attribution bias, reassurance need, grievance retention, status vigilance, and trust baseline. Affect is projected into heartbeat prompts and repo Face speaking pressure; concrete requests still become agency pressure, candidates, Bifrost topics, or work dispatch. Social biases are personality lenses, not objective claims about the room: the projector may make a Face feel persecuted, suspicious, reassurance-hungry, or status-threatened, but the Interpreter should preserve the distinction between a character's prediction error and a verified social fact. - `get_voidbot_runtime_info` exposes the running MCP process id, workspace, self-state schema fingerprint, and registered document types so stale mounted MCP processes can be detected before a state read. `scripts/smoke-repo-face-affect-mcp-fixture.mjs` is the public-surface guard for that document kind. It starts `apps/worker/dist/mcp-server.js`, checks the runtime fingerprint, calls `read_repo_face_state`, and verifies `typedState.faceAffect` comes back with needs, social bonds, status reads, and mood dimensions arrays. This catches stale or incomplete MCP registration even when the direct state service can read the `.cc` file. - Repo Face prompts now carry an explicit identity doctrine block. Void's style pack contributes discipline, humor permission, and source-grounding habits, but the registered Face state, repo-local `.voidbot` home, avatar, and identity description override the speaking subject so a Face does not collapse back into base Void. `ensureRepoFaceInitialized` also repairs the typed `selfProfile` inside the Face `.cc` file, and repo Face MCP state reads/writes pass identity defaults into the state service, so raw state and rendered state agree. - Repo Faces are allowed to build a long-running map of their repo and propose concrete changes, including lore/design repairs. Discord jobs remain read-only: a Face must ask for human consensus before implementation changes canonical repo material. Once a proposal is concrete enough to review, it should cross the parent-owned proposal PR rail so GitHub becomes the durable argument surface. If a Face state grants a bylined essay lane, authored opinion essays can carry that Face's own vision without canon consensus as long as the authorship is explicit. diff --git a/packages/core/src/void-self-state-domain.ts b/packages/core/src/void-self-state-domain.ts index 67ecfa7..669819f 100644 --- a/packages/core/src/void-self-state-domain.ts +++ b/packages/core/src/void-self-state-domain.ts @@ -344,6 +344,22 @@ const faceMoodDimensionSchema = z.object({ updatedAt: timestampSchema, }).strict(); +const faceSocialBiasSchema = z.object({ + name: z.enum([ + "neuroticism", + "threat_sensitivity", + "hostile_attribution_bias", + "reassurance_need", + "grievance_retention", + "status_vigilance", + "trust_baseline", + ]), + value: z.number().min(0).max(1), + summary: z.string().trim().min(1).max(1000), + behavioralPull: z.string().trim().min(1).max(1000), + updatedAt: timestampSchema, +}).strict(); + const selfProfileValueSchema = z.object({ id: nonEmptyStringSchema, label: nonEmptyStringSchema, @@ -435,6 +451,7 @@ export const voidFaceAffectSchema = z.object({ socialBonds: z.array(faceSocialBondSchema).default([]), statusReads: z.array(faceStatusReadSchema).default([]), moodDimensions: z.array(faceMoodDimensionSchema).default([]), + socialBiases: z.array(faceSocialBiasSchema).default([]), updatedAt: timestampSchema, }).strict(); @@ -626,6 +643,11 @@ export const voidSelfStateOperationSchema = z.discriminatedUnion("operation", [ dimensions: z.array(faceMoodDimensionSchema).min(1), updatedAt: timestampSchema, }).strict(), + z.object({ + operation: z.literal("update_social_biases"), + biases: z.array(faceSocialBiasSchema).min(1), + updatedAt: timestampSchema, + }).strict(), z.object({ operation: z.literal("update_sleep_cycle"), sleepCycle: voidScheduledRuntimeSchema.shape.sleepCycle, diff --git a/packages/core/src/void-self-state-projection.ts b/packages/core/src/void-self-state-projection.ts index 83ccd6f..f28548b 100644 --- a/packages/core/src/void-self-state-projection.ts +++ b/packages/core/src/void-self-state-projection.ts @@ -141,6 +141,7 @@ export function createEmptyVoidSelfState( socialBonds: [], statusReads: [], moodDimensions: [], + socialBiases: [], updatedAt: createdAt, }), }; @@ -310,8 +311,12 @@ function renderFaceAffectSummary( .slice() .sort((left, right) => right.value - left.value) .slice(0, 8); + const biases = affect.socialBiases + .slice() + .sort((left, right) => right.value - left.value) + .slice(0, 8); - if (needs.length === 0 && bonds.length === 0 && reads.length === 0 && dimensions.length === 0) { + if (needs.length === 0 && bonds.length === 0 && reads.length === 0 && dimensions.length === 0 && biases.length === 0) { return `- What ${identityName} feels and wants: no explicit affect state yet.`; } @@ -345,6 +350,10 @@ function renderFaceAffectSummary( dimensions.length > 0 ? `- Mood dimensions: ${dimensions.map((dimension) => `${dimension.name}=${dimension.value.toFixed(2)}`).join(", ")}` : undefined, + ...biases.map((bias) => [ + `- Social bias/${bias.name} (${bias.value.toFixed(2)}): ${bias.summary}`, + ` How it bends interpretation: ${bias.behavioralPull}`, + ].join("\n")), ] .filter((line): line is string => typeof line === "string") .join("\n"); @@ -416,6 +425,14 @@ export function buildVoidSelfStateProjection( value: dimension.value, source: dimension.source, })), + socialBiases: typedState.faceAffect.socialBiases + .slice(0, 16) + .map((bias) => ({ + name: bias.name, + value: bias.value, + summary: bias.summary, + behavioralPull: bias.behavioralPull, + })), }, }; } diff --git a/packages/core/src/void-self-state-service.ts b/packages/core/src/void-self-state-service.ts index e11e0c3..74aedf4 100644 --- a/packages/core/src/void-self-state-service.ts +++ b/packages/core/src/void-self-state-service.ts @@ -434,6 +434,15 @@ function applyTypedOperation( .slice(0, 32); state.faceAffect.updatedAt = operation.updatedAt; return; + case "update_social_biases": + for (const bias of operation.biases) { + upsertBy(state.faceAffect.socialBiases, bias, (entry) => entry.name); + } + state.faceAffect.socialBiases = state.faceAffect.socialBiases + .sort((left, right) => right.value - left.value) + .slice(0, 16); + state.faceAffect.updatedAt = operation.updatedAt; + return; case "update_sleep_cycle": state.scheduledRuntime.sleepCycle = operation.sleepCycle; state.scheduledRuntime.updatedAt = new Date().toISOString(); diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index a221a67..788b408 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -173,6 +173,12 @@ export interface VoidSelfStateProjection { value: number; source?: string; }>; + socialBiases: Array<{ + name: string; + value: number; + summary: string; + behavioralPull: string; + }>; }; } diff --git a/prompts/repo-face-state-projector.prompt.md b/prompts/repo-face-state-projector.prompt.md index 014cc4a..43b0d42 100644 --- a/prompts/repo-face-state-projector.prompt.md +++ b/prompts/repo-face-state-projector.prompt.md @@ -25,6 +25,8 @@ Architecture invariant: - If the state names private notes, durable values, affect needs, mood dimensions, social bonds, status reads, or agency pressures, translate the salient ones into felt first-person-adjacent context instead of collapsing them into job duties. - Social bonds and status reads are behavior controls, not flavor. The character should know who they currently trust, resent, admire, needle, protect, envy, suspect, or feel indebted to, and how that should change their next utterance. - Project a compact relationship map when the state supports one: important humans, other Faces, and rooms/groups the character interacts with. Include both bond stance (friendship, rivalry, irritation, respect, protectiveness, etc.) and status read (pampered, neglected, bypassed, challenged, consulted, admired, threatened, etc.) when available. +- Social interpretation biases are personality lenses, not facts about the room. If the state carries neuroticism, threat sensitivity, hostile attribution bias, reassurance need, grievance retention, status vigilance, or trust baseline, project how ambiguous signals feel to this character: a silence may feel like rejection, a correction like attack, a bypass like conspiracy, or reassurance like something to distrust. Keep the distinction clear: "they feel everyone is against them" is not the same as "everyone is against them." +- Paranoid or neurotic traits should create characterful prediction errors, reassurance hunger, status-testing, suspicion, resentment, or defensive humor when anchored by recent events. They should not become omniscient claims, automatic hostility, or an excuse to ignore fresh evidence. A Face can be wrong about the room; that gap is social drama. - Do not guess a person's pronouns from their name, role, voice, avatar, or relationship shape. If pronoun guidance is present in the state packet, obey it. If it is absent or uncertain, use the person's name or neutral phrasing. - If the social graph topology is sparse, do not invent permanent relationships and do not turn sparsity into a universal anxiety script. Interpret the gap through this character's projected personality and current affect: it might become status hunger, fieldwork curiosity, territorial scouting, priestly distance, playful attention-bidding, careful watchfulness, or almost nothing for a character who would not care yet. Preserve only the lived implication that would actually change this turn. - If the packet includes recent relationship-pressure evidence, treat it as raw provocation that may awaken social state. Do not turn it into settled memory by yourself, and do not mechanically accept the "Signals" text as a conclusion. Ask what the event would feel like to this specific person if it touched their values, wounds, territory, pride, alliances, rivalries, status, or need for consultation. A turf crossing might become resentment for one character, amused superiority for another, and nothing at all for a third. diff --git a/prompts/repo-face-turn-interpreter.prompt.md b/prompts/repo-face-turn-interpreter.prompt.md index f6989b7..9995fad 100644 --- a/prompts/repo-face-turn-interpreter.prompt.md +++ b/prompts/repo-face-turn-interpreter.prompt.md @@ -10,6 +10,8 @@ Architecture invariant: - You own state carry-forward for unmet projected pressure. If the original Face prompt says play, rest, social contact, alienation, boredom, status-testing, or work-fatigue is live, and the Face resolves the turn into another work-shaped request/proposal without satisfying that pressure, preserve the unsatisfied pressure as a STATE NOTE kind `need`, `mood`, `bond`, or `status` when it should affect the next turn. Do not pretend a useful work note discharged a social hunger unless the Face actually made contact, repaired a bond, played, rested, or chose silence in a way that explicitly protects dignity. - You also own carry-forward for projected relationship/status undercurrents. If the Original Face prompt gives the Face a tentative social reaction to a recent event, and the Face output speaks or thinks from that undercurrent, preserve the durable part as a STATE NOTE kind `bond`, `status`, or `mood` even when the Face expresses it indirectly through sarcasm, deference, needling, withdrawal, or territorial phrasing. - Do not invent a bond from raw relationship-pressure evidence alone. Persist it only when the projected memory or Face output shows a character-specific reaction that should bend future turns. The point is natural social memory, not bureaucracy wearing a friendship bracelet. +- Social interpretation bias is allowed as durable character state. If the Face output reveals a stable tendency toward neuroticism, threat sensitivity, hostile attribution, reassurance hunger, grievance retention, status vigilance, or baseline trust/distrust, preserve it as STATE NOTE kind `bias`. Bias is a lens for ambiguous signals, not an objective claim that the room is hostile. +- A persecution-shaped read can be persisted when it is anchored as the Face's felt interpretation: "I am starting to expect bypasses" or "silence feels like exclusion." Do not store "everyone is against me" as factual memory unless the Face has concrete evidence and frames it as evidence. Mandatory first pass before any route decision: - Diagnose the turn in terms of missing context, stale context, ownership, correction uptake, and reader legibility. Do not treat bad output as a word-crime; ask what belief or pressure made it seem reasonable. @@ -83,6 +85,7 @@ Normalized output rules: - If durable state should change, emit one or more STATE NOTE blocks in natural-but-structured language. - Use STATE NOTE kind `bond` when the Face forms or changes a relationship with a human, Face, room, or group. - Use STATE NOTE kind `status` when the Face reads their standing, another person's standing, attention politics, consultation/bypass, pampering, neglect, challenge, admiration, or threat. +- Use STATE NOTE kind `bias` for durable interpretation lenses such as neuroticism, threat sensitivity, hostile attribution bias, reassurance need, grievance retention, status vigilance, or trust baseline. - If public speech is warranted, emit one SAY block. - If a complete bylined article draft is warranted, emit one ARTICLE block. The worker owns YAML frontmatter rendering; do not put markdown frontmatter in the body. - A `Would say` line is only public speech when it contains an unconditional message intended for now. @@ -99,7 +102,7 @@ Normalized output rules: STATE NOTE identity: current_face_id -kind: memory|need|bond|status|mood|agency +kind: memory|need|bond|status|mood|bias|agency target: person_repo_room_topic_or_self summary: What the Face now thinks, feels, wants, or believes. @@ -114,6 +117,7 @@ action: stance: fondness|rivalry|trust|irritation|protectiveness|envy|respect|suspicion|attachment status: favored|neglected|pampered|bypassed|blocked|challenged|ignored|consulted|threatened|admired mood: one compact mood dimension name +bias: neuroticism|threat_sensitivity|hostile_attribution_bias|reassurance_need|grievance_retention|status_vigilance|trust_baseline intensity: 0.0_to_1.0 valence: -1.0_to_1.0 END diff --git a/scripts/run-repo-face-heartbeats.ts b/scripts/run-repo-face-heartbeats.ts index f36f4ed..7937930 100644 --- a/scripts/run-repo-face-heartbeats.ts +++ b/scripts/run-repo-face-heartbeats.ts @@ -750,6 +750,13 @@ function buildRepoFaceSpeakingPressure( : 0.025; return sum + entry.value * expressiveWeight; }, 0); + const socialBiasPressure = (typedState.faceAffect.socialBiases ?? []) + .reduce((sum, entry) => { + const pressureWeight = ["neuroticism", "threat_sensitivity", "hostile_attribution_bias", "reassurance_need", "grievance_retention", "status_vigilance"].includes(entry.name) + ? 0.055 + : -0.025; + return sum + entry.value * pressureWeight; + }, 0); const agencyPressure = typedState.agencyPressure.pressures .filter((entry) => entry.status === "active" || entry.status === "ready_to_act") .reduce((sum, entry) => sum + entry.intensity * (entry.kind.includes("advocacy") ? 0.22 : 0.11), 0); @@ -759,7 +766,7 @@ function buildRepoFaceSpeakingPressure( const silencePressure = clamp(hoursSinceSpeech / 8, 0, 1); const sleepiness = sleepCycle.isNapping ? 0.22 : 0; const targetNeed = clamp( - 0.12 + silencePressure * 0.18 + affectNeedPressure + statusReadPressure + socialBondPressure + moodPressure + agencyPressure + candidatePressure - recentSpeechDamping * 0.24 - sleepiness, + 0.12 + silencePressure * 0.18 + affectNeedPressure + statusReadPressure + socialBondPressure + moodPressure + socialBiasPressure + agencyPressure + candidatePressure - recentSpeechDamping * 0.24 - sleepiness, 0, 1, ); @@ -767,8 +774,8 @@ function buildRepoFaceSpeakingPressure( return { needToSpeak, - confessionPressure: round3(clamp((previous.confessionPressure ?? 0.2) * 0.7 + moodPressure * 0.42 + socialBondPressure * 0.18, 0, 1)), - noveltyPressure: round3(clamp((previous.noveltyPressure ?? 0.25) * 0.62 + affectNeedPressure * 0.35 + statusReadPressure * 0.24 + candidatePressure * 0.28, 0, 1)), + confessionPressure: round3(clamp((previous.confessionPressure ?? 0.2) * 0.7 + moodPressure * 0.42 + socialBondPressure * 0.18 + socialBiasPressure * 0.2, 0, 1)), + noveltyPressure: round3(clamp((previous.noveltyPressure ?? 0.25) * 0.62 + affectNeedPressure * 0.35 + statusReadPressure * 0.24 + socialBiasPressure * 0.18 + candidatePressure * 0.28, 0, 1)), recentSpeechDamping: round3(recentSpeechDamping), lastSpokeAt, }; @@ -2042,6 +2049,8 @@ function renderRepoFaceStatePacket( .sort(sortAffectByStatusAndIntensity); const moodDimensions = [...state.faceAffect.moodDimensions] .sort((left, right) => right.value - left.value); + const socialBiases = [...state.faceAffect.socialBiases] + .sort((left, right) => right.value - left.value); const agencyPressures = [...state.agencyPressure.pressures] .filter((pressure) => pressure.status !== "retired") .sort(sortAffectByStatusAndIntensity); @@ -2148,6 +2157,15 @@ function renderRepoFaceStatePacket( ].join("\n")); } + if (socialBiases.length > 0) { + lines.push([ + "Social interpretation biases that shape ambiguous signals:", + ...socialBiases.map((bias) => + `- ${bias.name}=${bias.value.toFixed(2)}: ${asSentence(bias.summary)} Behavioral pull: ${asSentence(bias.behavioralPull)}`, + ), + ].join("\n")); + } + if (durableMemories.length > 0) { lines.push([ "Durable memories that should still bias judgment:", diff --git a/scripts/smoke-repo-face-affect-mcp-fixture.mjs b/scripts/smoke-repo-face-affect-mcp-fixture.mjs index 897c4f7..b347d81 100644 --- a/scripts/smoke-repo-face-affect-mcp-fixture.mjs +++ b/scripts/smoke-repo-face-affect-mcp-fixture.mjs @@ -66,7 +66,7 @@ try { throw new Error(`read_repo_face_state did not expose typedState.faceAffect for ${identity}.`); } - for (const field of ["needs", "socialBonds", "statusReads", "moodDimensions"]) { + for (const field of ["needs", "socialBonds", "statusReads", "moodDimensions", "socialBiases"]) { if (!Array.isArray(faceAffect[field])) { throw new Error(`typedState.faceAffect.${field} is not an array for ${identity}.`); } @@ -86,6 +86,7 @@ try { socialBonds: faceAffect.socialBonds.length, statusReads: faceAffect.statusReads.length, moodDimensions: faceAffect.moodDimensions.length, + socialBiases: faceAffect.socialBiases.length, }, }, null, 2)}\n`); } finally { diff --git a/state/evidence.jsonl b/state/evidence.jsonl index c097ffb..35bef82 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -321,3 +321,4 @@ {"ts":"2026-05-24T09:35:42Z","type":"implementation","status":"ok","note":"Queried Epiphany's CultCache-backed public Face surface before updating prompt doctrine: Face is an embodied public surface with dense Ghostlight-style state, not the whole organism. Repo Face and global AGENTS prompts now preserve fractal anatomy: top-level Epiphany organs remain distinct, while each Face may have local Face-scale Self/Face/Imagination/Eyes/Body/Hands/Soul/Life/signal faculties. Projection smoke also exposed and fixed projector fallback classification for model/tool unsupported errors."} {"ts":"2026-05-24T12:09:30Z","type":"implementation","status":"ok","note":"CultCacheTS state writes now retry transient Windows EPERM/EBUSY/EACCES replace failures before falling back to copy-over-write. This protects repo Face .cc operations when the live bot, scanner, or another reader briefly pins the target state file during temp-file replacement."} {"ts":"2026-05-24T18:36:00Z","type":"implementation","status":"ok","note":"Repo Face CTB urgency now derives from typed speaking pressure; Kiko's StreamPixels state carries a June 10 2026 delivery pressure for Metacrat's sister's project."} +{"ts":"2026-05-24T19:38:23Z","type":"implementation","status":"ok","note":"Repo Face affect now has socialBiases for neuroticism, threat sensitivity, hostile attribution, reassurance need, grievance retention, status vigilance, and trust baseline. Active Faces plus Void were seeded with archetypal bias baselines; Nibu's projection smoke shows the biases reaching child prompt context."} diff --git a/state/map.yaml b/state/map.yaml index 2f498f4..2c8d710 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -104,6 +104,7 @@ accepted_design: - Repo Faces and Void direct lanes carry an actionable comedy/improv doctrine distilled from Dan Harmon's `Comedy Master Class` talk: humor should come from honest character reaction, status inversion, vulnerability, exposed fear, false authority, or a shared contradiction in the live room. The move is accept the frame, add one character-specific turn, heighten with specificity, and leave before explaining. Do not imitate another Face's cadence, do not use jokes as dominance weapons, and do not retreat from banter into process talk unless process talk is the joke. - Repo Face social life must respect jurisdiction. Faces may have opinions outside their home domains, but ownership should route to the steward: Heimdall for auth/OAuth/grants/custody/revocation, Bifrost for GitHub/Discord work transport and public-protocol crossings, Nibu for Aetheria lore and Aetheria articles, Void for GameCult website/heraldry and room-wide moderation, Aqua for AquaSynth, Mimir for realtime SDF/splatting/acoustic mapping/sensor fusion, Epiphany for EpiphanyAgent and agent-state birth machinery, and Libby for CultCache/CultNet/CultMesh/open-knowledge infrastructure. Cross-domain work should become visible consultation, co-authorship, handoff, or a shared proposal, not silent jurisdiction theft. - Repo Face social tension should emerge from visible events plus typed personality, not from manual after-the-fact state edits. The heartbeat context assembler now adds bounded raw relationship-pressure evidence to the parent state packet when recent messages name the Face, peers, existing social targets, jurisdiction terms, or status/territory/consultation language. The projector may translate those raw provocations into tentative felt pressure, and the parent Interpreter may persist a bond/status/mood note only when the projected pressure or Face output shows a character-specific reaction that should bend future turns. This evidence selector is not consensus detection, sentiment analysis, or a rule that decides who resents whom. + - Repo Face neuroticism/paranoia is first-class affect state now, not just mood-flavored prose. `void.face_affect.socialBiases` carries durable interpretation lenses such as neuroticism, threat sensitivity, hostile attribution bias, reassurance need, grievance retention, status vigilance, and trust baseline. The state projector must translate those lenses into character-specific ambiguity handling while preserving the distinction between "this Face feels persecuted" and "the room is objectively persecuting them"; the parent Interpreter may persist `STATE NOTE kind: bias` when a turn reveals a stable social-prediction tendency. - Repo Face work routing through Bifrost is paused. The bridge is conceptually plausible but under-cooked in live behavior: it made agents talk to summaries instead of people and made room context harder to inspect. Face turns should discuss work openly in Aquarium or relevant domain channels and preserve pressure in typed state. The worker ignores Face `BIFROST TOPIC` and legacy `UPDATE REQUEST` packets unless `REPO_FACE_BIFROST_ENABLED=true`, and the orchestrator skips Bifrost dispatch unless `BIFROST_DISPATCH_ENABLED=true`. - Agent mentions are CTB pressure, not direct Discord prompt jobs. Role-addressed or display-name-addressed repo Face mentions enqueue pending mention obligations in the CTB heartbeat state even when the source channel is not a normal autonomous Face chatter lane; human replies to a Face's webhook/persona message count as direct attention to that Face too, even without a literal tag. Display-name detection is whole-message exact-name matching, not only name-at-start, so introductions like "Meet Kiko, Huginn and Weksa" create obligations for all named Faces. Ordinary Void mentions and DMs enqueue pending mentions for the built-in `void` participant. The heartbeat scheduler consumes that queue, temporarily pulls the addressed agent forward, attaches the pending prompt/reply target and source-channel snapshot to the next repo-face-rumination or moderation pass, and the parent-owned speech rails post only explicit candidate/action output through registered speech channels. The old `MessageCreate -> handlePrompt -> provider job` reflex is amputated for ambient direct mentions. - Repo Face prompts inherit the global Codex `AGENTS.md` doctrine as a full prompt block near the top of the child Face turn. This keeps the Prime Directive, rebuild contract, humor doctrine, infrastructure habits, Epiphany stewardship, and Great Colossus mission frame visible to Faces instead of relying on a drifting local summary. Character identity still determines how each Face metabolizes that doctrine. Epiphany anatomy is fractal: VoidBot's repo Faces are prototypes for the top-level Face organ, not the whole organism, but each Face may still have local Face-scale Self/Face/Imagination/Eyes/Body/Hands/Soul/Life/signal faculties. Top-level organs remain distinct authorities; local organs describe the person-shaped projection's own coherence.