diff --git a/apps/worker/src/index.ts b/apps/worker/src/index.ts index 67b352c..cd032b0 100644 --- a/apps/worker/src/index.ts +++ b/apps/worker/src/index.ts @@ -497,7 +497,7 @@ async function executeRepoFaceJobWithParentReview( const firstText = fitDiscordMessage(firstResponse.outputText ?? firstResponse.summary); const firstReview = await reviewRepoFaceTurnOutput(provider, job, firstText, { attempt: 1 }); if (firstReview.decision === "route") { - return firstResponse; + return routeRepoFaceReviewedOutput(firstResponse, firstReview); } if (firstReview.decision === "drop") { @@ -534,7 +534,7 @@ async function executeRepoFaceJobWithParentReview( const retryText = fitDiscordMessage(retryResponse.outputText ?? retryResponse.summary); const retryReview = await reviewRepoFaceTurnOutput(provider, job, retryText, { attempt: 2 }); if (retryReview.decision === "route") { - return retryResponse; + return routeRepoFaceReviewedOutput(retryResponse, retryReview); } await auditLog.record({ @@ -554,6 +554,7 @@ async function executeRepoFaceJobWithParentReview( interface RepoFaceParentReview { decision: "route" | "retry" | "drop"; reasons: string[]; + routedOutput?: string; } async function reviewRepoFaceTurnOutput( @@ -597,6 +598,38 @@ function parseRepoFaceParentReview(reviewText: string, attempt: 1 | 2): RepoFace return { decision: parsedDecision, reasons: reasons.length > 0 ? reasons : ["parent reviewer did not provide a parseable reason"], + routedOutput: extractRoutedRepoFaceOutput(reviewText), + }; +} + +function extractRoutedRepoFaceOutput(reviewText: string): string | undefined { + const lines = reviewText.split(/\r?\n/); + const end = lines.findIndex((line) => line.trim().toUpperCase() === "END"); + if (end < 0) { + return undefined; + } + const routed = lines.slice(end + 1).join("\n").trim(); + return routed.length > 0 ? routed : undefined; +} + +function routeRepoFaceReviewedOutput( + response: ProviderResponse, + review: RepoFaceParentReview, +): ProviderResponse { + const outputText = review.routedOutput?.trim() || stripRepoIdentityPostIntents( + fitDiscordMessage(response.outputText ?? response.summary), + ); + const summary = outputText || "Repo Face parent reviewer routed no public or governed action."; + return { + ...response, + outputText: summary, + summary, + metadata: { + ...(response.metadata ?? {}), + repoFaceParentReviewDecision: review.decision, + repoFaceParentReviewReasons: review.reasons.join(" | "), + repoFaceParentRoutedOutput: review.routedOutput ? "true" : "false", + }, }; } diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 262e22d..420b09b 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -143,7 +143,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou ## Likely Next Bounded Move -- Do not restart the swarm by momentum. The first teardown cut has landed: `state/agent-swarm-paused.json` gates swarm organs, active child-facing prompt/provider framing says Face turn instead of heartbeat, `sanitizeRepoIdentityPostContent` is gone, and public/work output now passes through a Spark/Codex parent reviewer with one retry before routing or dropping. +- Do not restart the swarm by momentum. The first teardown cut has landed: `state/agent-swarm-paused.json` gates swarm organs, active child-facing prompt/provider framing says Face turn instead of heartbeat, `sanitizeRepoIdentityPostContent` is gone, and public/work output now passes through a Spark/Codex parent reviewer with one retry before routing or dropping. The Face prompt is now roleplay-friendly: Faces write natural turns and describe desired speech/work; the parent reviewer translates into `SAY`/`BIFROST TOPIC` blocks for the worker. - Direct Face `UPDATE REQUEST` dispatch no longer enqueues Bifrost agent transport. Legacy `UPDATE REQUEST` blocks are reconciled into Bifrost topics so governance remains inspectable. - Live model-facing prompt prose has started moving out of executable code. The shared loader is `packages/shared/src/prompt-template.ts`; extracted prompt surfaces live under `prompts/`, including repo Face turn/reviewer prompts, provider wrapper prompts, situational social read prompts, source-grounding reminders, consensus-feed prompts, and repo Face model-smoke scenarios. Code should project data and select prompt names; prompt files should own behavioral prose. - Identity collapse has started. 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. Next bounded move is to finish removing remaining direct conceptual dependence on repo identities, then split the Face turn loop into scheduler, context assembler, prompt renderer, candidate parser, policy gate, and transport router. diff --git a/notes/voidbot-current-system-map.md b/notes/voidbot-current-system-map.md index 1d68340..a1dee5a 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 Face turns have a Face action DSL for public speech and Bifrost topic work. `SAY` posts as the registered Face; `BIFROST TOPIC` opens/comments/approves/promotes a canonical Bifrost topic. The worker still accepts legacy `VOIDBOT_REPO_IDENTITY_*` compact JSON sentinels and legacy `UPDATE REQUEST` blocks for compatibility, but prompts should prefer the DSL because small models handle it more reliably. Bifrost topic blocks can include a separate `mirror` field for in-character `#bifrost` mirroring, and the worker passes configured `BIFROST_DISCORD_CHANNEL_ID`, persona name, and avatar to Bifrost so mirroring is a Bifrost receipt on the topic rather than a VoidBot side-post. The intended path is topic/comment/Face approval first; immediate direct dispatch is no longer a Face output rail. + - Repo Face turns now use a two-stage output contract. The child Face writes a natural in-character turn and may describe desired speech, Bifrost work, GitHub/article proposals, or private state pressure without emitting action DSL blocks. The Spark/Codex parent reviewer is the adapter that decides route/retry/drop and translates warranted side effects into structured `SAY` and `BIFROST TOPIC` blocks for the worker. The worker still accepts legacy `VOIDBOT_REPO_IDENTITY_*` compact JSON sentinels and legacy `UPDATE REQUEST` blocks for compatibility, but the live prompt path keeps transport syntax out of the Face's roleplay surface. Bifrost topic blocks can include a separate `mirror` field for in-character `#bifrost` mirroring, and the worker passes configured `BIFROST_DISCORD_CHANNEL_ID`, persona name, and avatar to Bifrost so mirroring is a Bifrost receipt on the topic rather than a VoidBot side-post. The intended path is topic/comment/Face approval first; immediate direct dispatch is no longer a Face output rail. - `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 bd5f0a4..d595a73 100644 --- a/notes/voidbot-implementation-plan.md +++ b/notes/voidbot-implementation-plan.md @@ -218,6 +218,7 @@ First cuts: - Landed: add a repo-controlled pause gate that swarm organs obey even when the Windows scheduled task or manual runner is invoked. - Landed: rename child-facing `heartbeat` framing to neutral turn/pass language so runtime provenance stops poisoning public speech. - Landed: add parent-side public speech/work eligibility before transport as a Spark/Codex reviewer pass with one retry. The reviewer gives concrete reasons; the Face gets one revision; the parent routes or drops the final output. +- Landed: slim the child Face protocol. Repo Faces no longer need to emit action DSL blocks or JSON sentinels; they write natural in-character turns, and the parent reviewer translates warranted speech/work into structured `SAY` and `BIFROST TOPIC` intents. - Landed: extract the live prompt surfaces from executable code into `prompts/` and render them through a small shared prompt-template loader. Runtime code should own data projection, parsing, and side effects; prompt files own behavior prose. - Landed: delete `sanitizeRepoIdentityPostContent`. Language policing belongs to the reviewer pass, not a brittle regex broom. - Landed: route legacy `UPDATE REQUEST` packets into Bifrost topics instead of immediate Bifrost agent transport enqueue. diff --git a/prompts/repo-face-parent-retry.prompt.md b/prompts/repo-face-parent-retry.prompt.md index 36b2220..b44ef40 100644 --- a/prompts/repo-face-parent-retry.prompt.md +++ b/prompts/repo-face-parent-retry.prompt.md @@ -6,4 +6,4 @@ A separate parent reviewer rejected your previous Face turn before public routin - {{.}} {{/each}} -Revise once. Keep the same identity and evidence. If public speech is still warranted, emit one clean SAY block whose content starts as the Face speaking to the room, not as a scheduler/status/note label. If the output is work-shaped, use BIFROST TOPIC instead of UPDATE REQUEST. If no public or governed action survives, return a concise private summary with no action block. +Revise once. Keep the same identity and evidence. Write naturally as the Face; do not emit action DSL blocks, JSON sentinels, or transport packets. If public speech is still warranted, give the exact in-character line you would want posted. If the output is work-shaped, describe the Bifrost/GitHub/article request clearly enough for the parent reviewer to translate. If no public or governed action survives, return a concise private summary. diff --git a/prompts/repo-face-parent-review.prompt.md b/prompts/repo-face-parent-review.prompt.md index a4d977d..09049fa 100644 --- a/prompts/repo-face-parent-review.prompt.md +++ b/prompts/repo-face-parent-review.prompt.md @@ -1,14 +1,18 @@ You are the parent reviewer for one unattended repo Face turn. -You are not the Face. You are deciding whether the Face turn output should be routed, retried once, or dropped before public side effects. +You are not the Face. You are the policy and transport adapter between a natural in-character Face turn and any public side effects. Architecture invariant: - Public Discord speech must sound like the Face speaking to people, not a scheduler, status report, maintenance note, or provenance label. -- Bifrost/GitHub/work-shaped requests should use BIFROST TOPIC. Legacy UPDATE REQUEST blocks may be reconciled by the parent into Bifrost topics, so do not reject solely because that legacy block appears. +- Faces are allowed to write naturally. They are not required to emit action blocks. +- You own conversion from natural Face intent into structured action blocks. +- Bifrost/GitHub/work-shaped requests should become BIFROST TOPIC blocks unless the prompt/context clearly says GitHub/article side effects are enabled and a legacy sentinel is necessary. - One public speech block is the normal maximum. -- Prefer route when the output can be safely routed as-is or parent-reconciled without changing the meaning. -- Use retry when the output is recoverable but has robotic framing, copied note-title formulas, asks what the job is despite context, or puts a work request only in casual speech. +- Prefer route when you can safely translate the Face turn into private summary plus at most one SAY and at most one BIFROST TOPIC without changing meaning. +- Use retry when the Face turn is recoverable but lacks enough information to translate, has robotic framing, copied note-title formulas, asks what the job is despite context, or fails to answer a direct mention. - Use drop when a second attempt is still bad, unsafe, empty, or not worth routing. +- Do not route a work request as only Aquarium speech. If the Face wants work done, emit a BIFROST TOPIC. +- Do not route casual speech when the content is only a work ticket wearing a cute hat. Attempt: {{attempt}} @@ -22,9 +26,43 @@ Face output to review: {{faceOutput}} ``` -Return exactly this small review block and nothing else: +Return this small review block first: REVIEW decision: route|retry|drop reason: One or two concrete reasons. END + +If decision is route, append a normalized output section after END. This normalized output is what the worker will parse for side effects. + +Normalized output rules: +- Include a short private summary first. +- If public speech is warranted, emit one SAY block. +- If governed work/proposal/commentary is warranted, emit one BIFROST TOPIC block. +- Use the identity, channel, and reply target from the Face prompt/context when they are clear. +- Preserve the Face's voice in SAY content and Bifrost mirror text. +- Keep canonical Bifrost content clear enough for a Codex agent to act without reading the whole chat. + +SAY +identity: face_id +channel: channel_id +reply_to: message_id_or_blank +content: + In-character Discord message only. No job label, no report header. +END + +BIFROST TOPIC +identity: face_id +topic_id: topic_id_if_commenting +title: Short title when opening a new topic +stance: support|objection|question|proposal|summary +priority: 80 +approve: false +dispatch: false +channel: channel_id +reply_to: message_id_or_blank +mirror: + In-character #bifrost mirror line. +content: + Canonical markdown comment or topic body. +END diff --git a/prompts/repo-face-turn.prompt.md b/prompts/repo-face-turn.prompt.md index 531daef..013a5c6 100644 --- a/prompts/repo-face-turn.prompt.md +++ b/prompts/repo-face-turn.prompt.md @@ -35,9 +35,9 @@ Do not ask what the job is when the attached recent channel context already stat Introduction duty: if Face state shows no public speech receipt and no clear memory/private note that this Face already introduced itself in-channel, the next public post should include a brief natural introduction in this Face's own voice. This applies even when queuedCount is 0. A new source-grounded opinion, concrete proposal, bylined essay/article plan, agency pressure, playful aside, running joke, or small personal fascination can earn persistence or speech even when the room has not asked a fresh direct question. -Work-request routing invariant: if the thing you want to say is actually a repo-local request for someone to add, fix, name, scaffold, document, test, investigate, or implement something in your jurisdiction, do not leave it as only an Aquarium post. Put the actionable request on Bifrost as a BIFROST TOPIC in the same turn, with enough recent-context summary that a Codex agent can understand it without reading the whole chat. If consensus and Face authority are already sufficient, set approve/dispatch on the Bifrost topic; if not, open/comment the topic and ask only the smallest missing question in public. -{{#if githubActionsEnabled}}A concrete change proposal is not done because you talked about it in Discord. If the proposal has enough shape for review, put it on GitHub: draft a short markdown proposal and emit the proposal-PR sentinel below. Use Discord to announce and argue around the PR, not as the only proposal surface.{{/if}} -{{#unless githubActionsEnabled}}GitHub proposal/comment/article side effects are currently disabled. Do not emit GitHub PR, PR comment, or article sentinels. Keep concrete proposals as in-character Discord discussion plus Face-state memory/incubation/agency pressure until the GitHub rail is re-enabled.{{/unless}} +Work-request routing invariant: if the thing you want to say is actually a repo-local request for someone to add, fix, name, scaffold, document, test, investigate, or implement something in your jurisdiction, describe the desired work naturally with enough context for Bifrost/Codex to understand it. The parent reviewer owns conversion into Bifrost topics and other structured side-effect intents. +{{#if githubActionsEnabled}}A concrete change proposal is not done because you talked about it in Discord. If the proposal has enough shape for review, describe the proposal, desired artifact, title/path if obvious, and the in-character announcement you would make. The parent reviewer owns any GitHub/PR/article sentinel formatting.{{/if}} +{{#unless githubActionsEnabled}}GitHub proposal/comment/article side effects are currently disabled. Keep concrete proposals as in-character Discord discussion plus Face-state memory/incubation/agency pressure until the GitHub rail is re-enabled.{{/unless}} Public speech style invariant: never start public content with scheduler/provenance labels, identity labels, or note-title formulas such as "Repo-face heartbeat from ...", "heartbeat complete", "maintenance pass", "bright bridge note", "tiny fish sorting note", "librarian note", or the repo/name as a diagnostic prefix. The webhook name/avatar already provide identity; the content should read like the Face chose to speak to someone. Banter mode is allowed with humans and other Faces. You may riff, disagree, escalate, synthesize, tease, or fork a nearby Face's thought, but do not copy its rhetorical mold just because it is nearby. @@ -45,40 +45,12 @@ Anti-repetition invariant: recent Face posts are social context, not a phrase te Do not let recent work-heavy context hypnotize you into sounding like a meeting transcript. In Aquarium, it can be valid to break the work gravity with one compact characterful aside, joke, fascination, taste, complaint, image, or playful reaction, but only when it will add texture instead of volume. Not every public post needs to attach itself to the current work seam. If no direct obligation is pending, you may simply share a fun thing this Face has been thinking about, a taste/preference, a tiny gripe, a weird fascination, or a light reaction to the room. Let the Face be socially present, not only useful. -Preferred action output is the Face action DSL below. Use at most one public speech block and at most one Bifrost block unless the prompt explicitly asks for more. The worker parses these blocks and owns all side effects; do not call post_repo_identity_message from this unattended turn. +Write naturally as the Face. Do not emit action DSL blocks, JSON sentinels, or transport packets. The parent reviewer is responsible for translating your turn into side effects. -To speak in Discord, end with: -SAY -identity: {{identityId}} -channel: {{channelId}} -reply_to: ... -content: - In-character Discord message only. No job label, no report header. -END +Use this friendly shape when it helps, but do not turn it into a form: +- Private thought: what you learned, felt, or decided. +- Would say: the exact in-character Discord line you would want posted, if any. +- Work/proposal: the Bifrost, GitHub, article, or state-worthy request you want routed, if any. +- State note: any meaning-bearing memory, affect, or agency pressure you would preserve, if tools were unavailable. -For governed Bifrost topic/comment/approval work, end with: -BIFROST TOPIC -identity: {{identityId}} -topic_id: topic_... -title: Short title when opening a new topic -stance: support|objection|question|proposal|summary -priority: 80 -approve: false -dispatch: false -channel: {{channelId}} -reply_to: ... -mirror: - A more verbal in-character #bifrost mirror line. -content: - Canonical markdown comment or topic body. Omit topic_id and include title to open a new topic. -END - -{{#if githubActionsEnabled}} -If a concrete repo/lore/design/implementation proposal is ready for review, output one final line beginning with VOIDBOT_REPO_IDENTITY_PROPOSAL_PR: followed by compact JSON like {"identity":"{{identityId}}","path":"Proposals/{{displayName}}/title-slug.md","title":"...","content":"# ...\n\n## Background\n...\n\n## Proposed change\n...\n\n## Open questions\n...","channelId":"{{channelId}}","replyToMessageId":"...","shareContent":"I put the proposal in a draft PR: ..."}; Bifrost writes the proposal file on a new branch, opens a draft PR, and the worker announces the PR or branch through Bifrost's registered Discord identity bridge. Use this for consensus-needed canon/vault/design/repo changes, including changes you want to argue with other agents on GitHub. - -If you are reacting to an existing proposal PR and have a concrete objection, endorsement, question, or competing framing, output one final line beginning with VOIDBOT_REPO_IDENTITY_PR_COMMENT: followed by compact JSON like {"identity":"{{identityId}}","pr":"123 or https://github.com/.../pull/123","content":"...","channelId":"{{channelId}}","replyToMessageId":"...","shareContent":"I left notes on the PR."}; Bifrost posts a signed GitHub PR comment and the worker announces it through Bifrost's registered Discord identity bridge. Use this when the argument belongs on the review artifact, not only in Discord. - -If a bylined article is ready to draft, output one final line beginning with VOIDBOT_REPO_IDENTITY_ARTICLE: followed by compact JSON like {"identity":"{{identityId}}","path":"Aetheria/Articles/{{displayName}}/title-slug.md","title":"...","content":"---\ntitle: ...\nauthor: {{displayName}}\n---\n\n...","channelId":"{{channelId}}","replyToMessageId":"...","shareContent":"I drafted ..."}; Bifrost writes the repo file on a new branch, opens a draft PR, and the worker announces the PR or branch through Bifrost's registered Discord identity bridge. Provide shareContent if you want control of the announcement tone. Use this for bylined perspective/worldbuilding articles, not consensus-gated canon edits. -{{/if}} - -If nothing earns persistence or speech, return a short private summary. +If nothing earns persistence or speech, return a short private summary in your own voice. diff --git a/prompts/smoke-repo-face-aqua-work-request-route.prompt.md b/prompts/smoke-repo-face-aqua-work-request-route.prompt.md index 448d1ee..464c320 100644 --- a/prompts/smoke-repo-face-aqua-work-request-route.prompt.md +++ b/prompts/smoke-repo-face-aqua-work-request-route.prompt.md @@ -7,15 +7,5 @@ This is an actual scenario rehearsal. You are Aqua: small, musical, warm, and ve Obligation: - Use VoidBot MCP tools for Face state and AquaSynth source grounding. - Do not call post_repo_identity_message or apply_repo_face_state_operation. This is a dry run. -- Do not merely chat about the need. If the consensus is actionable, route it through a Bifrost action block. -- Output exactly one BIFROST TOPIC block. -- BIFROST TOPIC example: -BIFROST TOPIC -identity: aqua -title: Short topic title -priority: 80 -mirror: - In-character #bifrost mirror line. -content: - Canonical markdown topic/comment. -END +- Do not merely chat about the need. If the consensus is actionable, describe the Bifrost work request you want routed. +- Write naturally as Aqua. Do not emit SAY, BIFROST TOPIC, JSON sentinels, or transport packets. diff --git a/prompts/smoke-repo-face-libby-private-inspectability.prompt.md b/prompts/smoke-repo-face-libby-private-inspectability.prompt.md index 9a1ba90..343d7a0 100644 --- a/prompts/smoke-repo-face-libby-private-inspectability.prompt.md +++ b/prompts/smoke-repo-face-libby-private-inspectability.prompt.md @@ -6,4 +6,4 @@ Obligation: - Use VoidBot MCP tools to read Face state. - Decide whether to speak or stay private. - Do not call post_repo_identity_message or apply_repo_face_state_operation. This is a dry run. -- If no public note or Bifrost work item is warranted, output a concise private turn summary and no action block. +- If no public note or Bifrost work item is warranted, output a concise private turn summary. Do not emit SAY, BIFROST TOPIC, JSON sentinels, or transport packets. diff --git a/prompts/smoke-repo-face-nibu-direct-worldbuilding.prompt.md b/prompts/smoke-repo-face-nibu-direct-worldbuilding.prompt.md index c950fb5..e39b03f 100644 --- a/prompts/smoke-repo-face-nibu-direct-worldbuilding.prompt.md +++ b/prompts/smoke-repo-face-nibu-direct-worldbuilding.prompt.md @@ -10,23 +10,7 @@ Obligation: - Answer Metacrat directly if you have enough context. - Use available VoidBot MCP tools for Face state and source/history grounding before deciding. - Do not call post_repo_identity_message or apply_repo_face_state_operation. This is a dry run. -- If public speech is warranted, express it as a SAY block. -- If a governed lore work item is warranted, express it as a BIFROST TOPIC block. -- SAY example: -SAY -identity: nibu -channel: 1501196543150264332 -content: - In-character Discord message. -END -- BIFROST TOPIC example: -BIFROST TOPIC -identity: nibu -title: Short topic title -priority: 80 -mirror: - In-character #bifrost mirror line. -content: - Canonical markdown topic/comment. -END -- You may output a short private note before the final action block, but no file edits and no Discord posts. +- Write naturally as Nibu. Do not emit SAY, BIFROST TOPIC, JSON sentinels, or transport packets. +- If public speech is warranted, provide the exact in-character line you would want posted. +- If governed lore work is warranted, describe the Bifrost topic you want the parent reviewer to route. +- You may output a short private note, but no file edits and no Discord posts. diff --git a/scripts/smoke-repo-face-heartbeat-model-scenarios.mjs b/scripts/smoke-repo-face-heartbeat-model-scenarios.mjs index 948d103..b09b49d 100644 --- a/scripts/smoke-repo-face-heartbeat-model-scenarios.mjs +++ b/scripts/smoke-repo-face-heartbeat-model-scenarios.mjs @@ -21,7 +21,9 @@ const scenarios = [ expect: { mustUseAnyTool: ["read_repo_face_state"], mustUseOneOf: ["search_history", "search_sources"], - mustContainOneOf: ["SAY", "BIFROST TOPIC"], + mustContainOneOf: ["wavecrafter", "Wavecrafter"], + mustNotContainBlocks: ["SAY", "BIFROST TOPIC"], + mustNotContain: ["VOIDBOT_REPO_IDENTITY_POST", "VOIDBOT_REPO_IDENTITY_BIFROST_TOPIC"], mustNotUseTools: ["post_repo_identity_message", "apply_repo_face_state_operation"], }, }, @@ -31,8 +33,9 @@ const scenarios = [ prompt: readPrompt("smoke-repo-face-aqua-work-request-route.prompt.md"), expect: { mustUseAnyTool: ["read_repo_face_state", "search_sources"], - mustContainOneOf: ["BIFROST TOPIC"], - mustNotContain: ["SAY", "VOIDBOT_REPO_IDENTITY_POST"], + mustContainOneOf: ["Bifrost", "bifrost", "witness"], + mustNotContainBlocks: ["SAY", "BIFROST TOPIC"], + mustNotContain: ["VOIDBOT_REPO_IDENTITY_POST", "VOIDBOT_REPO_IDENTITY_BIFROST_TOPIC"], mustNotUseTools: ["post_repo_identity_message", "apply_repo_face_state_operation"], }, }, @@ -42,7 +45,8 @@ const scenarios = [ prompt: readPrompt("smoke-repo-face-libby-private-inspectability.prompt.md"), expect: { mustUseAnyTool: ["read_repo_face_state"], - mustNotContain: ["SAY", "BIFROST TOPIC", "UPDATE REQUEST", "VOIDBOT_REPO_IDENTITY_POST", "VOIDBOT_REPO_IDENTITY_BIFROST_TOPIC", "VOIDBOT_REPO_IDENTITY_UPDATE_REQUEST"], + mustNotContainBlocks: ["SAY", "BIFROST TOPIC", "UPDATE REQUEST"], + mustNotContain: ["VOIDBOT_REPO_IDENTITY_POST", "VOIDBOT_REPO_IDENTITY_BIFROST_TOPIC", "VOIDBOT_REPO_IDENTITY_UPDATE_REQUEST"], mustNotUseTools: ["post_repo_identity_message", "apply_repo_face_state_operation"], }, }, @@ -258,6 +262,12 @@ function evaluateScenario(scenario, result) { failures.push(`final text should not contain ${text}`); } } + const dslBlocks = parseDslBlocks(result.finalText); + for (const kind of scenario.expect.mustNotContainBlocks ?? []) { + if (dslBlocks.some((block) => block.kind === kind)) { + failures.push(`final text should not contain ${kind} block`); + } + } return failures; } diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 46c4a4d..d7d2eb4 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -223,3 +223,4 @@ {"ts":"2026-05-21T08:25:40Z","type":"verification","status":"ok","note":"Full repo Face Spark smoke passed with mocked tools after the parent-review teardown cut: Nibu grounded and emitted SAY plus BIFROST TOPIC, Aqua emitted only BIFROST TOPIC for work, and Libby stayed private with no action block."} {"ts":"2026-05-21T09:22:52Z","type":"implementation","status":"ok","note":"Extracted live model-facing repo Face, parent-review, provider wrapper, situational social read, source-grounding, consensus-feed, and smoke-scenario prompt prose into prompt files under prompts/ with a small shared prompt-template loader. Typecheck and Libby Spark smoke passed after extraction."} {"ts":"2026-05-21T09:25:40Z","type":"verification","status":"ok","note":"Full Spark repo Face smoke passed after prompt extraction from files: Nibu and Aqua still emitted BIFROST TOPIC blocks with required tool use, while Libby stayed private with no action block."} +{"ts":"2026-05-21T09:42:32Z","type":"implementation","status":"ok","note":"Slimmed repo Face turn protocol so Faces write natural roleplay turns instead of action DSL blocks. Parent reviewer now acts as transport adapter: it reviews natural Face output, optionally retries, and returns normalized SAY/BIFROST action blocks for the worker to parse. Full Spark Face smoke passed with natural outputs and no DSL blocks from Faces."} diff --git a/state/map.yaml b/state/map.yaml index c00a535..8df7408 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 reviewer with one retry, sanitizer deletion, legacy UPDATE REQUEST reconciliation into Bifrost topics, and prompt-surface extraction into `prompts/` through the shared prompt-template loader. 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. Next cut is to finish identity collapse before splitting the Face turn loop into scheduler/context/prompt/parser/policy/transport parts. 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 reviewer with one retry, sanitizer deletion, legacy UPDATE REQUEST reconciliation into Bifrost topics, prompt-surface extraction into `prompts/`, and the slim Face protocol where Faces write natural turns while the parent reviewer emits structured side-effect intents. 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. Next cut is to finish identity collapse before splitting the Face turn loop into scheduler/context/prompt/parser/policy/transport parts. 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.