Keep raw Face drafts out of public speech

This commit is contained in:
Meta Vens 2026-06-03 01:42:21 +01:00
commit ccd3807705
4 changed files with 43 additions and 49 deletions

View file

@ -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 {

View file

@ -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"
}

View file

@ -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."}

View file

@ -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.