Surface live social hooks for Face turns
This commit is contained in:
parent
831858ed20
commit
c33b300509
6 changed files with 170 additions and 5 deletions
|
|
@ -129,7 +129,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou
|
|||
- Repo Face state projection is no longer allowed to be deterministic schema slurry. `scripts/run-repo-face-heartbeats.ts` builds a compact state packet, then calls the model prompt at `prompts/repo-face-state-projector.prompt.md` to turn it into character-facing prose before the Face sees it. `REPO_FACE_STATE_PROJECTOR_ENABLED=false` exists only as an emergency isolation switch; both the model output and fallback packet reject schema/prompt-construction leaks such as `grants:`, `jurisdictions:`, `Face of`, repo paths, and "do not prompt" phrasing.
|
||||
- First role-addressed chat also initializes the target repo's local `.voidbot` home. `ensureRepoFaceInitialized` resolves `repoPath` from the registry, `SOURCE_REPO_ROOT`, or `E:/Projects/<repoName>`, creates `voice/`, `state/`, `birth/`, and `logs/`, writes `voice/identity.json`, then starts `scripts/run-repo-face-birth.mjs` detached. That worker runs EpiphanyAgent's `epiphany-repo-personality scout` and `epiphany-repo-birth-runner` so newborn personality/memory/trajectory packets come from repo contents and git history. Status and logs live in the target repo under `.voidbot/birth/voidbot-face-birth.json` and `.voidbot/logs/voidbot-face-birth.log`.
|
||||
- Agent heartbeats now use one CTB/Final-Fantasy-X-style initiative queue. `scripts/run-repo-face-heartbeats.ts` still has its old filename, but it now reconciles built-in system agents plus registered repo Faces, stores the roster at `REPO_FACE_HEARTBEAT_STATE_PATH`, advances virtual initiative time independently of wall-clock due dates, and dispatches selected turns. Repo Faces queue normal `repo-face-rumination` jobs; built-in `void` starts the existing moderation rumination runner as its turn. Active jobs/locks freeze initiative: recovery starts only after the owner-Codex job or Void moderation lock clears, and completion cannot grant another turn in the same pulse. The active-job scan now marks stale approved/running Face jobs failed through the job queue and records `stale_active_turn_recovered`, so a dead worker claim cannot freeze one Face forever. `GameCult Local Orchestrator` is the live hidden local scheduled task that ticks the scheduler; `npm run state:install-repo-face-heartbeat-task` remains an isolation/backcompat installer, not the normal pulse owner. Heat knobs are `REPO_FACE_HEARTBEAT_GLOBAL_HEAT` and `REPO_FACE_HEARTBEAT_HEAT_OVERRIDES`; overrides can target `all`, `kind:<participantKind>`, `turn:<turnKind>`, `identity:<id>`, `repo:<repo>`, `display:<name>`, `channel:<id>`, or bare identity/repo/display keys. Repo Face prompts now include a jurisdiction-dive cadence so unattended heartbeats periodically inspect repo/history terrain with source tools and convert real findings into state, proposals, essay seeds, agency pressure, or speech.
|
||||
- Repo Face heartbeat prompt assembly now has one model projection boundary before the child Face sees context: `repo-face-state-projector.prompt.md` turns typed Face state into character-facing memory/affect/social prose. Recent room messages, nearby channel snapshots, and pending mentions are now deterministic raw transcript sections, oldest-to-newest, with explicit recency/correction rules. The deleted conversation projector was turning evidence into mushy consensus; do not revive it. The Face model must read human corrections directly. Model ownership is split: Face turns default to full `gpt-5.4`; state projection and parent Interpreter passes use the cheaper Spark/Mini support chain.
|
||||
- Repo Face heartbeat prompt assembly now has one model projection boundary before the child Face sees context: `repo-face-state-projector.prompt.md` turns typed Face state into character-facing memory/affect/social prose. Recent room messages, nearby channel snapshots, and pending mentions are now deterministic raw transcript sections, oldest-to-newest, with explicit recency/correction rules. The deleted conversation projector was turning evidence into mushy consensus; do not revive it. The Face model must read human corrections directly. A separate deterministic social-affordance surface connects active typed person-bonds/status reads to recent raw messages by that person, so relationship state becomes a live hook instead of private flavor text. Model ownership is split: Face turns default to full `gpt-5.4`; state projection and parent Interpreter passes use the cheaper Spark/Mini support chain.
|
||||
- Repo Face prompts now include read-only recent home-repo activity before Discord transcript. `scripts/run-repo-face-heartbeats.ts` calls `scripts/export-recent-repo-activity.mjs --repos <repo> --state-path <face-state> --read-only --hours 96 --max-commits 5`, so Faces see current body/jurisdiction motion before reacting to room pressure. This is the fix for Mimir missing live camera-runtime work, not a downstream compensator telling it to apologize harder.
|
||||
- Worker delivery must treat Interpreter channel names as Face-local labels, not raw Discord ids. `SAY channel: aquarium` or `channel: narrative` resolves through the registered identity's `channelPermissions` before allow-list checks and webhook delivery. Earlier live Aqua/Nibu turns parsed `say=1` but did not post because the worker literally checked whether the identity was allowed in channel id `aquarium`; the post path now resolves labels and has a live Epiphany webhook receipt after restart.
|
||||
- Repo Face rest projection now persists normalized sleep cycles back into each Face `.cc` state during heartbeat loading. Missed nap windows are treated as already missed/completed and rolled forward instead of starting a fresh nap at inspection time. This fixes the Aqua/Mimir/Epiphany failure mode where stale `nextNapStartsAt` values stayed raw, fresh projections repeatedly marked them as napping, and ready selection skipped them forever while their initiative was overdue.
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ This note is the source-grounded description of how the live VoidBot stack is sh
|
|||
- Role mentions, display-name-prefix mentions, and human replies to a Face's webhook/persona message append pending Face obligations to `pendingMentions` in `REPO_FACE_HEARTBEAT_STATE_PATH`; ordinary bot mentions and DMs append pending obligations for the built-in `void` participant. They no longer create direct provider jobs from `MessageCreate`. The CTB scheduler temporarily prioritizes agents with pending mentions, attaches those prompts plus reply targets to the next `repo-face-rumination` or Void moderation pass, and consumes the pending items only after a turn is actually queued. Rumination jobs remain private unless they explicitly emit a parent-owned Face action block or a typed candidate intervention; the worker must never auto-post their summaries as the base bot. Heartbeat prompts now include the channel permission plan, recent context from non-primary permitted channels, social embodiment, jurisdiction respect, comedy/improv doctrine, and anti-template sampling guidance so a Face can jump into domain chatter, form durable social reads, consult the right steward, and answer people without copying nearby note formulas.
|
||||
- Repo Face proposal/article PRs, PR comments, and registered-identity Discord posts now cross through Bifrost bridge commands. The child may emit `VOIDBOT_REPO_IDENTITY_PROPOSAL_PR` for consensus-needed repo/lore/design proposals, `VOIDBOT_REPO_IDENTITY_ARTICLE` for bylined essays, or `VOIDBOT_REPO_IDENTITY_PR_COMMENT` for signed arguments on existing proposal PRs. The worker validates the registered identity and records Face-local receipts, but `BIFROST_ROOT/tools/bifrost-bridge.mjs` owns draft PR creation, PR comments, and Discord persona posting. This is not merely helper placement: Bifrost's original governance/labor platform role makes it the owner of governed public crossings for GameCult work, including GitHub, Discord-native work/member/swarm interactions, dispatch receipts, CultNet/CultCache intake, and future collaboration interfaces. Heimdall is the OAuth/account/capability gate; Bifrost is the bridge/request/routing/receipt authority. VoidBot still owns room cognition, moderation judgment, archive/source retrieval, and Face personality; Bifrost owns Discord when Discord acts as the Labor Platform interface or swarm transport. If Nibu or another Face frames something as the next cut/pass and enough source or room material exists, the expected action is to draft the artifact, submit the PR, and announce it rather than continuing to nag the room.
|
||||
- Repo Face turns now receive a Bifrost governance digest from `BIFROST_ROOT/tools/governance-threads.mjs digest --repo <repo> --agent <identity>` alongside recent Discord context. Bifrost typed CultCache topics/comments are the canonical home for feature requests, Face opinions, missing questions, approvals, and dispatch receipts; Discord is a mirror/evidence source. When `BIFROST_DISCORD_CHANNEL_ID` is configured for `#bifrost`, repo Face Discord context ignores mirrored bot chatter there because the authoritative activity arrives through the Bifrost digest. Human `#bifrost` messages should only become governance comments after Bifrost/Heimdall can link the Discord id to a registered actor. Repo-local asks that would send a human or Codex to add/fix/name/scaffold/document/test/investigate/implement belong on Bifrost with enough room/repo context for a claimed Codex turn; a Face may still speak characterfully in Aquarium, but not as the only dispatch surface. Legacy `UPDATE REQUEST` packets are parsed only as compatibility input and reconciled into Bifrost topics instead of immediate transport enqueue. Local `#bifrost` is channel `1506760343836430376`; work proposals, governance mirrors, and Bifrost dispatch receipts should go there, while Aquarium remains the casual/social swarm room.
|
||||
- Repo character turns now have a state-projector plus turn-Interpreter shape. The state-in boundary is `prompts/repo-face-state-projector.prompt.md`: typed state and identity material are translated into lived character prose before the child sees it. The state projector must surface values, drives, wounds, fascinations, aesthetic taste, dignity, mood, needs, social reads, bonds, and agency pressure when the state contains them. Deterministic code also attaches recent read-only home-repo activity before chat, using the Face's own state path as the repo-activity cursor source, so current implementation motion is visible as body context. Current-room and nearby-channel logs no longer cross a model conversation projector; deterministic code attaches raw oldest-to-newest transcript evidence with explicit recency/correction rules so the Face can notice human reframes directly. Deterministic code still owns full prompt assembly and inspection through `scripts/run-repo-face-heartbeats.ts --assemble-prompt <identity>`; that path composes stable doctrine, allowed rooms, research/archive capability guidance, Bifrost digest, projected memory/affect prose, home-repo activity, and raw conversation evidence into the exact child prompt. It must not invent personality, mood, affect, or social memory from typed state. Situational obligations such as direct-call priority or first-public-introduction duty are emitted only when the scheduler packet proves they apply, not as standing prompt law. The child character writes a natural in-character turn on full `gpt-5.4` and may describe desired speech, repo/article proposals, social reads, needs, mood shifts, or memory-worthy thoughts without emitting action DSL blocks. The cheaper Spark/Mini parent Interpreter decides route/retry/drop and translates warranted effects into structured `STATE NOTE` and `SAY` blocks for the worker while governance dispatch remains paused. `STATE NOTE` blocks become typed short-term memory, affect needs, social bonds, status reads, mood dimensions, or agency pressure. The worker still accepts legacy compact JSON sentinels and legacy `UPDATE REQUEST` blocks for compatibility, but the live prompt path keeps transport and state syntax out of the character's roleplay surface.
|
||||
- Repo character turns now have a state-projector plus turn-Interpreter shape. The state-in boundary is `prompts/repo-face-state-projector.prompt.md`: typed state and identity material are translated into lived character prose before the child sees it. The state projector must surface values, drives, wounds, fascinations, aesthetic taste, dignity, mood, needs, social reads, bonds, and agency pressure when the state contains them. Deterministic code also attaches recent read-only home-repo activity before chat, using the Face's own state path as the repo-activity cursor source, so current implementation motion is visible as body context. Current-room and nearby-channel logs no longer cross a model conversation projector; deterministic code attaches raw oldest-to-newest transcript evidence with explicit recency/correction rules so the Face can notice human reframes directly. Deterministic code also attaches a social-affordance surface that matches active typed person-bonds/status reads to recent raw messages from that person, so relationship state arrives as a live hook rather than private decorative lore. Deterministic code still owns full prompt assembly and inspection through `scripts/run-repo-face-heartbeats.ts --assemble-prompt <identity>`; that path composes stable doctrine, allowed rooms, research/archive capability guidance, Bifrost digest, projected memory/affect prose, home-repo activity, live social affordances, and raw conversation evidence into the exact child prompt. It must not invent personality, mood, affect, or social memory from typed state. Situational obligations such as direct-call priority or first-public-introduction duty are emitted only when the scheduler packet proves they apply, not as standing prompt law. The child character writes a natural in-character turn on full `gpt-5.4` and may describe desired speech, repo/article proposals, social reads, needs, mood shifts, or memory-worthy thoughts without emitting action DSL blocks. The cheaper Spark/Mini parent Interpreter decides route/retry/drop and translates warranted effects into structured `STATE NOTE` and `SAY` blocks for the worker while governance dispatch remains paused. `STATE NOTE` blocks become typed short-term memory, affect needs, social bonds, status reads, mood dimensions, or agency pressure. The worker still accepts legacy compact JSON sentinels and legacy `UPDATE REQUEST` blocks for compatibility, but the live prompt path keeps transport and state syntax out of the character's roleplay surface.
|
||||
- `scripts/run-repo-face-heartbeats.ts` now acts as the broader agent CTB/Final-Fantasy-X-style initiative scheduler. The wall-clock task is only a pulse; virtual initiative time chooses who gets the next turn. The scheduler stores per-agent speed, reaction bias, load, groups, heat, effective speed, next-turn time, active turn markers, and recent queue history at `REPO_FACE_HEARTBEAT_STATE_PATH`. Repo Faces queue `repo-face-rumination` owner-Codex jobs; built-in Void currently participates as the `void` system agent and starts the existing moderation rumination runner as its turn. Initiative freezes while an owner-Codex job or Void moderation lock is active; recovery is scheduled only after that active turn completes, and completion cannot grant another turn in the same scheduler pulse. The active-job scan recovers stale Face jobs by marking ancient approved/running rows failed through the job queue before building the active-turn map. Above-baseline heat bypasses repo Face nap gating, while active-turn freeze still prevents overlapping thoughts.
|
||||
- The live wall-clock pulse for this scheduler is `scripts/run-gamecult-orchestrator.ps1`, installed as `GameCult Local Orchestrator`. `scripts/install-repo-face-heartbeat-task.ps1` remains available for isolated testing, but its per-organ scheduled task should stay disabled in normal operation. Turn frequency is controlled by `REPO_FACE_HEARTBEAT_BASE_RECOVERY_MINUTES`, `REPO_FACE_HEARTBEAT_GLOBAL_HEAT`, `REPO_FACE_HEARTBEAT_SPEED_OVERRIDES`, and `REPO_FACE_HEARTBEAT_HEAT_OVERRIDES`. Heat keys can target `all`, `kind:<participantKind>`, `turn:<turnKind>`, `identity:<id>`, `repo:<repo>`, `display:<name>`, `channel:<id>`, or the bare identity/repo/display key.
|
||||
- The local ignored registry currently includes Nibu for `AetheriaLore`, with Face state in `E:/Projects/AetheriaLore/.voidbot/state/nibu.cc`, Discord role id `1505918221453103247`, and an Aquarium channel allow-list. Nibu's Face state now treats AetheriaLore-first map-building, abrasive character voice, consensus-backed change agency, her own `Aetheria/Lore/Nibu.md` character-note stewardship, Nibu-authored lore essays, and tangential setting fascination as separate active doctrines. That tangential fascination points at embodied ship minds, junkyard abandonment, murderous autonomy, salvage, and save-scumming survival. The repo Face heartbeat organ is now pulsed by `GameCult Local Orchestrator`; after restarting the stack onto the new worker code, forced heartbeat job `76020f1e-0c55-424b-9e35-89ba7f77fcc3` completed without base-bot auto-posting.
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ What you remember, feel, and want right now:
|
|||
Recent home repo activity:
|
||||
{{repoActivitySurface}}
|
||||
|
||||
Live social affordances:
|
||||
{{socialOpportunitySurface}}
|
||||
|
||||
Recent conversation transcript:
|
||||
{{conversationMemorySurface}}
|
||||
|
||||
|
|
|
|||
|
|
@ -486,6 +486,13 @@ async function queueRepoFaceTurn(input: {
|
|||
input.config,
|
||||
);
|
||||
const repoActivitySurface = renderRepoFaceRepoActivitySurface(identity, input.config);
|
||||
const socialOpportunitySurface = await renderRepoFaceSocialOpportunitySurface({
|
||||
identity,
|
||||
config: input.config,
|
||||
recentMessages,
|
||||
channelSnapshots,
|
||||
channelPlan,
|
||||
});
|
||||
const conversationMemorySurface = renderRepoFaceConversationTranscript({
|
||||
identity,
|
||||
recentMessages,
|
||||
|
|
@ -501,6 +508,7 @@ async function queueRepoFaceTurn(input: {
|
|||
recentMessages,
|
||||
memorySurface,
|
||||
repoActivitySurface,
|
||||
socialOpportunitySurface,
|
||||
conversationMemorySurface,
|
||||
bifrostDigest,
|
||||
participant: input.participant,
|
||||
|
|
@ -1426,6 +1434,7 @@ function buildHeartbeatPrompt(input: {
|
|||
recentMessages: SourceMessage[];
|
||||
memorySurface?: string;
|
||||
repoActivitySurface?: string;
|
||||
socialOpportunitySurface?: string;
|
||||
conversationMemorySurface?: string;
|
||||
bifrostDigest?: BifrostGovernanceDigest;
|
||||
participant: FaceHeartbeatParticipant;
|
||||
|
|
@ -1441,6 +1450,7 @@ function buildHeartbeatPrompt(input: {
|
|||
channelId: input.channelId,
|
||||
memorySurface: input.memorySurface ?? `- ${input.identity.displayName} has no strong personal memory surface yet. Let the attached conversation and repo evidence wake something specific.`,
|
||||
repoActivitySurface: input.repoActivitySurface ?? "- No recent home repo activity was attached for this turn.",
|
||||
socialOpportunitySurface: input.socialOpportunitySurface ?? "- No live social affordances were attached for this turn.",
|
||||
conversationMemorySurface: input.conversationMemorySurface ?? "- No recent conversation transcript was attached for this turn.",
|
||||
turnSituationDirective: renderTurnSituationDirective({
|
||||
identity: input.identity,
|
||||
|
|
@ -1522,6 +1532,13 @@ async function assembleRepoFaceTurnPrompt(input: {
|
|||
: renderRepoFaceMemorySurfaceForTurn(identity, input.config),
|
||||
]);
|
||||
const repoActivitySurface = renderRepoFaceRepoActivitySurface(identity, input.config);
|
||||
const socialOpportunitySurface = await renderRepoFaceSocialOpportunitySurface({
|
||||
identity,
|
||||
config: input.config,
|
||||
recentMessages,
|
||||
channelSnapshots,
|
||||
channelPlan,
|
||||
});
|
||||
const conversationMemorySurface = input.conversationSurfacePath
|
||||
? await readOptionalMemorySurface(input.conversationSurfacePath)
|
||||
: renderRepoFaceConversationTranscript({
|
||||
|
|
@ -1543,6 +1560,7 @@ async function assembleRepoFaceTurnPrompt(input: {
|
|||
recentMessages,
|
||||
memorySurface,
|
||||
repoActivitySurface,
|
||||
socialOpportunitySurface,
|
||||
conversationMemorySurface,
|
||||
bifrostDigest,
|
||||
participant,
|
||||
|
|
@ -1686,6 +1704,147 @@ function renderRepoFaceStatePacket(
|
|||
return rejectLeakyMemorySurface(lines.join("\n\n"));
|
||||
}
|
||||
|
||||
async function renderRepoFaceSocialOpportunitySurface(input: {
|
||||
identity: RepoDiscordIdentity;
|
||||
config: ReturnType<typeof loadConfig>;
|
||||
recentMessages: SourceMessage[];
|
||||
channelSnapshots: ChannelSnapshot[];
|
||||
channelPlan: RepoFaceChannelPlan;
|
||||
}): Promise<string> {
|
||||
const statePath = resolveRepoFaceStatePath(input.identity, input.config.storageRoot);
|
||||
const typedState = await loadVoidSelfStateTypedDocuments({ canonicalPath: statePath });
|
||||
const relations = collectRepoFaceSocialRelations(typedState);
|
||||
if (relations.length === 0) {
|
||||
return [
|
||||
"No durable social reads are attached yet.",
|
||||
"Use the raw conversation to notice who earns trust, irritation, protection, rivalry, respect, or suspicion; do not fake intimacy just to fill the gap.",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
const messages = collectSocialOpportunityMessages(input);
|
||||
const hookedRelations = relations
|
||||
.map((relation) => ({
|
||||
relation,
|
||||
hooks: findMessagesForSocialTarget(messages, relation.targetLabel, input.identity.displayName),
|
||||
}))
|
||||
.filter((entry) => entry.hooks.length > 0)
|
||||
.slice(0, 5);
|
||||
|
||||
if (hookedRelations.length === 0) {
|
||||
return [
|
||||
"No durable bond target appears to have spoken in the attached recent messages.",
|
||||
"Treat the transcript itself as fresh social evidence: notice who is interrupting, protecting, escalating, ignoring, joking, or asking for recognition before you decide whether to speak.",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
const lines = [
|
||||
"These are live relationship hooks, not orders. If one fits the room, maintain or complicate the bond in character instead of treating it as private trivia.",
|
||||
];
|
||||
|
||||
for (const { relation, hooks } of hookedRelations) {
|
||||
lines.push(`- ${relation.targetLabel}: ${relation.pressure} Recent hook: ${hooks.map(formatSocialOpportunityMessage).join(" | ")}`);
|
||||
}
|
||||
|
||||
return rejectLeakyMemorySurface(lines.join("\n"));
|
||||
}
|
||||
|
||||
function collectRepoFaceSocialRelations(
|
||||
state: VoidSelfStateTypedProjection,
|
||||
): Array<{ targetLabel: string; pressure: string; intensity: number }> {
|
||||
const byTarget = new Map<string, { targetLabel: string; parts: string[]; intensity: number }>();
|
||||
|
||||
for (const bond of state.faceAffect.socialBonds ?? []) {
|
||||
if (bond.status !== "active") {
|
||||
continue;
|
||||
}
|
||||
if (bond.target.kind !== "person") {
|
||||
continue;
|
||||
}
|
||||
const targetLabel = cleanSocialTargetLabel(bond.target.label ?? bond.target.id);
|
||||
if (!targetLabel) {
|
||||
continue;
|
||||
}
|
||||
const entry = byTarget.get(targetLabel) ?? { targetLabel, parts: [], intensity: 0 };
|
||||
entry.parts.push(`${bond.stance}: ${asSentence(bond.summary)} ${asSentence(bond.actionImplication)}`);
|
||||
entry.intensity = Math.max(entry.intensity, bond.intensity);
|
||||
byTarget.set(targetLabel, entry);
|
||||
}
|
||||
|
||||
for (const read of state.faceAffect.statusReads ?? []) {
|
||||
if (read.retiredAt) {
|
||||
continue;
|
||||
}
|
||||
if (read.target.kind !== "person") {
|
||||
continue;
|
||||
}
|
||||
const targetLabel = cleanSocialTargetLabel(read.target.label ?? read.target.id);
|
||||
if (!targetLabel) {
|
||||
continue;
|
||||
}
|
||||
const entry = byTarget.get(targetLabel) ?? { targetLabel, parts: [], intensity: 0 };
|
||||
entry.parts.push(`${read.status}: ${asSentence(read.summary)} ${asSentence(read.actionImplication)}`);
|
||||
entry.intensity = Math.max(entry.intensity, read.intensity);
|
||||
byTarget.set(targetLabel, entry);
|
||||
}
|
||||
|
||||
return [...byTarget.values()]
|
||||
.map((entry) => ({
|
||||
targetLabel: entry.targetLabel,
|
||||
pressure: entry.parts.map(cleanCharacterFacingSentence).filter(Boolean).join(" "),
|
||||
intensity: entry.intensity,
|
||||
}))
|
||||
.filter((entry) => entry.pressure.length > 0)
|
||||
.sort((left, right) => right.intensity - left.intensity);
|
||||
}
|
||||
|
||||
function collectSocialOpportunityMessages(input: {
|
||||
channelPlan: RepoFaceChannelPlan;
|
||||
recentMessages: SourceMessage[];
|
||||
channelSnapshots: ChannelSnapshot[];
|
||||
}): Array<{ label: string; message: SourceMessage }> {
|
||||
const currentLabel = input.channelPlan.options.find((option) =>
|
||||
option.channelId === input.channelPlan.primaryChannelId
|
||||
)?.label ?? "current room";
|
||||
const messages = input.recentMessages.map((message) => ({ label: currentLabel, message }));
|
||||
|
||||
for (const snapshot of input.channelSnapshots) {
|
||||
const label = input.channelPlan.options.find((option) => option.channelId === snapshot.channelId)?.label ??
|
||||
"nearby room";
|
||||
messages.push(...snapshot.messages.map((message) => ({ label, message })));
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
function findMessagesForSocialTarget(
|
||||
messages: Array<{ label: string; message: SourceMessage }>,
|
||||
targetLabel: string,
|
||||
selfName: string,
|
||||
): Array<{ label: string; message: SourceMessage }> {
|
||||
const targetToken = normalizeSocialLabel(targetLabel);
|
||||
const selfToken = normalizeSocialLabel(selfName);
|
||||
if (!targetToken || targetToken === selfToken) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return messages
|
||||
.filter(({ message }) => normalizeSocialLabel(message.authorName) === targetToken)
|
||||
.slice(-2);
|
||||
}
|
||||
|
||||
function formatSocialOpportunityMessage(entry: { label: string; message: SourceMessage }): string {
|
||||
const speaker = entry.message.isBot ? `${entry.message.authorName} (agent)` : entry.message.authorName;
|
||||
return `${entry.label}/${speaker}: "${collapseWhitespace(entry.message.content, 220)}"`;
|
||||
}
|
||||
|
||||
function cleanSocialTargetLabel(value: string | undefined): string {
|
||||
return collapseWhitespace(value ?? "").replace(/^repo:/i, "").trim();
|
||||
}
|
||||
|
||||
function normalizeSocialLabel(value: string | undefined): string {
|
||||
return (value ?? "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
||||
}
|
||||
|
||||
function projectPrivateNoteForMemorySurface(note: string): string {
|
||||
return note
|
||||
.replace(/\bdo not prompt (?:her|him|them|it|[A-Z][A-Za-z0-9_-]*) as\b/gi, "she refuses to be treated as")
|
||||
|
|
|
|||
|
|
@ -259,3 +259,4 @@
|
|||
{"ts":"2026-05-21T23:05:32Z","type":"verification","status":"ok","note":"Tightened correction-acknowledgement behavior for repo Faces. Face prompts now say human corrections of the Face's own claim/proposal require a compact public acknowledgement unless already acknowledged, and the parent Interpreter must retry private silence instead of routing state-only. Worker Interpreter context now preserves full prompts up to 28k instead of slicing off later transcript evidence. Aqua split-model smoke now produces a public Aquarium acknowledgement of Metacrat's correction while preserving narrowed vocal-witness pressure in state."}
|
||||
{"ts":"2026-05-21T23:32:35Z","type":"verification","status":"ok","note":"Added explicit fake-consensus and jurisdiction-theft guards to repo Face and parent Interpreter prompts. Agent repetition is now standing prompt evidence, not approval; claims like consensus/we decided require human approval/direct ask, steward ownership, or grounded evidence. Smoked Mimir against repeated Aqua wet-voice chatter plus human correction: full gpt-5.4 Face acknowledged overreach, deferred Aqua's proof vocabulary, narrowed to timing/signal integrity, and Spark Interpreter routed an Aquarium acknowledgement plus boundary STATE NOTE."}
|
||||
{"ts":"2026-05-21T23:43:35Z","type":"implementation","status":"ok","note":"Moved the Mimir body-awareness failure upstream instead of adding a correction compensator. Repo Face turn prompts now include read-only recent home-repo activity from export-recent-repo-activity before Discord transcript, using the Face state path as cursor source. Assembled Mimir prompt showed current Mimir commits (probe frame events, camera topology, sensor pulls) before the chat correction. Smoke with the Mimir exchange produced a Face acknowledgement centered on six image sensors first, USB/Raven topology as body reality, and audio/remote feeds downstream; Spark Interpreter routed an Aquarium SAY plus state note."}
|
||||
{"ts":"2026-05-22T10:31:29Z","type":"implementation","status":"ok","note":"Repo Face social bonds were private prompt texture rather than live social objects. Added a deterministic social-affordance prompt surface that links active typed person-bonds/status reads to recent raw messages from that person, verified by assembled Nibu/Aqua prompts and typecheck."}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,10 @@ objective:
|
|||
restart, with Bifrost work routing paused because the governance bridge
|
||||
proved too disorienting for live social traffic. The immediate priority is
|
||||
to harden the Face/Interpreter/speech boundary with live evidence: live-room
|
||||
conversation memory must be projected into character-facing prose, no
|
||||
structured action truncation, no fake channel ids, no owner-DM leakage, and
|
||||
no premature dispatch machinery.
|
||||
conversation must arrive as raw transcript evidence, typed social state must
|
||||
become live person-hooks when recent speakers match, no structured action
|
||||
truncation, no fake channel ids, no owner-DM leakage, and no premature
|
||||
dispatch machinery.
|
||||
|
||||
constraints:
|
||||
- Public traffic still rides one remote Qwen box plus local embedding and ops infrastructure.
|
||||
|
|
@ -105,6 +106,7 @@ accepted_design:
|
|||
- Face prompts carry explicit Epiphany identity doctrine. The base Void style pack contributes discipline, humor permission, and source-grounding habits, but the registered Face state, Epiphany, repo-local `.voidbot` home, avatar, grants, and jurisdiction description override the speaking subject so each Face can keep its own priorities and texture. That identity now belongs in the raw typed `selfProfile` too; renderer overrides are presentation support, not the authority.
|
||||
- Repo Face volatile state projection is model-owned again. The heartbeat scheduler may deterministically assemble a compact state packet, but live child prompts must pass that packet through `prompts/repo-face-state-projector.prompt.md` before it reaches the Face. The deterministic packet is an emergency isolation fallback only behind `REPO_FACE_STATE_PROJECTOR_ENABLED=false`, and both paths reject schema/prompt-construction leaks such as `grants:`, `jurisdictions:`, `Face of`, repo paths, and "do not prompt" language before prompt assembly succeeds.
|
||||
- Repo Face conversation evidence is deterministic again. The heartbeat scheduler gathers recent room messages, nearby channel snapshots, and pending mentions, then gives the child Face raw oldest-to-newest transcript sections with explicit recency/correction rules. The model state projector still owns identity, memory, affect, social reads, and other lived internal pressure; it does not summarize room messages or decide consensus for the Face.
|
||||
- Repo Face social affordances are deterministic context, not a banter mandate. The heartbeat prompt matches active typed person-bonds/status reads to recent raw messages from the named person and shows only those live hooks to the child Face, so relationship state can bend public speech without becoming private flavor text or generic forced chumminess.
|
||||
- Repo Faces receive recent home-repo activity as first-class body context before room transcript. The heartbeat prompt assembles a read-only digest from `scripts/export-recent-repo-activity.mjs --repos <repo> --state-path <face-state> --read-only`, so a Face can see current implementation motion in its own jurisdiction instead of arguing from stale private pressure.
|
||||
- Face embodiment comes before task execution. Repo concerns, private rumination, public posts, jokes, objections, and silence should all be filtered through the Face's perspective, feelings, drives, values, wounds, fascinations, aesthetic taste, and dignity. Values must suit the kind of person the character is, not merely the jobs assigned to the Face.
|
||||
- Face affect is typed substrate now, not prompt garnish. Each Face state may carry explicit needs, social bonds, status reads, and mood dimensions in `void.face_affect`: substrate concern, attention hunger, agency frustration, recognition needs, territoriality, rest/play pressure, friendships, rivalries, envy, pride, irritation, pampering, neglect, bypasses, and consultation status. Affect is not an action request by itself; it bends speaking pressure and gives agency pressure a personal source when a need wants concrete change.
|
||||
|
|
|
|||
Loading…
Reference in a new issue