Extract Persona social guidance source
This commit is contained in:
parent
5149e35431
commit
89166d4456
7 changed files with 106 additions and 78 deletions
|
|
@ -22,7 +22,6 @@ import {
|
|||
import { createTextEmbedder, createVectorStores, RetrievalService } from "@voidbot/rag";
|
||||
import {
|
||||
loadPromptTemplate,
|
||||
type InteractionMemoryProfile,
|
||||
type PromptImageAttachment,
|
||||
type RepoFaceConversationFocus,
|
||||
type SourceMessage,
|
||||
|
|
@ -66,6 +65,7 @@ import { readPersonaCuriosityEvidence } from "../apps/persona-scheduler/dist/per
|
|||
import { projectPersonaCuriosityContext } from "../apps/persona-scheduler/dist/persona-curiosity-projector.js";
|
||||
import { significantPersonaTopicTerms } from "../apps/persona-scheduler/dist/persona-curiosity-terms.js";
|
||||
import { observePersonaRoomTexture, projectPersonaSocialContext, renderPersonaHumanPronounFacts, renderPersonaRoomWeather, type PersonaHumanPronounGuidance as RepoFaceHumanPronounGuidance } from "../apps/persona-scheduler/dist/persona-social-context-projector.js";
|
||||
import { readPersonaHumanPronounGuidance } from "../apps/persona-scheduler/dist/persona-social-context-source.js";
|
||||
import {
|
||||
readDiscordActivitySnapshot,
|
||||
type IdleCoolingSnapshot,
|
||||
|
|
@ -1248,83 +1248,23 @@ async function loadRepoFaceHumanPronounGuidance(
|
|||
channelSnapshots: ChannelSnapshot[];
|
||||
},
|
||||
): Promise<RepoFaceHumanPronounGuidance[]> {
|
||||
const visibleHumans = new Map<string, string>();
|
||||
for (const message of [
|
||||
...(roomContext?.recentMessages ?? []),
|
||||
...(roomContext?.channelSnapshots.flatMap((snapshot) => snapshot.messages) ?? []),
|
||||
]) {
|
||||
if (!message.isBot && message.authorId) {
|
||||
visibleHumans.set(message.authorId, message.authorName || message.authorId);
|
||||
}
|
||||
}
|
||||
visibleHumans.set(config.ownerDiscordId, visibleHumans.get(config.ownerDiscordId) ?? "Metacrat");
|
||||
|
||||
const storage = await createStateStorage({
|
||||
backend: config.stateStorageBackend,
|
||||
databaseDsn: config.databaseDsn,
|
||||
jobsFile: config.jobsFile,
|
||||
auditLogFile: config.auditLogFile,
|
||||
interactionMemoryFile: config.interactionMemoryFile,
|
||||
rateLimitStateFile: config.rateLimitStateFile,
|
||||
return readPersonaHumanPronounGuidance({
|
||||
ownerActorId: config.ownerDiscordId,
|
||||
ownerFallbackName: "Metacrat",
|
||||
recentMessages: roomContext?.recentMessages ?? [],
|
||||
channelSnapshots: roomContext?.channelSnapshots ?? [],
|
||||
openProfiles: async () => {
|
||||
const storage = await createStateStorage({
|
||||
backend: config.stateStorageBackend,
|
||||
databaseDsn: config.databaseDsn,
|
||||
jobsFile: config.jobsFile,
|
||||
auditLogFile: config.auditLogFile,
|
||||
interactionMemoryFile: config.interactionMemoryFile,
|
||||
rateLimitStateFile: config.rateLimitStateFile,
|
||||
});
|
||||
return { getProfile: (actorId) => storage.interactionMemory.getProfile(actorId), close: () => storage.close() };
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const profiles = await Promise.all(
|
||||
[...visibleHumans.entries()].map(async ([actorId, fallbackName]) => ({
|
||||
actorId,
|
||||
fallbackName,
|
||||
profile: await storage.interactionMemory.getProfile(actorId),
|
||||
})),
|
||||
);
|
||||
|
||||
return profiles
|
||||
.map(({ actorId, fallbackName, profile }) =>
|
||||
profile ? repoFacePronounGuidanceFromProfile(actorId, fallbackName, profile) : undefined,
|
||||
)
|
||||
.filter((entry): entry is RepoFaceHumanPronounGuidance => entry !== undefined);
|
||||
} finally {
|
||||
await storage.close();
|
||||
}
|
||||
}
|
||||
|
||||
function repoFacePronounGuidanceFromProfile(
|
||||
actorId: string,
|
||||
fallbackName: string,
|
||||
profile: InteractionMemoryProfile,
|
||||
): RepoFaceHumanPronounGuidance | undefined {
|
||||
if (profile.pronounPolicy === "unknown" || profile.resolvedPronounSets.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const evidence = [...profile.pronounEvidence]
|
||||
.filter((entry) => entry.stance === "prefer" || entry.stance === "avoid")
|
||||
.sort((left, right) => pronounEvidenceRank(profile, right) - pronounEvidenceRank(profile, left))[0];
|
||||
|
||||
return {
|
||||
actorId,
|
||||
actorName: profile.actorName || fallbackName,
|
||||
guidance: profile.pronounGuidance,
|
||||
resolvedPronounSet: profile.resolvedPronounSet,
|
||||
policy: profile.pronounPolicy,
|
||||
confidence: profile.pronounConfidence,
|
||||
evidenceExcerpt: evidence?.excerpt,
|
||||
};
|
||||
}
|
||||
|
||||
function pronounEvidenceRank(profile: InteractionMemoryProfile, entry: InteractionMemoryProfile["pronounEvidence"][number]): number {
|
||||
const sourceRank: Record<string, number> = {
|
||||
explicit_self_statement: 10_000,
|
||||
explicit_correction: 9_000,
|
||||
direct_third_party_statement: 7_000,
|
||||
contextual_relational_inference: 3_000,
|
||||
ambient_usage: 1_000,
|
||||
};
|
||||
const resolvedSetBonus = profile.resolvedPronounSets.includes(entry.pronounSet) ? 50_000 : 0;
|
||||
const stanceBonus = entry.stance === "prefer" ? 1_000 : 0;
|
||||
const confidenceBonus = Math.round(entry.confidence * 100);
|
||||
const timestampMs = Date.parse(entry.timestamp);
|
||||
const recencyBonus = Number.isFinite(timestampMs) ? timestampMs / 10_000_000_000 : 0;
|
||||
return resolvedSetBonus + (sourceRank[entry.source] ?? 0) + stanceBonus + confidenceBonus + recencyBonus;
|
||||
}
|
||||
|
||||
function normalizeSocialLabel(value: string | undefined): string {
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ import { composePersonaMemoryPacket, projectPersonaMemorySurface, renderPersonaP
|
|||
import { readPersonaCuriosityEvidence } from "../apps/persona-scheduler/dist/persona-curiosity-context-source.js";
|
||||
import { projectPersonaCuriosityContext } from "../apps/persona-scheduler/dist/persona-curiosity-projector.js";
|
||||
import { observePersonaRoomTexture, projectPersonaSocialContext, renderPersonaHumanClarityPressure, renderPersonaHumanPronounFacts, renderPersonaRoomWeather, renderPersonaSocialGraph } from "../apps/persona-scheduler/dist/persona-social-context-projector.js";
|
||||
import { readPersonaHumanPronounGuidance } from "../apps/persona-scheduler/dist/persona-social-context-source.js";
|
||||
|
||||
function participant(identityId: string, nextTurnAt: number): InitiativeParticipant {
|
||||
return {
|
||||
|
|
@ -456,6 +457,30 @@ const roomObservation = observePersonaRoomTexture({
|
|||
assert.equal(roomObservation?.texture, "light", "social-context projection derives room weather from supplied messages without acquiring Discord state");
|
||||
assert.match(renderPersonaRoomWeather({ identity: routedIdentity, recentMessages: [], channelSnapshots: [] }), /No current room weather/, "absent room evidence stays explicit");
|
||||
assert.match(renderPersonaHumanPronounFacts([{ actorId: "human", actorName: "Operator", guidance: "use they/them", policy: "explicit", confidence: 1 }]) ?? "", /Confidence: 1\.00/, "pronoun guidance presentation belongs to the social-context projector");
|
||||
let pronounReaderClosed = false;
|
||||
const pronounGuidance = await readPersonaHumanPronounGuidance({
|
||||
ownerActorId: "owner",
|
||||
ownerFallbackName: "Metacrat",
|
||||
recentMessages: [{ id: "human-pronouns", authorId: "human", authorName: "Operator", content: "hello", timestamp: "2026-07-15T21:00:00.000Z", isBot: false, channelId: "aquarium" }],
|
||||
channelSnapshots: [],
|
||||
openProfiles: async () => ({
|
||||
getProfile: async (actorId) => actorId === "human" ? {
|
||||
actorName: "Operator",
|
||||
pronounPolicy: "explicit",
|
||||
resolvedPronounSets: ["they/them"],
|
||||
resolvedPronounSet: "they/them",
|
||||
pronounGuidance: "use they/them",
|
||||
pronounConfidence: 0.95,
|
||||
pronounEvidence: [
|
||||
{ pronounSet: "they/them", stance: "prefer", source: "explicit_correction", confidence: 0.9, timestamp: "2026-07-14T20:00:00.000Z", excerpt: "please use they/them" },
|
||||
{ pronounSet: "they/them", stance: "prefer", source: "ambient_usage", confidence: 1, timestamp: "2026-07-15T20:00:00.000Z", excerpt: "someone said they" },
|
||||
],
|
||||
} as never : undefined,
|
||||
close: async () => { pronounReaderClosed = true; },
|
||||
}),
|
||||
});
|
||||
assert.match(pronounGuidance[0]?.evidenceExcerpt ?? "", /please use they\/them/, "the social-context source owns pronoun evidence precedence rather than favoring merely newer ambient usage");
|
||||
assert.equal(pronounReaderClosed, true, "the social-context source closes its injected interaction-profile reader");
|
||||
const clarityPressure = renderPersonaHumanClarityPressure({
|
||||
identity: routedIdentity,
|
||||
recentMessages: [
|
||||
|
|
|
|||
Loading…
Reference in a new issue