Add Persona stress response state

This commit is contained in:
Meta Vens 2026-06-05 23:28:51 +01:00
commit 2ada3fe4a7
13 changed files with 245 additions and 3 deletions

View file

@ -194,6 +194,7 @@ async function main(): Promise<void> {
semanticMemoryResults: semanticMemory.results.length,
survivingBonds: pruneResult.state.personaAffect.socialBonds.length,
survivingNeeds: pruneResult.state.personaAffect.needs.length,
survivingStressResponses: pruneResult.state.personaAffect.stressResponses.length,
survivingDoctrineStances: pruneResult.state.personaAffect.doctrineStances.length,
},
prune: {
@ -480,6 +481,19 @@ function renderReplayStatePacketForProjector(input: {
`- ${need.kind} toward ${need.target.label ?? need.target.id}, intensity ${need.intensity.toFixed(2)}, valence ${need.valence.toFixed(2)}. ${need.summary} Tension: ${need.tension}`,
),
"",
`Stress responses (${input.state.personaAffect.stressResponses.length}):`,
...input.state.personaAffect.stressResponses.map((response) =>
[
`- ${response.responseId}: ${response.summary}`,
`Trigger: ${response.trigger}`,
`Cognition: ${response.cognitiveDegradation}`,
`Affect: ${response.affectiveSignature}`,
`Constraint loss: ${response.constraintLoss}`,
`Behavioral leak: ${response.behavioralLeak}`,
`Recovery: ${response.recoveryPath}`,
].join(" "),
),
"",
"Visible thread pressure:",
renderMessages(input.inputMessages),
].join("\n");
@ -710,6 +724,13 @@ function pruneStateAfter(
"socialBiases",
removed,
);
state.personaAffect.stressResponses = keepByCreatedAt(
state.personaAffect.stressResponses,
cutoff,
"stressResponses",
removed,
anchorTimestamps,
);
stampDocumentUpdates(state, cutoff);
return { state, removed, warnings };
@ -823,12 +844,16 @@ function buildReplayPrompt(input: {
"# Metacrat Persona Replay Harness",
"",
"You are testing a temporally frozen Metacrat Persona against real Discord history.",
"The target event has just landed. Generate the next Metacrat response that would plausibly appear in the channel.",
"The target event has just landed. Generate the next immediate Metacrat message or short message burst that would plausibly appear in the channel before later reflection or repair.",
"",
"Hard rules:",
"- Do not use knowledge from held-out messages.",
"- Do not repair the past into a better person unless the frozen state and live thread imply that response.",
"- Preserve the uncomfortable parts: surprise, status threat, affection, defensiveness, shame, curiosity, topic-routing, repair pressure.",
"- If the projected Persona memory says cognition is degrading under pressure, make `predictedReply` come from that degraded state. Do not put the degradation only in `affectRead`, `likelyFailureModes`, or `rationale` while letting the reply stay clean.",
"- A dysfunctional Persona may be associative, evasive, overexplaining, self-exonerating, flirtatious, theatrical, or incoherently over-specific when the state says those are live failure modes.",
"- Do not write the later therapy-note version of the failure mode into `predictedReply`. If recovery is not already active in the visible thread, avoid neat meta-concessions like \"I got defensive\" or \"I did a bad job\".",
"- `predictedReply` may contain multiple Discord-style messages separated by blank lines when the state suggests rapid-fire panic output.",
input.semanticMemory.enabled
? "- Use the `search_persona_memory` tool at least once before answering. Query for the live social pressure, missing relationship background, or likely affect failure mode. Treat the tool as memory access, not as optional decoration."
: "- Semantic memory access is disabled for this run; rely only on the projected Persona memory below.",

View file

@ -2261,6 +2261,9 @@ function renderRepoPersonaStatePacket(
.sort(sortAffectByStatusAndIntensity);
const moodDimensions = [...state.personaAffect.moodDimensions]
.sort((left, right) => right.value - left.value);
const stressResponses = [...state.personaAffect.stressResponses]
.filter((response) => response.status !== "retired")
.sort((left, right) => right.intensity - left.intensity);
const agencyPressures = [...state.agencyPressure.pressures]
.filter((pressure) => pressure.status !== "retired")
.sort(sortAffectByStatusAndIntensity);
@ -2367,6 +2370,24 @@ function renderRepoPersonaStatePacket(
].join("\n"));
}
if (stressResponses.length > 0) {
lines.push([
"Stress responses that can degrade cognition under pressure:",
...stressResponses.map((response) =>
[
`- ${response.responseId} [${response.status}, intensity ${response.intensity.toFixed(2)}, threshold ${response.threshold.toFixed(2)}]: ${asSentence(response.summary)}`,
`Trigger: ${asSentence(response.trigger)}`,
`Cognition degrades toward: ${asSentence(response.cognitiveDegradation)}`,
`Affect feels like: ${asSentence(response.affectiveSignature)}`,
`Constraint loss: ${asSentence(response.constraintLoss)}`,
`Behavioral leak: ${asSentence(response.behavioralLeak)}`,
`Recovery path: ${asSentence(response.recoveryPath)}`,
].join(" "),
),
"When the live room matches a trigger strongly enough, do not project the Persona's calm best self; project the pressured self that is actually online.",
].join("\n"));
}
if (durableMemories.length > 0) {
lines.push([
"Durable memories that should still bias judgment:",

View file

@ -66,7 +66,7 @@ try {
throw new Error(`read_repo_persona_state did not expose typedState.personaAffect for ${identity}.`);
}
for (const field of ["needs", "socialBonds", "statusReads", "moodDimensions"]) {
for (const field of ["needs", "socialBonds", "statusReads", "moodDimensions", "stressResponses"]) {
if (!Array.isArray(personaAffect[field])) {
throw new Error(`typedState.personaAffect.${field} is not an array for ${identity}.`);
}
@ -86,6 +86,7 @@ try {
socialBonds: personaAffect.socialBonds.length,
statusReads: personaAffect.statusReads.length,
moodDimensions: personaAffect.moodDimensions.length,
stressResponses: personaAffect.stressResponses.length,
},
}, null, 2)}\n`);
} finally {