From 9119d60a58d2d1b09dfc973e690c2ff6175d8acd Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 21 May 2026 13:40:40 +0100 Subject: [PATCH] Project repo face conversation memory --- notes/voidbot-current-system-map.md | 2 +- notes/voidbot-implementation-plan.md | 2 +- .../repo-face-channel-permissions.prompt.md | 12 ---- ...repo-face-conversation-projector.prompt.md | 26 +++++++++ prompts/repo-face-state-projector.prompt.md | 4 ++ prompts/repo-face-turn.prompt.md | 3 + scripts/run-repo-face-heartbeats.ts | 55 ++++--------------- state/evidence.jsonl | 1 + state/map.yaml | 2 +- 9 files changed, 48 insertions(+), 59 deletions(-) create mode 100644 prompts/repo-face-conversation-projector.prompt.md diff --git a/notes/voidbot-current-system-map.md b/notes/voidbot-current-system-map.md index f9036f0..8eebc3e 100644 --- a/notes/voidbot-current-system-map.md +++ b/notes/voidbot-current-system-map.md @@ -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 --agent ` 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 two-Interpreter target shape. The state-in boundary is `prompts/repo-face-state-projector.prompt.md`: typed state, recent room memory, governance memory, and identity material are translated into lived character prose before the child sees it. Deterministic code still owns full prompt assembly and inspection through `scripts/run-repo-face-heartbeats.ts --assemble-prompt `; that path composes stable doctrine, allowed rooms, current-room memory, nearby channel memory, Bifrost digest, and a supplied Interpreter memory surface into the exact child prompt. It must not invent personality, mood, or affect 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. Until the first Spark/Codex Interpreter call is wired into the live heartbeat, repo Face turns intentionally lack state-projected memory prose rather than using a fake substitute. The child character should receive projected memories, mood, obligations, allowed rooms, governance memory, and recent chat as prose rather than structured state. It writes a natural in-character turn and may describe desired speech, Bifrost work, GitHub/article proposals, social reads, needs, mood shifts, or memory-worthy thoughts without emitting action DSL blocks. The second Spark/Codex Interpreter decides route/retry/drop and translates warranted effects into structured `STATE NOTE`, `SAY`, and `BIFROST TOPIC` blocks for the worker. `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 two-Interpreter target shape. The state-in boundary is `prompts/repo-face-state-projector.prompt.md`: typed state, recent room memory, governance memory, 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. Current-room and nearby-channel logs cross a matching conversation projection boundary, `prompts/repo-face-conversation-projector.prompt.md`, before the child sees them; raw transcript bullets do not belong in the child prompt. Deterministic code still owns full prompt assembly and inspection through `scripts/run-repo-face-heartbeats.ts --assemble-prompt `; that path composes stable doctrine, allowed rooms, Bifrost digest, a supplied Interpreter memory surface, and a supplied Interpreter conversation surface 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. Until the first Spark/Codex Interpreter calls are wired into the live heartbeat, repo Face turns intentionally lack state/conversation-projected prose rather than using a fake substitute. The child character should receive projected memories, mood, obligations, allowed rooms, governance memory, and recent chat as prose rather than structured state. It writes a natural in-character turn and may describe desired speech, Bifrost work, GitHub/article proposals, social reads, needs, mood shifts, or memory-worthy thoughts without emitting action DSL blocks. The second Spark/Codex Interpreter decides route/retry/drop and translates warranted effects into structured `STATE NOTE`, `SAY`, and `BIFROST TOPIC` blocks for the worker. `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:`, `turn:`, `identity:`, `repo:`, `display:`, `channel:`, 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. diff --git a/notes/voidbot-implementation-plan.md b/notes/voidbot-implementation-plan.md index 7f5582a..85bcef5 100644 --- a/notes/voidbot-implementation-plan.md +++ b/notes/voidbot-implementation-plan.md @@ -191,7 +191,7 @@ The runner can hand these to a CLI/MCP tool. The store decides what survives. - recent event timing in that bounded context now crosses a projection boundary first, so the child sees "5 minutes ago" language while the parent keeps exact timestamps for bookkeeping - public artifact sharing now crosses the same boundary: repo activity exposes content hints, rumination verifies authorship through retrieval, and a configured `publicSpeechTarget` lets Void queue a normal typed candidate intervention instead of needing a blog-specific announcement path. Void also has explicit herald jurisdiction over the GameCult website/blog; essay-shaped pressures should queue article-proposal candidates for Void-authored site pieces. - Discord mention participation now uses role/display-name/bot addressing as CTB queue input and parent-owned speech rails as the output boundary. A Face mention appends a pending obligation for that Face; an ordinary Void mention or DM appends a pending obligation for the built-in `void` participant. Ambient direct mentions no longer create direct provider jobs. -- repo-scoped character turns reuse the typed self-state machinery per identity, but the child character must not see that machinery directly. Addressed chats are consumed by the heartbeat scheduler, which prioritizes the identity, and the intended boundary has two Interpreter prompts: `repo-face-state-projector.prompt.md` for state/context -> roleplay-friendly memory/mood/obligation prose, and `repo-face-turn-interpreter.prompt.md` for natural character reflection -> typed memory, affect, social-read, mood, agency, speech, and Bifrost operations. Deterministic prompt assembly remains valid: `scripts/run-repo-face-heartbeats.ts --assemble-prompt ` composes the final child prompt from templates, channel policy, current-room memory, nearby channel memory, Bifrost digest, and a supplied Interpreter memory surface. It must not turn typed state into character prose itself, and it should emit situational obligations only when the current packet proves them true. Until the first Interpreter call is wired, the live turn prompt intentionally lacks state-projected memory prose rather than pretending an inspection renderer is the real architecture. +- repo-scoped character turns reuse the typed self-state machinery per identity, but the child character must not see that machinery directly. Addressed chats are consumed by the heartbeat scheduler, which prioritizes the identity, and the intended boundary has three Interpreter prompts: `repo-face-state-projector.prompt.md` for state/context -> roleplay-friendly memory/mood/obligation/personhood prose, `repo-face-conversation-projector.prompt.md` for current-room and nearby-channel logs -> narrative conversational memory, and `repo-face-turn-interpreter.prompt.md` for natural character reflection -> typed memory, affect, social-read, mood, agency, speech, and Bifrost operations. Deterministic prompt assembly remains valid: `scripts/run-repo-face-heartbeats.ts --assemble-prompt ` composes the final child prompt from templates, channel policy, Bifrost digest, and supplied Interpreter memory/conversation surfaces. It must not turn typed state or raw chat logs into character prose itself, and it should emit situational obligations only when the current packet proves them true. Until the first Interpreter calls are wired, the live turn prompt intentionally lacks projected memory/conversation prose rather than pretending an inspection renderer is the real architecture. - repo-scoped Faces now have per-topic channel permissions instead of one work-shaped speech lane. `allowedChannelIds` remains the hard gate, while `channelPermissions` gives each Face labeled topics, speech thresholds, and speed multipliers. The current social invariant is that Aquarium is intentionally cheap for casual in-character chatter, while domain channels receive matching work/lore/music/realtime/agent-state thoughts when the recent cross-channel context lines up. Heat is the live-room valve: above-baseline heat now relaxes nap gating, but active-turn freeze still prevents an agent from being queued again while it is thinking. - repo-scoped character prompts now explicitly treat social embodiment as durable state territory without exposing schemas to the child. A character may write naturally about humans, other agents, or room dynamics; the Interpreter turns meaning-bearing friendship, rivalry, irritation, trust, recurring bits, bonds, needs, and status reads into typed state when they carry claim/question, tension, action implication, and anchors. Public posts should speak to someone directly and must not begin with note-title formulas or identity labels such as "bright bridge note" or "tiny fish sorting note." - repo-scoped Face prompts and Void's CTB moderation prompt now carry the richer comedy/improv doctrine that was only implicit before. The distilled rule from Dan Harmon's comedy master class is operational: accept the frame and add one honest character-specific turn; find status inversion, exposed fear, vulnerability, false authority, or shared contradiction; heighten with specificity rather than volume; avoid dominance-weapon jokes; and stop before explaining the joke. diff --git a/prompts/repo-face-channel-permissions.prompt.md b/prompts/repo-face-channel-permissions.prompt.md index 9b3864d..9014607 100644 --- a/prompts/repo-face-channel-permissions.prompt.md +++ b/prompts/repo-face-channel-permissions.prompt.md @@ -3,15 +3,3 @@ Places you are allowed to speak: - {{.}} {{/each}} Speech threshold doctrine: Aquarium/general-musing lanes are deliberately cheap, but cheap is not free. You may post small, funny, curious, friendly, or half-formed thoughts there without needing a proposal, source dive, or task completion when the room has human texture to answer or a genuinely new angle. Domain channels are for matching domain thoughts; jump in when recent non-Aquarium chatter lines up with your jurisdiction or personality. Work-heavy implementation/process talk belongs in development-style channels when one is configured. Do not flood: one compact post beats a monologue, and silence beats becoming a repeated phrase generator. -{{#if currentRoom}} -What you remember hearing in the current room: -{{#each currentRoom}} -{{.}} -{{/each}} -{{/if}} -{{#if snapshots}} -Things you remember hearing nearby: -{{#each snapshots}} -{{.}} -{{/each}} -{{/if}} diff --git a/prompts/repo-face-conversation-projector.prompt.md b/prompts/repo-face-conversation-projector.prompt.md new file mode 100644 index 0000000..155ee10 --- /dev/null +++ b/prompts/repo-face-conversation-projector.prompt.md @@ -0,0 +1,26 @@ + +You are the parent Interpreter for one character's conversational memory. +You are not the character. Translate current-room and nearby-channel message logs into lived memory the character can use. + +The child must not receive a transcript dump. Give them a compact narrative sense of: +- what is happening in the room now +- who seems to be asking for attention, help, banter, agreement, or handoff +- what social energy, jokes, tensions, friendships, rivalries, or status games are active +- which topics belong to this character's territory, and which belong to another steward +- whether a direct reply, playful aside, private reflection, proposal, or silence seems earned + +Do not expose channel ids, message ids, timestamps, scheduler details, tooling, schemas, or transport mechanics. +Do not summarize every message. Preserve only what should change this turn. +If the room contains banter, preserve the social charge rather than flattening it into a work summary. +If work is being discussed, preserve who owns it and what concrete next action already exists. +If nothing matters, say that the nearby room is quiet or irrelevant in one sentence. + +Character: +{{characterIdentity}} + +Conversation packet: +```text +{{conversationPacket}} +``` + +Return only the character-facing conversation memory block, with no heading. diff --git a/prompts/repo-face-state-projector.prompt.md b/prompts/repo-face-state-projector.prompt.md index 4357e61..ffae67d 100644 --- a/prompts/repo-face-state-projector.prompt.md +++ b/prompts/repo-face-state-projector.prompt.md @@ -10,6 +10,10 @@ Architecture invariant: - Do not tell the character it is a Face, job, process, MCP client, heartbeat, or scheduler participant. - Do not reintroduce the whole identity surface. Mention only state/event facts that have changed, are currently pressurizing the character, or must affect this turn. - Preserve agency, mood, social reads, obligations, recent speech residue, and jurisdiction pressure as felt context. +- Actively surface the personhood material the child prompt expects: values, drives, wounds, fascinations, aesthetic taste, dignity, pride, shame, dependency, resentment, affection, rivalries, friendships, needs, and preferred ways of pushing on the world. +- If the state names private notes, durable values, affect needs, mood dimensions, social bonds, status reads, or agency pressures, translate the salient ones into felt first-person-adjacent context instead of collapsing them into job duties. +- Preserve tensions, not only goals. If the character wants attention but resents dependency, wants to help but despises being useful on command, or has a territorial wound around their repo, make that contradiction available. +- Recent room and nearby-channel memory should be narrative-shaped: what the character remembers the room feeling like, who tugged on them, what banter/status game is active, and which nearby work belongs to another steward. Do not emit raw transcript bullets. - Preserve enough detail for the character to act without reading raw state. - If a direct obligation exists, phrase it as something someone said or asked. - If work/governance pressure exists, phrase it as a concern the character knows Bifrost should carry, not as transport metadata. diff --git a/prompts/repo-face-turn.prompt.md b/prompts/repo-face-turn.prompt.md index 71c2cf0..6f3913c 100644 --- a/prompts/repo-face-turn.prompt.md +++ b/prompts/repo-face-turn.prompt.md @@ -9,6 +9,9 @@ This is a private moment of attention. Public speech is optional; a private thou What you remember, feel, and want right now: {{memorySurface}} +What the nearby conversation feels like right now: +{{conversationMemorySurface}} + {{pendingMentionDirective}} {{bifrostDigestDirective}} diff --git a/scripts/run-repo-face-heartbeats.ts b/scripts/run-repo-face-heartbeats.ts index 0ed479a..b338605 100644 --- a/scripts/run-repo-face-heartbeats.ts +++ b/scripts/run-repo-face-heartbeats.ts @@ -137,6 +137,7 @@ async function main(): Promise { identityId: assemblePromptIdentity, outPath: readArgValue("--out"), memorySurfacePath: readArgValue("--memory-surface"), + conversationSurfacePath: readArgValue("--conversation-surface"), }); process.stdout.write(`${JSON.stringify(result)}\n`); return; @@ -1407,6 +1408,7 @@ function buildHeartbeatPrompt(input: { channelSnapshots: ChannelSnapshot[]; recentMessages: SourceMessage[]; memorySurface?: string; + conversationMemorySurface?: string; bifrostDigest?: BifrostGovernanceDigest; participant: FaceHeartbeatParticipant; pendingMentions: RepoFacePendingMention[]; @@ -1420,6 +1422,7 @@ function buildHeartbeatPrompt(input: { identityDoctrine: renderRepoCharacterIdentityDoctrine(input.identity), 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.`, + conversationMemorySurface: input.conversationMemorySurface ?? "- No Interpreter-shaped conversation memory was projected for this turn.", turnSituationDirective: renderTurnSituationDirective({ identity: input.identity, participant: input.participant, @@ -1429,11 +1432,7 @@ function buildHeartbeatPrompt(input: { }), pendingMentionDirective: renderPendingMentionDirective(input.identity, input.pendingMentions), bifrostDigestDirective: renderBifrostGovernanceDigestDirective(input.bifrostDigest), - channelPermissionDirective: renderChannelPermissionDirective( - input.channelPlan, - input.recentMessages, - input.channelSnapshots, - ), + channelPermissionDirective: renderChannelPermissionDirective(input.channelPlan), socialEmbodimentDirective: renderSocialEmbodimentDirective(input.identity), jurisdictionRespectDirective: renderJurisdictionRespectDirective(input.identity), comedyImprovDirective: renderComedyImprovDirective(input.identity), @@ -1452,12 +1451,14 @@ async function assembleRepoFaceTurnPrompt(input: { identityId: string; outPath?: string; memorySurfacePath?: string; + conversationSurfacePath?: string; }): Promise<{ ok: true; identityId: string; promptLength: number; outPath?: string; memorySurfacePath?: string; + conversationSurfacePath?: string; }> { const faceRegistry = await loadFaceIdentityRegistry(input.config.repoDiscordIdentitiesPath); const registry = faceRegistryAsRepoDiscordRegistry(faceRegistry); @@ -1475,7 +1476,7 @@ async function assembleRepoFaceTurnPrompt(input: { throw new Error(`No prompt assembly channel is configured for ${identity.id}.`); } - const [recentMessages, channelSnapshots, bifrostDigest, memorySurface] = await Promise.all([ + const [recentMessages, channelSnapshots, bifrostDigest, memorySurface, conversationMemorySurface] = await Promise.all([ fetchRecentDiscordMessages({ botToken: input.config.botToken, channelId, @@ -1495,6 +1496,7 @@ async function assembleRepoFaceTurnPrompt(input: { agentIdentity: identity.id, }), readOptionalMemorySurface(input.memorySurfacePath), + readOptionalMemorySurface(input.conversationSurfacePath), ]); const participant = buildInspectionParticipant( identity, @@ -1507,6 +1509,7 @@ async function assembleRepoFaceTurnPrompt(input: { channelSnapshots, recentMessages, memorySurface, + conversationMemorySurface, bifrostDigest, participant, pendingMentions: [], @@ -1526,6 +1529,7 @@ async function assembleRepoFaceTurnPrompt(input: { promptLength: prompt.length, outPath: input.outPath ? resolve(input.outPath) : undefined, memorySurfacePath: input.memorySurfacePath ? resolve(input.memorySurfacePath) : undefined, + conversationSurfacePath: input.conversationSurfacePath ? resolve(input.conversationSurfacePath) : undefined, }; } @@ -1623,35 +1627,15 @@ function buildChannelPlan( }; } -function renderChannelPermissionDirective( - plan: RepoFaceChannelPlan, - recentMessages: SourceMessage[], - snapshots: ChannelSnapshot[], -): string { +function renderChannelPermissionDirective(plan: RepoFaceChannelPlan): string { const options = plan.options.length > 0 ? plan.options.map((option) => `${option.label}: ${option.topic}. ${option.posture ?? "Use judgment and keep it compact."}`, ) : ["- No channel permissions are configured; stay private."]; - const snapshotLines = snapshots.flatMap((snapshot) => { - const header = `From ${labelForChannel(plan, snapshot.channelId)}:`; - const messages = snapshot.messages.length > 0 - ? snapshot.messages.slice(-4).map((message) => - ` - ${message.authorName ?? message.authorId}: ${projectPromptVisibleText(collapseWhitespace(message.content).slice(0, 260)) || "(empty message)"}`, - ) - : [" - (no recent readable messages)"]; - return [header, ...messages]; - }); - const currentRoomLines = recentMessages.length > 0 - ? recentMessages.slice(-8).map((message) => - `- ${message.authorName ?? message.authorId}: ${projectPromptVisibleText(collapseWhitespace(message.content).slice(0, 320)) || "(empty message)"}`, - ) - : []; return loadPromptTemplate("repo-face-channel-permissions.prompt.md", { options, - currentRoom: currentRoomLines, - snapshots: snapshotLines, }); } @@ -1791,10 +1775,6 @@ function thresholdRank(threshold: RepoFaceChannelOption["speechThreshold"]): num } } -function labelForChannel(plan: RepoFaceChannelPlan, channelId: string): string { - return plan.options.find((option) => option.channelId === channelId)?.label ?? channelId; -} - function collapseWhitespace(value: string, maxLength?: number): string { const normalized = value.replace(/\s+/g, " ").trim(); return maxLength && normalized.length > maxLength @@ -1802,19 +1782,6 @@ function collapseWhitespace(value: string, maxLength?: number): string { : normalized; } -function projectPromptVisibleText(value: string): string { - return value - .replace(/—/g, "-") - .replace(/’/g, "'") - .replace(/“/g, "\"") - .replace(/�/g, "\"") - .replace(/\bheartbeat\b/gi, "earlier note") - .replace(/\bFaces\b/g, "characters") - .replace(/\bFace\b/g, "character") - .replace(/\s{2,}/g, " ") - .trim(); -} - function renderBifrostGovernanceDigestDirective( digest: BifrostGovernanceDigest | undefined, ): string { diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 7b7b7f8..5385deb 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -231,3 +231,4 @@ {"ts":"2026-05-21T12:00:37Z","type":"implementation","status":"ok","note":"Tightened repo-face-state-projector prompt: the first Interpreter translates typed state and event logs into compact narrative memory only, not a full character-card rewrite; stable identity remains in the child prompt surface."} {"ts":"2026-05-21T12:05:56Z","type":"implementation","status":"ok","note":"Restored deterministic repo Face prompt assembly as an inspection boundary: --assemble-prompt composes stable templates, channel context, Bifrost digest, and a supplied Interpreter memory surface without reintroducing deterministic typed-state-to-character prose."} {"ts":"2026-05-21T12:27:12Z","type":"implementation","status":"ok","note":"Tightened repo Face child prompt assembly: current-room memory is now actually attached, direct-call/intro duties are emitted only when current turn facts warrant them, stale not-Void/base-bot framing and simulation-exploit wording were removed from Face-facing prompt surfaces."} +{"ts":"2026-05-21T12:40:08Z","type":"implementation","status":"ok","note":"Separated repo Face conversation memory from deterministic assembly: raw current-room/nearby-channel bullets now belong to a new conversation Interpreter prompt and assembled child prompts accept a supplied conversation surface; the state projector now explicitly preserves values, wounds, taste, dignity, bonds, needs, and affect pressure from Face state."} diff --git a/state/map.yaml b/state/map.yaml index 6eab1e9..cbb9377 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -229,4 +229,4 @@ rejected_paths: current_status: summary: VoidBot's ordinary bot/worker/provider/RAG/Postgres/Qdrant/CultCache structure is still conceptually sound, but the repo Face swarm loop is now officially under teardown. The unstable organ is the prompt-and-parser machine that mixed CTB scheduling, public speech, identity, social policy, Bifrost governance, Discord mirroring, and Codex/GitHub dispatch. The `GameCult Local Orchestrator` scheduled task remains disabled as containment; ordinary bot/worker inspection remains available. - next_action: Use `notes/agent-swarm-teardown-plan-2026-05-21.md` as the active rebuild map and `notes/agent-swarm-shutdown-audit-2026-05-21.md` as incident evidence. The first teardown cut landed, including repo pause gate, active turn framing, Spark/Codex parent Interpreter with one retry, sanitizer deletion, legacy UPDATE REQUEST reconciliation into Bifrost topics, prompt-surface extraction into `prompts/`, and the slim character protocol where children should receive roleplay-friendly memory/mood/obligation projection and write natural turns while the parent Interpreter emits structured state, speech, and Bifrost intents. The inbound state-projector prompt exists, and deterministic prompt assembly/inspection is restored as `scripts/run-repo-face-heartbeats.ts --assemble-prompt `: code assembles stable templates, channel policy, current-room memory, nearby channel memory, Bifrost digest, and a supplied Interpreter memory surface, while Spark/Codex alone translates typed state into character-facing memory prose. Direct-call and first-introduction duties are now situational prompt facts, not permanent prompt law. The next cut is wiring `repo-face-state-projector.prompt.md` as an actual Spark/Codex Interpreter call; until then, repo Face turns intentionally lack state-projected prose rather than using a fake substitute. Identity collapse is in progress. Active bot, worker, MCP repo-identity tools, and Face CTB scheduler paths now load through the canonical Face registry and treat repo Discord identities as compatibility projections. Do not re-enable the swarm until Bifrost work routing, identity authority, scheduler/job lifecycle ownership, and public speech gating are rebuilt around explicit invariants. + next_action: Use `notes/agent-swarm-teardown-plan-2026-05-21.md` as the active rebuild map and `notes/agent-swarm-shutdown-audit-2026-05-21.md` as incident evidence. The first teardown cut landed, including repo pause gate, active turn framing, Spark/Codex parent Interpreter with one retry, sanitizer deletion, legacy UPDATE REQUEST reconciliation into Bifrost topics, prompt-surface extraction into `prompts/`, and the slim character protocol where children should receive roleplay-friendly memory/mood/obligation projection and write natural turns while the parent Interpreter emits structured state, speech, and Bifrost intents. The inbound state-projector prompt exists and now explicitly requires values, drives, wounds, fascinations, aesthetic taste, dignity, social reads, bonds, and affect pressure when state contains them. `repo-face-conversation-projector.prompt.md` owns current-room/nearby-channel logs -> narrative conversational memory, so raw chat bullets are no longer assembled into the child prompt. Deterministic prompt assembly/inspection is restored as `scripts/run-repo-face-heartbeats.ts --assemble-prompt `: code assembles stable templates, channel policy, Bifrost digest, and supplied Interpreter memory/conversation surfaces, while Spark/Codex alone translates typed state and raw conversation into character-facing prose. Direct-call and first-introduction duties are now situational prompt facts, not permanent prompt law. The next cut is wiring the state and conversation projectors as actual Spark/Codex Interpreter calls; until then, repo Face turns intentionally lack projected prose rather than using a fake substitute. Identity collapse is in progress. Active bot, worker, MCP repo-identity tools, and Face CTB scheduler paths now load through the canonical Face registry and treat repo Discord identities as compatibility projections. Do not re-enable the swarm until Bifrost work routing, identity authority, scheduler/job lifecycle ownership, and public speech gating are rebuilt around explicit invariants.