Project temporary Persona affect state in swarm harness

This commit is contained in:
Meta Vens 2026-06-11 13:30:30 +01:00
commit b873517ccd

View file

@ -208,7 +208,26 @@ async function runPhase(input) {
async function runAttempt(input) {
const candidate = input.job.candidate;
const memorySurface = renderMemorySurface(input.baseMemorySurface, candidate, input.promptState, input.job);
const temporaryPersonaStateRequest = renderTemporaryPersonaStateRequest({ candidate, promptState: input.promptState });
await writeFile(resolve(input.attemptRoot, "temporary-persona-state-request.md"), temporaryPersonaStateRequest, "utf8");
const temporaryPersonaState = input.dryRun
? renderDryTemporaryPersonaState(candidate)
: await runCodex(temporaryPersonaStateRequest, {
job: `${input.job.id}:persona-state-projector`,
cwd: repoRoot,
logRoot: input.attemptRoot,
});
const temporaryPersonaStatePath = resolve(input.attemptRoot, "temporary-persona-state.cc");
await writeFile(temporaryPersonaStatePath, `${temporaryPersonaState.trim()}\n`, "utf8");
const memorySurface = renderMemorySurface({
baseMemorySurface: input.baseMemorySurface,
candidate,
promptState: input.promptState,
job: input.job,
temporaryPersonaState,
temporaryPersonaStatePath,
});
const conversationSurface = renderConversationSurface(candidate);
await writeFile(resolve(input.attemptRoot, "memory-surface.md"), memorySurface, "utf8");
await writeFile(resolve(input.attemptRoot, "conversation-surface.md"), conversationSurface, "utf8");
@ -254,6 +273,8 @@ async function runAttempt(input) {
const loweringRequest = renderWeksaLoweringRequest({
memorySurface,
temporaryPersonaState,
temporaryPersonaStatePath,
culturePack: input.culturePack,
interlingua,
promptState: input.promptState,
@ -277,7 +298,9 @@ async function runAttempt(input) {
worker: input.job.worker,
attempt: input.job.attempt,
candidate: redactedCandidateRecord(candidate),
temporary_persona_state: summarizeTemporaryPersonaState(temporaryPersonaState),
spoken_text: spokenText,
generated_affect: inferTextAffect(spokenText),
target_text: candidate.target.content,
score,
loss: Number((1 - score.f1).toFixed(3)),
@ -307,7 +330,7 @@ async function runOptimizationPass({ candidates, promptState, phaseSummary, cult
" \"candidate_id\": \"...\",",
" \"run_id\": \"...\",",
" \"loss\": 0.0,",
" \"failure_labels\": [\"stance_error\", \"referent_loss\"],",
" \"failure_labels\": [\"stance_error\", \"referent_loss\", \"affect_mismatch\"],",
" \"evidence\": \"short source-grounded reason, no target wording\",",
" \"confidence\": 0.0",
" }",
@ -329,6 +352,8 @@ async function runOptimizationPass({ candidates, promptState, phaseSummary, cult
"Allowed prompt delta surfaces:",
JSON.stringify(promptStateKeys, null, 2),
"",
"Failure labels should include affect mismatches when the generated line's affect does not match the temporary Persona `.cc` projection.",
"",
"Delta acceptance policy in the harness:",
"- apply add-deltas only",
"- accept weight >= 0.85 with at least one candidate",
@ -394,6 +419,50 @@ function renderFaceTurnRequest({ assembledPrompt, candidate, promptState }) {
].join("\n");
}
function renderTemporaryPersonaStateRequest({ candidate, promptState }) {
return [
"# Historical Persona-State Projector",
"",
"Project a temporary Persona state overlay for this held-out historical scenario.",
"Use only visible context and candidate metadata. The target utterance is hidden and must not be inferred as text.",
"This is not canonical Persona state. It is a run-scoped `.cc` projection for affect, stance, social pressure, and live-room posture.",
"",
"Return YAML-compatible CultCache text only with this shape:",
"schema_version: weksa.temporary_persona_state_projection.v0",
"authority: run_scoped_projection_not_canonical",
"persona:",
" id: metame",
" canonical_state_ref: E:/Projects/VoidBot/.voidbot/private/personas/metame/metame.cc",
"scenario:",
" candidate_id:",
" channel:",
" year:",
"affect:",
" valence: -1.0_to_1.0",
" arousal: 0.0_to_1.0",
" dominance: 0.0_to_1.0",
" labels: []",
" evidence: []",
"stance:",
" posture:",
" social_move_likelihoods: []",
" register_pressure: []",
"lowering_pressure:",
" should_sound:",
" should_not_sound:",
"",
"Temporary optimizer notes:",
...promptState.memoryNotes.map((note) => `- ${note}`),
...promptState.interpreterNotes.map((note) => `- ${note}`),
...promptState.loweringNotes.map((note) => `- ${note}`),
"",
"Visible scenario:",
"```yaml",
renderCandidateScene(candidate),
"```",
].join("\n");
}
function renderInterpreterInterlinguaRequest({ faceTurn, candidate, promptState }) {
return [
"# Face Interpreter To Weksa Interlingua",
@ -419,7 +488,7 @@ function renderInterpreterInterlinguaRequest({ faceTurn, candidate, promptState
].join("\n");
}
function renderWeksaLoweringRequest({ memorySurface, culturePack, interlingua, promptState, holdouts }) {
function renderWeksaLoweringRequest({ memorySurface, temporaryPersonaState, temporaryPersonaStatePath, culturePack, interlingua, promptState, holdouts }) {
const redactedProfiles = culturePack.profiles.map((profile) => ({
...profile,
text: redactHoldouts(profile.text, holdouts),
@ -441,6 +510,12 @@ function renderWeksaLoweringRequest({ memorySurface, culturePack, interlingua, p
memorySurface,
"```",
"",
"Temporary Persona `.cc` projection mounted for this test:",
temporaryPersonaStatePath,
"```yaml",
redactHoldouts(temporaryPersonaState, holdouts),
"```",
"",
"Metame cultural stack reference:",
"```yaml",
culturePack.stack,
@ -597,7 +672,7 @@ function renderConversationSurface(candidate) {
].join("\n");
}
function renderMemorySurface(baseMemorySurface, candidate, promptState, job) {
function renderMemorySurface({ baseMemorySurface, candidate, promptState, job, temporaryPersonaState, temporaryPersonaStatePath }) {
return [
baseMemorySurface,
"",
@ -606,6 +681,11 @@ function renderMemorySurface(baseMemorySurface, candidate, promptState, job) {
`- Treat channel ${candidate.channelName || candidate.channelId} in ${candidate.year} as the live room.`,
`- The hidden target is a ${candidate.lengthClass} Metacrat message after the visible context.`,
`- Candidate features: ${candidate.features.join(", ") || "none"}.`,
`- Temporary Persona .cc projection path: ${temporaryPersonaStatePath}`,
"- Temporary Persona .cc projection body:",
"```yaml",
temporaryPersonaState.trim(),
"```",
...promptState.projectorNotes.map((note) => `- Projector pressure: ${note}`),
...promptState.personaStateAdjustmentNotes.map((note) => `- Temporary Persona-state adjustment: ${note}`),
...promptState.memoryNotes.map((note) => `- ${note}`),
@ -700,6 +780,38 @@ function renderDryAssembledPrompt(memorySurface, conversationSurface) {
].join("\n");
}
function renderDryTemporaryPersonaState(candidate) {
const generated = inferTextAffect(candidate.before.map((message) => message.content).join("\n"));
return [
"schema_version: weksa.temporary_persona_state_projection.v0",
"authority: run_scoped_projection_not_canonical",
"persona:",
" id: metame",
" canonical_state_ref: E:/Projects/VoidBot/.voidbot/private/personas/metame/metame.cc",
"scenario:",
` candidate_id: "${candidate.id}"`,
` channel: "${escapeYaml(candidate.channelName || candidate.channelId)}"`,
` year: ${candidate.year}`,
"affect:",
` valence: ${generated.valence}`,
` arousal: ${generated.arousal}`,
` dominance: ${generated.dominance}`,
" labels:",
...generated.labels.map((label) => ` - ${label}`),
" evidence:",
" - dry_run_visible_context_proxy",
"stance:",
" posture: embedded_participant",
" social_move_likelihoods:",
` - ${guessSpeechAct(candidate)}`,
" register_pressure:",
" - casual_discord",
"lowering_pressure:",
" should_sound: situated, local, and persona-owned",
" should_not_sound: generic assistant commentary",
].join("\n");
}
function renderDryFaceTurn(candidate) {
return [
"Private thought: This is a dry-run reconstruction of what the visible room seems to be asking for.",
@ -984,6 +1096,8 @@ function compactRun(run) {
agent: run.agent,
attempt: run.attempt,
spoken_text: run.spoken_text,
temporary_persona_state: run.temporary_persona_state,
generated_affect: run.generated_affect,
token_f1: run.score.f1,
loss: run.loss,
};
@ -1034,6 +1148,98 @@ function detectFeatures(content, before, targetAuthorId) {
return [...new Set(features)];
}
function inferTextAffect(content) {
const text = String(content ?? "");
const lower = text.toLowerCase();
const labels = [];
let valence = 0;
let arousal = 0.35;
let dominance = 0.5;
if (/\b(lol|lmao|haha|ahaha|hehe)\b/i.test(text)) {
labels.push("amused");
valence += 0.25;
arousal += 0.1;
}
if (/\b(fuck|shit|damn|ass|bullshit)\b/i.test(text)) {
labels.push("agitated_or_emphatic");
valence -= 0.1;
arousal += 0.25;
dominance += 0.1;
}
if (/\b(oof|sorry|my bad|aw)\b/i.test(text)) {
labels.push("softened_or_recoiling");
valence -= 0.05;
dominance -= 0.1;
}
if (/[!?]{1,}$/.test(text.trim()) || /[A-Z]{4,}/.test(text)) {
labels.push("high_energy");
arousal += 0.2;
}
if (/\b(no|nah|wrong|can't|wont|won't|don't|stop)\b/i.test(text)) {
labels.push("resistant");
valence -= 0.15;
dominance += 0.1;
}
if (/\b(yeah|yep|honestly|i mean)\b/i.test(lower)) {
labels.push("conversational");
}
return {
valence: round(clampNumber(valence, -1, 1)),
arousal: round(clampNumber(arousal, 0, 1)),
dominance: round(clampNumber(dominance, 0, 1)),
labels: labels.length ? [...new Set(labels)] : ["neutral_or_unmarked"],
};
}
function summarizeTemporaryPersonaState(text) {
const labels = [];
const evidence = [];
let inLabels = false;
let inEvidence = false;
for (const line of String(text ?? "").split(/\r?\n/)) {
if (/^\s*labels:\s*$/.test(line)) {
inLabels = true;
inEvidence = false;
continue;
}
if (/^\s*evidence:\s*$/.test(line)) {
inLabels = false;
inEvidence = true;
continue;
}
if (/^\S/.test(line)) {
inLabels = false;
inEvidence = false;
}
const item = /^\s*-\s*(.+?)\s*$/.exec(line)?.[1]?.replace(/^["']|["']$/g, "");
if (item && inLabels) {
labels.push(item);
}
if (item && inEvidence) {
evidence.push(item);
}
}
return {
schema_version: /^schema_version:\s*(.+)$/m.exec(text)?.[1]?.trim(),
authority: /^authority:\s*(.+)$/m.exec(text)?.[1]?.trim(),
affect: {
valence: numberFromField(text, "valence"),
arousal: numberFromField(text, "arousal"),
dominance: numberFromField(text, "dominance"),
labels: labels.slice(0, 8),
evidence: evidence.slice(0, 4),
},
stance: {
posture: /^ posture:\s*(.+)$/m.exec(text)?.[1]?.trim(),
},
};
}
function numberFromField(text, field) {
const value = new RegExp(`^\\s*${field}:\\s*(-?\\d+(?:\\.\\d+)?)`, "m").exec(text)?.[1];
return value === undefined ? undefined : Number(value);
}
function guessSpeechAct(candidate) {
const features = candidate.features;
if (features.includes("repair_or_recoil")) return "repair_or_playful_recoil";