Project temporary Persona affect state in swarm harness
This commit is contained in:
parent
33e407fb86
commit
b873517ccd
1 changed files with 210 additions and 4 deletions
|
|
@ -208,7 +208,26 @@ async function runPhase(input) {
|
||||||
|
|
||||||
async function runAttempt(input) {
|
async function runAttempt(input) {
|
||||||
const candidate = input.job.candidate;
|
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);
|
const conversationSurface = renderConversationSurface(candidate);
|
||||||
await writeFile(resolve(input.attemptRoot, "memory-surface.md"), memorySurface, "utf8");
|
await writeFile(resolve(input.attemptRoot, "memory-surface.md"), memorySurface, "utf8");
|
||||||
await writeFile(resolve(input.attemptRoot, "conversation-surface.md"), conversationSurface, "utf8");
|
await writeFile(resolve(input.attemptRoot, "conversation-surface.md"), conversationSurface, "utf8");
|
||||||
|
|
@ -254,6 +273,8 @@ async function runAttempt(input) {
|
||||||
|
|
||||||
const loweringRequest = renderWeksaLoweringRequest({
|
const loweringRequest = renderWeksaLoweringRequest({
|
||||||
memorySurface,
|
memorySurface,
|
||||||
|
temporaryPersonaState,
|
||||||
|
temporaryPersonaStatePath,
|
||||||
culturePack: input.culturePack,
|
culturePack: input.culturePack,
|
||||||
interlingua,
|
interlingua,
|
||||||
promptState: input.promptState,
|
promptState: input.promptState,
|
||||||
|
|
@ -277,7 +298,9 @@ async function runAttempt(input) {
|
||||||
worker: input.job.worker,
|
worker: input.job.worker,
|
||||||
attempt: input.job.attempt,
|
attempt: input.job.attempt,
|
||||||
candidate: redactedCandidateRecord(candidate),
|
candidate: redactedCandidateRecord(candidate),
|
||||||
|
temporary_persona_state: summarizeTemporaryPersonaState(temporaryPersonaState),
|
||||||
spoken_text: spokenText,
|
spoken_text: spokenText,
|
||||||
|
generated_affect: inferTextAffect(spokenText),
|
||||||
target_text: candidate.target.content,
|
target_text: candidate.target.content,
|
||||||
score,
|
score,
|
||||||
loss: Number((1 - score.f1).toFixed(3)),
|
loss: Number((1 - score.f1).toFixed(3)),
|
||||||
|
|
@ -307,7 +330,7 @@ async function runOptimizationPass({ candidates, promptState, phaseSummary, cult
|
||||||
" \"candidate_id\": \"...\",",
|
" \"candidate_id\": \"...\",",
|
||||||
" \"run_id\": \"...\",",
|
" \"run_id\": \"...\",",
|
||||||
" \"loss\": 0.0,",
|
" \"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\",",
|
" \"evidence\": \"short source-grounded reason, no target wording\",",
|
||||||
" \"confidence\": 0.0",
|
" \"confidence\": 0.0",
|
||||||
" }",
|
" }",
|
||||||
|
|
@ -329,6 +352,8 @@ async function runOptimizationPass({ candidates, promptState, phaseSummary, cult
|
||||||
"Allowed prompt delta surfaces:",
|
"Allowed prompt delta surfaces:",
|
||||||
JSON.stringify(promptStateKeys, null, 2),
|
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:",
|
"Delta acceptance policy in the harness:",
|
||||||
"- apply add-deltas only",
|
"- apply add-deltas only",
|
||||||
"- accept weight >= 0.85 with at least one candidate",
|
"- accept weight >= 0.85 with at least one candidate",
|
||||||
|
|
@ -394,6 +419,50 @@ function renderFaceTurnRequest({ assembledPrompt, candidate, promptState }) {
|
||||||
].join("\n");
|
].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 }) {
|
function renderInterpreterInterlinguaRequest({ faceTurn, candidate, promptState }) {
|
||||||
return [
|
return [
|
||||||
"# Face Interpreter To Weksa Interlingua",
|
"# Face Interpreter To Weksa Interlingua",
|
||||||
|
|
@ -419,7 +488,7 @@ function renderInterpreterInterlinguaRequest({ faceTurn, candidate, promptState
|
||||||
].join("\n");
|
].join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderWeksaLoweringRequest({ memorySurface, culturePack, interlingua, promptState, holdouts }) {
|
function renderWeksaLoweringRequest({ memorySurface, temporaryPersonaState, temporaryPersonaStatePath, culturePack, interlingua, promptState, holdouts }) {
|
||||||
const redactedProfiles = culturePack.profiles.map((profile) => ({
|
const redactedProfiles = culturePack.profiles.map((profile) => ({
|
||||||
...profile,
|
...profile,
|
||||||
text: redactHoldouts(profile.text, holdouts),
|
text: redactHoldouts(profile.text, holdouts),
|
||||||
|
|
@ -441,6 +510,12 @@ function renderWeksaLoweringRequest({ memorySurface, culturePack, interlingua, p
|
||||||
memorySurface,
|
memorySurface,
|
||||||
"```",
|
"```",
|
||||||
"",
|
"",
|
||||||
|
"Temporary Persona `.cc` projection mounted for this test:",
|
||||||
|
temporaryPersonaStatePath,
|
||||||
|
"```yaml",
|
||||||
|
redactHoldouts(temporaryPersonaState, holdouts),
|
||||||
|
"```",
|
||||||
|
"",
|
||||||
"Metame cultural stack reference:",
|
"Metame cultural stack reference:",
|
||||||
"```yaml",
|
"```yaml",
|
||||||
culturePack.stack,
|
culturePack.stack,
|
||||||
|
|
@ -597,7 +672,7 @@ function renderConversationSurface(candidate) {
|
||||||
].join("\n");
|
].join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMemorySurface(baseMemorySurface, candidate, promptState, job) {
|
function renderMemorySurface({ baseMemorySurface, candidate, promptState, job, temporaryPersonaState, temporaryPersonaStatePath }) {
|
||||||
return [
|
return [
|
||||||
baseMemorySurface,
|
baseMemorySurface,
|
||||||
"",
|
"",
|
||||||
|
|
@ -606,6 +681,11 @@ function renderMemorySurface(baseMemorySurface, candidate, promptState, job) {
|
||||||
`- Treat channel ${candidate.channelName || candidate.channelId} in ${candidate.year} as the live room.`,
|
`- 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.`,
|
`- The hidden target is a ${candidate.lengthClass} Metacrat message after the visible context.`,
|
||||||
`- Candidate features: ${candidate.features.join(", ") || "none"}.`,
|
`- 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.projectorNotes.map((note) => `- Projector pressure: ${note}`),
|
||||||
...promptState.personaStateAdjustmentNotes.map((note) => `- Temporary Persona-state adjustment: ${note}`),
|
...promptState.personaStateAdjustmentNotes.map((note) => `- Temporary Persona-state adjustment: ${note}`),
|
||||||
...promptState.memoryNotes.map((note) => `- ${note}`),
|
...promptState.memoryNotes.map((note) => `- ${note}`),
|
||||||
|
|
@ -700,6 +780,38 @@ function renderDryAssembledPrompt(memorySurface, conversationSurface) {
|
||||||
].join("\n");
|
].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) {
|
function renderDryFaceTurn(candidate) {
|
||||||
return [
|
return [
|
||||||
"Private thought: This is a dry-run reconstruction of what the visible room seems to be asking for.",
|
"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,
|
agent: run.agent,
|
||||||
attempt: run.attempt,
|
attempt: run.attempt,
|
||||||
spoken_text: run.spoken_text,
|
spoken_text: run.spoken_text,
|
||||||
|
temporary_persona_state: run.temporary_persona_state,
|
||||||
|
generated_affect: run.generated_affect,
|
||||||
token_f1: run.score.f1,
|
token_f1: run.score.f1,
|
||||||
loss: run.loss,
|
loss: run.loss,
|
||||||
};
|
};
|
||||||
|
|
@ -1034,6 +1148,98 @@ function detectFeatures(content, before, targetAuthorId) {
|
||||||
return [...new Set(features)];
|
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) {
|
function guessSpeechAct(candidate) {
|
||||||
const features = candidate.features;
|
const features = candidate.features;
|
||||||
if (features.includes("repair_or_recoil")) return "repair_or_playful_recoil";
|
if (features.includes("repair_or_recoil")) return "repair_or_playful_recoil";
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue