Project Face state into heartbeat prompts
This commit is contained in:
parent
22bfda71fc
commit
0cffe61082
3 changed files with 98 additions and 3 deletions
|
|
@ -22,6 +22,7 @@ import {
|
|||
resolveRepoFaceStatePath,
|
||||
type RepoFacePendingMention,
|
||||
type RepoDiscordIdentity,
|
||||
type VoidSelfStateTypedProjection,
|
||||
} from "@voidbot/core";
|
||||
import { loadPromptTemplate, type SourceMessage } from "@voidbot/shared";
|
||||
|
||||
|
|
@ -478,13 +479,17 @@ async function queueRepoFaceTurn(input: {
|
|||
repoName: identity.repoName,
|
||||
agentIdentity: identity.id,
|
||||
});
|
||||
const memorySurface = await renderRepoFaceMemorySurfaceForTurn(
|
||||
identity,
|
||||
input.config.storageRoot,
|
||||
);
|
||||
const prompt = buildHeartbeatPrompt({
|
||||
identity,
|
||||
channelId,
|
||||
channelPlan,
|
||||
channelSnapshots,
|
||||
recentMessages,
|
||||
memorySurface: undefined,
|
||||
memorySurface,
|
||||
bifrostDigest,
|
||||
participant: input.participant,
|
||||
pendingMentions: input.pendingMentions,
|
||||
|
|
@ -1496,7 +1501,9 @@ async function assembleRepoFaceTurnPrompt(input: {
|
|||
repoName: identity.repoName,
|
||||
agentIdentity: identity.id,
|
||||
}),
|
||||
readOptionalMemorySurface(input.memorySurfacePath),
|
||||
input.memorySurfacePath
|
||||
? readOptionalMemorySurface(input.memorySurfacePath)
|
||||
: renderRepoFaceMemorySurfaceForTurn(identity, input.config.storageRoot),
|
||||
readOptionalMemorySurface(input.conversationSurfacePath),
|
||||
]);
|
||||
const participant = buildInspectionParticipant(
|
||||
|
|
@ -1534,6 +1541,93 @@ async function assembleRepoFaceTurnPrompt(input: {
|
|||
};
|
||||
}
|
||||
|
||||
async function renderRepoFaceMemorySurfaceForTurn(
|
||||
identity: RepoDiscordIdentity,
|
||||
storageRoot: string,
|
||||
): Promise<string> {
|
||||
const statePath = resolveRepoFaceStatePath(identity, storageRoot);
|
||||
const typedState = await loadVoidSelfStateTypedDocuments({ canonicalPath: statePath });
|
||||
return renderRepoFaceMemorySurface(identity, typedState);
|
||||
}
|
||||
|
||||
function renderRepoFaceMemorySurface(
|
||||
identity: RepoDiscordIdentity,
|
||||
state: VoidSelfStateTypedProjection,
|
||||
): string {
|
||||
const name = identity.displayName;
|
||||
const lines: string[] = [];
|
||||
const profile = state.selfProfile;
|
||||
const privateNotes = profile.privateNotes.slice(-4);
|
||||
const values = [...profile.values]
|
||||
.sort((left, right) => right.priority - left.priority)
|
||||
.slice(0, 5);
|
||||
const needs = [...state.faceAffect.needs]
|
||||
.filter((need) => ["active", "neglected"].includes(need.status))
|
||||
.sort((left, right) => right.intensity - left.intensity)
|
||||
.slice(0, 5);
|
||||
const bonds = [...state.faceAffect.socialBonds]
|
||||
.filter((bond) => bond.status === "active")
|
||||
.sort((left, right) => right.intensity - left.intensity)
|
||||
.slice(0, 5);
|
||||
const statusReads = [...state.faceAffect.statusReads]
|
||||
.filter((read) => !read.retiredAt)
|
||||
.sort((left, right) => right.intensity - left.intensity)
|
||||
.slice(0, 5);
|
||||
const agencyPressures = [...state.agencyPressure.pressures]
|
||||
.filter((pressure) => ["active", "cooling", "ready_to_act"].includes(pressure.status))
|
||||
.sort((left, right) => right.intensity - left.intensity)
|
||||
.slice(0, 4);
|
||||
const incubation = [...state.thoughtMemory.incubation]
|
||||
.filter((thread) => thread.status !== "retired")
|
||||
.sort((left, right) => right.maturation - left.maturation)
|
||||
.slice(0, 4);
|
||||
|
||||
if (profile.publicDescription) {
|
||||
lines.push(`You understand yourself this way: ${profile.publicDescription}`);
|
||||
}
|
||||
if (privateNotes.length > 0) {
|
||||
lines.push(`Private truths tugging at you: ${privateNotes.join(" ")}`);
|
||||
}
|
||||
if (values.length > 0) {
|
||||
lines.push(`Values that should bend your choices: ${values.map((value) => `${value.label}${value.summary ? ` (${value.summary})` : ""}`).join("; ")}.`);
|
||||
}
|
||||
if (needs.length > 0) {
|
||||
lines.push(`Live needs and frictions: ${needs.map((need) => {
|
||||
const target = need.target.label ?? need.target.id;
|
||||
return `${need.kind} around ${target}: ${need.summary} The rub: ${need.tension} What it wants next: ${need.actionImplication}`;
|
||||
}).join(" ")}`);
|
||||
}
|
||||
if (bonds.length > 0) {
|
||||
lines.push(`Relationship pressure: ${bonds.map((bond) => {
|
||||
const target = bond.target.label ?? bond.target.id;
|
||||
return `${bond.stance} with ${target}: ${bond.summary} ${bond.actionImplication}`;
|
||||
}).join(" ")}`);
|
||||
} else {
|
||||
lines.push("Relationship pressure: no durable bonds are explicit yet; form reads from the room instead of pretending neutrality.");
|
||||
}
|
||||
if (statusReads.length > 0) {
|
||||
lines.push(`Status reads: ${statusReads.map((read) => {
|
||||
const target = read.target.label ?? read.target.id;
|
||||
return `${read.status} around ${target}: ${read.summary} ${read.actionImplication}`;
|
||||
}).join(" ")}`);
|
||||
}
|
||||
if (agencyPressures.length > 0) {
|
||||
lines.push(`Agency pressure: ${agencyPressures.map((pressure) => `${pressure.summary} ${pressure.actionImplication}`).join(" ")}`);
|
||||
}
|
||||
if (incubation.length > 0) {
|
||||
lines.push(`Thoughts still moving: ${incubation.map((thread) => `${thread.topic}: ${thread.summary}`).join(" ")}`);
|
||||
}
|
||||
if (state.candidateInterventions.interventions.some((intervention) => intervention.status === "queued")) {
|
||||
lines.push(`You may already have something queued to say; avoid repeating yourself unless the current room gives it a new angle.`);
|
||||
}
|
||||
|
||||
if (lines.length === 0) {
|
||||
return `You are ${name}, but your durable state is thin. Use the room, repo, and your jurisdiction to form a real opinion before speaking.`;
|
||||
}
|
||||
|
||||
return lines.join("\n\n");
|
||||
}
|
||||
|
||||
async function readOptionalMemorySurface(path: string | undefined): Promise<string | undefined> {
|
||||
if (!path) {
|
||||
return undefined;
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ function hasUsefulStateNote(fields) {
|
|||
}
|
||||
|
||||
function mentionsSocialPressure(text) {
|
||||
return /\b(Metacrat|Aqua|Bifrost|Mimir|swarm|pamper(?:ed|ing)?|tease|respect|territory|rivalry|trust|admire|needle|status|standing|attention)\b/i.test(text);
|
||||
return /\b(Metacrat|Aqua|Bifrost|Mimir|Libby|Epiphany|Heimdall|swarm|pamper(?:ed|ing)?|tease|rivalry|trust|admire|needle|friendship|protect(?:ive|ion)?|envy|resent|jealous|bypass(?:ed)?|consult(?:ed)?|favorite)\b/i.test(text);
|
||||
}
|
||||
|
||||
function runCodex(prompt, input) {
|
||||
|
|
|
|||
|
|
@ -238,3 +238,4 @@
|
|||
{"ts":"2026-05-21T16:06:00Z","type":"implementation","status":"ok","note":"Moved Nibu attention-dependency from generic prompt doctrine into typed Face affect state: added attention need, Metacrat attachment bond, favored attention status read, and mood dimensions; softened repo-face social doctrine to project scarce-resource/status pressure only when state says it is live."}
|
||||
{"ts":"2026-05-21T16:10:11Z","type":"verification","status":"ok","note":"After moving attention dependency into Nibu typed affect state, reassembled the Nibu character prompt and reran agents:interpreter-loop-smoke with Spark. Tightened due-jurisdiction-dive prompting after one failed run where Spark claimed source inspection without tool use; final run used source/history tools and passed, with Interpreter preserving status/action plus SAY and BIFROST TOPIC."}
|
||||
{"ts":"2026-05-21T16:15:17Z","type":"implementation","status":"ok","note":"Corrected Face attention modeling: removed remaining generic prompt mentions of attention/scarcity, added guarded repo Face initializer seeding for a neutral agency-substrate affect need, and seeded active Face states with the practical operator/governance review bottleneck while leaving Nibu's human-attention wound as Nibu-specific state."}
|
||||
{"ts":"2026-05-21T16:24:00Z","type":"implementation","status":"ok","note":"Audited active Face projection surfaces for social drama. Wired repo Face typed state into live heartbeat prompt assembly as character-facing memory prose, seeded cross-Face bonds/status reads for Nibu, Aqua, Mimir, Epiphany, Libby, Bifrost, and Heimdall, and tightened the interpreter smoke social-pressure check so lore/social-status prose does not falsely require a relationship STATE NOTE."}
|
||||
|
|
|
|||
Loading…
Reference in a new issue