From ccd380770594be9e30af53db4133bd3c815e7605 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 3 Jun 2026 01:42:21 +0100 Subject: [PATCH] Keep raw Face drafts out of public speech --- apps/worker/src/index.ts | 80 ++++++++++++++++------------------- state/agent-swarm-paused.json | 7 ++- state/evidence.jsonl | 1 + state/map.yaml | 4 +- 4 files changed, 43 insertions(+), 49 deletions(-) diff --git a/apps/worker/src/index.ts b/apps/worker/src/index.ts index 663145d..a11fa94 100644 --- a/apps/worker/src/index.ts +++ b/apps/worker/src/index.ts @@ -737,9 +737,7 @@ function routeRepoFaceInterpretedOutput( interpretation: RepoFaceParentInterpretation, input: { job: JobRecord; faceOutputText: string }, ): ProviderResponse { - const rawOutputText = - interpretation.routedOutput?.trim() || - stripRepoIdentityPostIntents(fitDiscordMessage(response.outputText ?? response.summary)); + const rawOutputText = interpretation.routedOutput?.trim() ?? ""; const outputText = normalizeInterpretedRepoFaceSpeechDestinations(rawOutputText, input); const summary = outputText || "Repo Face parent interpreter routed no public or governed action."; return { @@ -1174,6 +1172,9 @@ async function postRepoIdentityIntent(job: JobRecord, intent: RepoIdentityPostIn if (/\S(?:\.\.\.|…)$/.test(content)) { throw new Error(`Repo identity ${identity.id} SAY content appears mechanically truncated.`); } + if (isNonPublicRepoIdentitySpeech(content)) { + throw new Error(`Repo identity ${identity.id} SAY content is not public speech.`); + } const contentFile = await writeBifrostPayloadFile(job, `${identity.id}-discord-post.md`, content); const posted = runBifrostBridge([ "discord-post", @@ -1286,54 +1287,47 @@ function parseRepoIdentityPostIntents(finalResponse: string): RepoIdentityPostIn } } - if (intents.length === 0) { - const draftSpeech = parseRepoIdentityDraftSpeechIntent(finalResponse); - if (draftSpeech) { - intents.push({ content: draftSpeech }); - } - } - return intents; } -function parseRepoIdentityDraftSpeechIntent(finalResponse: string): string | undefined { - const lines = finalResponse.split(/\r?\n/); - for (let index = 0; index < lines.length; index += 1) { - const match = lines[index]?.match(/^\s*Would say:\s*(.+?)\s*$/i); - if (!match) { - continue; - } - - const contentLines = [match[1] ?? ""]; - for (const following of lines.slice(index + 1)) { - const trimmed = following.trim(); - if (!trimmed) { - break; - } - if (/^(Private thought|Private summary|What should stick|State note|Would say):/i.test(trimmed)) { - break; - } - contentLines.push(trimmed); - } - - const content = stripPairedQuotes(contentLines.join("\n").trim()); - if (content) { - return content; - } +function isNonPublicRepoIdentitySpeech(value: string): boolean { + const trimmed = value.trim(); + if (!trimmed) { + return true; } - - return undefined; + if (isSingleMarkdownFence(trimmed)) { + return true; + } + const unfenced = stripSingleMarkdownFence(trimmed); + const normalized = unfenced + .toLowerCase() + .replace(/[.!?]+$/g, "") + .trim(); + return [ + "nothing", + "nothing right now", + "nothing public", + "nothing in aquarium", + "stay private", + "no public speech", + ].includes(normalized); } -function stripPairedQuotes(value: string): string { +function stripSingleMarkdownFence(value: string): string { const trimmed = value.trim(); - if ( - (trimmed.startsWith("\"") && trimmed.endsWith("\"")) || - (trimmed.startsWith("'") && trimmed.endsWith("'")) - ) { - return trimmed.slice(1, -1).trim(); + if (!isSingleMarkdownFence(trimmed)) { + return trimmed; } - return trimmed; + const singleLine = trimmed.match(/^`([^`\r\n]+)`$/); + if (singleLine) { + return singleLine[1].trim(); + } + const block = trimmed.match(/^```[A-Za-z0-9_-]*\s*\r?\n([\s\S]*?)\r?\n```$/); + return block ? block[1].trim() : trimmed; +} + +function isSingleMarkdownFence(value: string): boolean { + return /^`[^`\r\n]+`$/.test(value) || /^```[A-Za-z0-9_-]*\s*\r?\n[\s\S]*?\r?\n```$/.test(value); } function parseRepoIdentityIdFromRequestMessageId(requestMessageId: string | undefined): string | undefined { diff --git a/state/agent-swarm-paused.json b/state/agent-swarm-paused.json index d572f5e..4e2bbc8 100644 --- a/state/agent-swarm-paused.json +++ b/state/agent-swarm-paused.json @@ -1,6 +1,5 @@ { - "paused": false, - "reason": "Resumed by operator request.", - "pausedAt": "2026-05-24T09:14:06.6535422Z", - "resumedAt": "2026-05-24T10:19:00Z" + "paused": true, + "reason": "Operator reported repo Faces saying nothing or speaking in code blocks; pause while Face prompt and interpreter logs are inspected.", + "updatedAt": "2026-06-03T00:36:00.0000000Z" } diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 96ef62c..de16272 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -325,3 +325,4 @@ {"ts":"2026-06-02T16:50:14Z","type":"prompt","status":"ok","note":"Persona prompts now frame Odin as the all-seer and Verse discovery as early connected-Colossus nerve-sense; repo Face turns include a bounded poem that orients agents toward live CultMesh/Eve surfaces without replacing source-grounded tool discipline."} {"ts":"2026-06-02T16:54:34Z","type":"prompt","status":"ok","note":"Recast the Odin/Verse poem in the Face turn prompt toward a translated-Edda register while preserving the authority lesson: Odin witnesses Verse surfaces, local providers retain ownership, and agents touch commands only by explicit boundary."} {"ts":"2026-06-02T16:56:33Z","type":"prompt","status":"ok","note":"After reading public-domain Poetic Edda translations around Odin in Havamal, Vafthruthnismol, and Grimnismol, revised the Odin/Verse prompt poem toward Eddic devices: wisdom-travel formulae, counsel/riddle address, Hugin/Munin memory, rune/rede language, measured gifts, and alliterative half-lines."} +{"ts":"2026-06-03T00:41:06Z","type":"implementation","status":"ok","note":"Paused repo Face swarm after operator reported silence/code-block public posts. Root cause: worker still allowed raw child Would say fallback and interpreter route fallback to write public speech; public mouth is now owned only by parent Interpreter SAY blocks, with transport guard rejecting full-message markdown fences and silence markers."} diff --git a/state/map.yaml b/state/map.yaml index 3f83975..6489d36 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -251,5 +251,5 @@ rejected_paths: reason: sleep/distillation that collapses concrete claims into generic abstraction creates memory nonsense and then forces later cleanup compensators current_status: - summary: VoidBot's bot/worker/provider/RAG/Postgres/Qdrant/CultCache structure is live, but the repo Face swarm is paused after unintelligible live speech. GameCult Local Orchestrator remains the single pulse owner, and the repo Face heartbeat path obeys `state/agent-swarm-paused.json`. Bifrost work routing remains paused by default. - next_action: Keep the swarm paused while inspecting projected Face prompts and model-output logs. Verify that the new semantic curiosity graph reaches the state projector, that human clarity pressure still supersedes stale attractors, and that topic gravity becomes curiosity/boredom/pivot pressure instead of another repetition loop before resuming ordinary orchestrator pulses. + summary: VoidBot's bot/worker/provider/RAG/Postgres/Qdrant/CultCache structure is live, but the repo Face swarm is paused after operator-reported silence/code-block public posts. The immediate fault was an obsolete worker fallback that let raw child `Would say:` text write the public mouth when the parent Interpreter did not produce a normalized `SAY`; that fallback has been cut, and transport now rejects full-message markdown fences plus silence markers before Discord posting. GameCult Local Orchestrator remains the single pulse owner, and the repo Face heartbeat path obeys `state/agent-swarm-paused.json`. Bifrost work routing remains paused by default. + next_action: Keep the swarm paused until a dry/mock repo Face turn proves public speech is owned only by parent Interpreter `SAY` blocks, raw child silence/code-fence text cannot bypass the Interpreter, and the repaired worker build is the running worker. Then resume ordinary orchestrator pulses cautiously and watch first live posts.