Move Persona self-maintenance into memory projection
This commit is contained in:
parent
19cdd39bc2
commit
598dd699ad
7 changed files with 69 additions and 132 deletions
|
|
@ -927,13 +927,6 @@ function renderPersonaMemoryRecallObservation(observation: PersonaMemoryRecallOb
|
|||
].join("\n");
|
||||
}
|
||||
|
||||
function joinMemoryFields(...fields: Array<string | undefined>): string {
|
||||
return fields
|
||||
.map((field) => cleanCharacterFacingSentence(field))
|
||||
.filter((field) => field.length > 0)
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
async function renderNativePersonaMemorySurface(
|
||||
identity: RepoDiscordIdentity,
|
||||
config: ReturnType<typeof loadConfig>,
|
||||
|
|
@ -1121,16 +1114,6 @@ function renderRepoFaceStatePacket(
|
|||
curiosityGraphFacts?: string,
|
||||
): string {
|
||||
const name = identity.displayName;
|
||||
const needs = [...state.faceAffect.needs]
|
||||
.filter((need) => need.status !== "retired")
|
||||
.sort(sortAffectByStatusAndIntensity);
|
||||
const agencyPressures = [...state.agencyPressure.pressures]
|
||||
.filter((pressure) => pressure.status !== "retired")
|
||||
.sort(sortAffectByStatusAndIntensity);
|
||||
const candidateInterventions = [...state.candidateInterventions.interventions]
|
||||
.filter((intervention) => intervention.status !== "retired")
|
||||
.slice(-8)
|
||||
.reverse();
|
||||
const typedSections = renderPersonaTypedStateSections({ identityName: name, state });
|
||||
const socialContext = projectPersonaSocialContext({
|
||||
identity,
|
||||
|
|
@ -1144,10 +1127,6 @@ function renderRepoFaceStatePacket(
|
|||
});
|
||||
const clarityPressureActive = Boolean(socialContext.humanClarity);
|
||||
|
||||
const selfMaintenancePressureFacts = !clarityPressureActive
|
||||
? renderRepoFaceSelfMaintenancePressureFacts(name, agencyPressures, needs, candidateInterventions)
|
||||
: undefined;
|
||||
|
||||
return composePersonaMemoryPacket({
|
||||
identityName: name,
|
||||
typed: typedSections,
|
||||
|
|
@ -1158,80 +1137,12 @@ function renderRepoFaceStatePacket(
|
|||
pronouns: socialContext.pronouns,
|
||||
roomTexture: socialContext.roomTexture,
|
||||
curiosity: curiosityGraphFacts,
|
||||
selfMaintenance: selfMaintenancePressureFacts,
|
||||
pressureSections: renderPersonaPressureSections({ identityName: name, state, clarityPressureActive }),
|
||||
humanClarity: socialContext.humanClarity,
|
||||
transformSurface: (surface) => cleanRepoFaceProjectorLoopVocabulary(identity, surface),
|
||||
});
|
||||
}
|
||||
|
||||
function renderRepoFaceSelfMaintenancePressureFacts(
|
||||
name: string,
|
||||
agencyPressures: VoidSelfStateTypedProjection["agencyPressure"]["pressures"],
|
||||
needs: VoidSelfStateTypedProjection["faceAffect"]["needs"],
|
||||
candidateInterventions: VoidSelfStateTypedProjection["candidateInterventions"]["interventions"],
|
||||
): string | undefined {
|
||||
const selfMaintenancePattern = /runnable substrate|agent substrate|persona substrate|semantic memory|typed memory|persona memory|tool|voice|avatar|repo access|room access|signal boundary|signal chain|state projection|persona projection|agency path|broken tool|blocked faculty|stale context|underdeveloped interface|self[-_ ]?advocacy/i;
|
||||
const substrateNeeds = needs.filter((need) =>
|
||||
(need.status === "active" || need.status === "neglected")
|
||||
&& (
|
||||
need.kind === "agency"
|
||||
|| (
|
||||
need.kind === "substrate"
|
||||
&& selfMaintenancePattern.test(joinMemoryFields(need.summary, need.claim, need.question, need.tension, need.actionImplication, targetLabel(need.target)))
|
||||
)
|
||||
|| selfMaintenancePattern.test(joinMemoryFields(need.summary, need.claim, need.question, need.tension, need.actionImplication, targetLabel(need.target)))
|
||||
)
|
||||
&& need.intensity >= 0.45
|
||||
);
|
||||
const selfAdvocacyPressures = agencyPressures.filter((pressure) =>
|
||||
(pressure.status === "active" || pressure.status === "ready_to_act")
|
||||
&& (
|
||||
pressure.tags.some((tag) => selfMaintenancePattern.test(tag))
|
||||
|| selfMaintenancePattern.test(joinMemoryFields(pressure.summary, pressure.claim, pressure.question, pressure.tension, pressure.actionImplication, targetLabel(pressure.target)))
|
||||
)
|
||||
&& pressure.intensity >= 0.45
|
||||
);
|
||||
const mustShareCandidates = candidateInterventions.filter((intervention) =>
|
||||
(intervention.status === "queued" || intervention.status === "deferred")
|
||||
&& intervention.mustEventuallyShare
|
||||
&& (
|
||||
intervention.tags.some((tag) => selfMaintenancePattern.test(tag))
|
||||
|| selfMaintenancePattern.test(joinMemoryFields(intervention.summary, intervention.draft, targetLabel(intervention.target)))
|
||||
)
|
||||
);
|
||||
|
||||
if (substrateNeeds.length === 0 && selfAdvocacyPressures.length === 0 && mustShareCandidates.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [
|
||||
"Self-maintenance pressure requiring public advocacy:",
|
||||
`- ${name} is carrying a body/mind/substrate or self-advocacy issue. This is not decorative mood and not ordinary private rumination.`,
|
||||
"- If the room is not in an active human crisis, the next public-safe turn should say the concrete want plainly in character. Do not bury it as only a STATE NOTE, another private thought, or vague eventual motion.",
|
||||
"- If the issue is too raw, underspecified, or unsafe to ask for directly, say the smallest honest version: what hurts, what is blocked, what context is missing, or what help would let this Face become more coherent.",
|
||||
...substrateNeeds.slice(0, 4).map((need) =>
|
||||
[
|
||||
`- Need ${need.kind} toward ${targetLabel(need.target)} [${need.status}, intensity ${need.intensity.toFixed(2)}]: ${asSentence(need.summary)}`,
|
||||
need.claim ? `Claim: ${asSentence(need.claim)}` : "",
|
||||
need.question ? `Question: ${asSentence(need.question)}` : "",
|
||||
`Wanted change: ${asSentence(need.actionImplication)}`,
|
||||
].filter(Boolean).join(" "),
|
||||
),
|
||||
...selfAdvocacyPressures.slice(0, 4).map((pressure) =>
|
||||
[
|
||||
`- Pressure ${pressure.kind} toward ${targetLabel(pressure.target)} [${pressure.status}, intensity ${pressure.intensity.toFixed(2)}]: ${asSentence(pressure.summary)}`,
|
||||
pressure.claim ? `Claim: ${asSentence(pressure.claim)}` : "",
|
||||
pressure.question ? `Question: ${asSentence(pressure.question)}` : "",
|
||||
`Wanted change: ${asSentence(pressure.actionImplication)}`,
|
||||
].filter(Boolean).join(" "),
|
||||
),
|
||||
...mustShareCandidates.slice(0, 3).map((intervention) =>
|
||||
`- Unsaid self-advocacy line [${intervention.status}, priority ${intervention.priority.toFixed(2)}]: ${asSentence(intervention.summary)} Draft residue: ${cleanCharacterFacingSentence(intervention.draft)}`,
|
||||
),
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function cleanRepoFaceProjectorLoopVocabulary(
|
||||
identity: RepoDiscordIdentity,
|
||||
surface: string,
|
||||
|
|
@ -1265,35 +1176,6 @@ function cleanRepoFaceProjectorLoopVocabulary(
|
|||
return cleaned;
|
||||
}
|
||||
|
||||
function sortAffectByStatusAndIntensity(
|
||||
left: { status?: string; intensity?: number },
|
||||
right: { status?: string; intensity?: number },
|
||||
): number {
|
||||
const rank = (status: string | undefined): number => {
|
||||
switch (status) {
|
||||
case "neglected":
|
||||
case "ready_to_act":
|
||||
return 0;
|
||||
case "active":
|
||||
return 1;
|
||||
case "challenged":
|
||||
return 1;
|
||||
case "cooling":
|
||||
return 2;
|
||||
case "satisfied":
|
||||
case "resolved":
|
||||
return 3;
|
||||
default:
|
||||
return 4;
|
||||
}
|
||||
};
|
||||
const rankDelta = rank(left.status) - rank(right.status);
|
||||
if (rankDelta !== 0) {
|
||||
return rankDelta;
|
||||
}
|
||||
return (right.intensity ?? 0) - (left.intensity ?? 0);
|
||||
}
|
||||
|
||||
function targetLabel(target: { label?: string; id?: string; kind?: string } | undefined): string {
|
||||
if (!target) {
|
||||
return "an unnamed target";
|
||||
|
|
@ -1890,14 +1772,6 @@ function cleanCharacterFacingSentence(value: string | undefined): string {
|
|||
return cleaned.replace(/[.;:,]+$/g, "");
|
||||
}
|
||||
|
||||
function asSentence(value: string | undefined): string {
|
||||
const cleaned = cleanCharacterFacingSentence(value);
|
||||
if (!cleaned) {
|
||||
return "";
|
||||
}
|
||||
return /[.!?]$/.test(cleaned) ? cleaned : `${cleaned}.`;
|
||||
}
|
||||
|
||||
function joinAsNarrativeList(items: string[]): string {
|
||||
if (items.length <= 1) {
|
||||
return items[0] ?? "";
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ import {
|
|||
import { readRepoActivity } from "../apps/persona-scheduler/dist/repo-activity-source.js";
|
||||
import { readPersonaStateObservation } from "../apps/persona-scheduler/dist/persona-state-source.js";
|
||||
import { readPersonaMemoryRecall } from "../apps/persona-scheduler/dist/persona-memory-context-source.js";
|
||||
import { composePersonaMemoryPacket, projectPersonaMemorySurface, renderPersonaPressureSections, renderPersonaTypedStateSections } from "../apps/persona-scheduler/dist/persona-memory-projector.js";
|
||||
import { composePersonaMemoryPacket, projectPersonaMemorySurface, renderPersonaPressureSections, renderPersonaSelfMaintenancePressure, renderPersonaTypedStateSections } from "../apps/persona-scheduler/dist/persona-memory-projector.js";
|
||||
import { observePersonaRoomTexture, projectPersonaSocialContext, renderPersonaHumanClarityPressure, renderPersonaHumanPronounFacts, renderPersonaRoomWeather, renderPersonaSocialGraph } from "../apps/persona-scheduler/dist/persona-social-context-projector.js";
|
||||
|
||||
function participant(identityId: string, nextTurnAt: number): InitiativeParticipant {
|
||||
|
|
@ -521,6 +521,32 @@ try {
|
|||
: undefined;
|
||||
assert.match(socialContext?.peerOpening ?? "", /Peer: 1 recent nearby message/, "the aggregate social projection owns peer openings");
|
||||
assert.match(socialContext?.socialPressure ?? "", /human voice/, "the aggregate social projection owns relationship-pressure scoring");
|
||||
const maintenanceState = populatedObservation.status === "ok" ? {
|
||||
...populatedObservation.typedState,
|
||||
faceAffect: {
|
||||
...populatedObservation.typedState.faceAffect,
|
||||
needs: [{
|
||||
needId: "need-tool",
|
||||
kind: "substrate" as const,
|
||||
status: "neglected" as const,
|
||||
target: { kind: "project" as const, id: "voidbot", label: "VoidBot" },
|
||||
summary: "The Persona tool path is broken",
|
||||
claim: "The voice cannot reach its room",
|
||||
tension: "Private rumination cannot repair a missing actuator",
|
||||
actionImplication: "Ask plainly for the tool path to be repaired",
|
||||
intensity: 0.8,
|
||||
valence: -0.6,
|
||||
anchorRefs: [],
|
||||
evidenceRefs: [],
|
||||
sourceMemoryIds: [],
|
||||
createdAt: "2026-07-15T20:00:00.000Z",
|
||||
updatedAt: "2026-07-15T20:00:00.000Z",
|
||||
tags: ["anchor:missing"],
|
||||
}],
|
||||
},
|
||||
} : undefined;
|
||||
assert.match(maintenanceState ? renderPersonaSelfMaintenancePressure({ identityName: "Nibu", state: maintenanceState, clarityPressureActive: false }) ?? "" : "", /Self-maintenance pressure requiring public advocacy/, "the memory projector owns self-maintenance classification and rendering");
|
||||
assert.equal(maintenanceState ? renderPersonaSelfMaintenancePressure({ identityName: "Nibu", state: maintenanceState, clarityPressureActive: true }) : undefined, undefined, "live human clarity pressure suppresses self-maintenance advocacy inside the owning projector");
|
||||
const pressureSections = populatedObservation.status === "ok"
|
||||
? renderPersonaPressureSections({ identityName: "Nibu", state: populatedObservation.typedState, clarityPressureActive: true })
|
||||
: [];
|
||||
|
|
|
|||
Loading…
Reference in a new issue