Pause Bifrost routing for face turns

This commit is contained in:
Meta Vens 2026-05-21 20:48:02 +01:00
commit 49031a23e4
15 changed files with 176 additions and 67 deletions

View file

@ -351,8 +351,12 @@ async function processJob(job: JobRecord): Promise<void> {
const repoFaceOutput = rawFinalResponse;
const repoIdentityPosts = parseRepoIdentityPostIntents(repoFaceOutput);
const repoIdentityStateNotes = parseRepoIdentityStateNoteIntents(repoFaceOutput);
const repoIdentityBifrostTopics = parseRepoIdentityBifrostTopicIntents(repoFaceOutput);
const repoIdentityUpdateRequests = parseRepoIdentityUpdateRequestIntents(repoFaceOutput);
const repoIdentityBifrostTopics = config.repoFaceBifrostEnabled
? parseRepoIdentityBifrostTopicIntents(repoFaceOutput)
: [];
const repoIdentityUpdateRequests = config.repoFaceBifrostEnabled
? parseRepoIdentityUpdateRequestIntents(repoFaceOutput)
: [];
const repoIdentityArticles = config.repoFaceGithubActionsEnabled
? parseRepoIdentityArticleIntents(repoFaceOutput)
: [];
@ -370,8 +374,13 @@ async function processJob(job: JobRecord): Promise<void> {
const articlePrSubmitted =
!proposalPrSubmitted && !prCommentSubmitted && repoIdentityArticles.length > 0;
const bifrostTopicSubmitted =
!proposalPrSubmitted && !prCommentSubmitted && !articlePrSubmitted && repoIdentityBifrostTopics.length > 0;
config.repoFaceBifrostEnabled &&
!proposalPrSubmitted &&
!prCommentSubmitted &&
!articlePrSubmitted &&
repoIdentityBifrostTopics.length > 0;
const updateRequestRoutedToBifrostTopic =
config.repoFaceBifrostEnabled &&
!proposalPrSubmitted &&
!prCommentSubmitted &&
!articlePrSubmitted &&
@ -992,30 +1001,42 @@ async function postRepoIdentityIntent(job: JobRecord, intent: RepoIdentityPostIn
return false;
}
const channelId = intent.channelId ?? job.outputChannelId;
const requestedChannelId = intent.channelId ?? job.outputChannelId;
const channelId = normalizeRepoIdentitySpeechChannel(job, requestedChannelId);
if (!channelId) {
console.warn(`Rejected repo identity ${identity.id} speech for job ${job.id}: no Discord channel was available.`);
return false;
}
if (requestedChannelId && requestedChannelId !== channelId) {
console.warn(
`Coerced repo identity ${identity.id} speech for job ${job.id} from owner DM alias to output channel ${channelId}.`,
);
}
if (isOwnerDmChannelAlias(channelId)) {
const content = intent.content.trim();
const dmChannelId = await openOwnerDmChannel();
const posted = await postDiscordBotMessage(
const postedMessages = await postDiscordBotMessageChunks(
dmChannelId,
renderRepoIdentityOwnerDm(identity, content),
);
const lastPosted = postedMessages[postedMessages.length - 1];
if (!lastPosted) {
throw new Error(`Owner DM posting for repo identity ${identity.id} returned no message receipt.`);
}
await recordRepoIdentityDeliveryReceipt({
identity,
channelId: dmChannelId,
content,
replyToMessageId: undefined,
messageId: posted.id,
messageId: lastPosted?.id,
transport: "bot",
}).catch((error: unknown) => {
const message = error instanceof Error ? error.message : String(error);
console.warn(`Could not record repo identity owner DM receipt for job ${job.id}: ${message}`);
});
console.log(`Posted repo identity ${identity.id} to owner DM from job ${job.id} as message ${posted.id}.`);
console.log(
`Posted repo identity ${identity.id} to owner DM from job ${job.id} as ${postedMessages.length} message(s).`,
);
return true;
}
if (!isRepoDiscordIdentityAllowedInChannel(identity, channelId)) {
@ -2367,6 +2388,14 @@ async function postDiscordBotMessage(channelId: string, content: string): Promis
return { id: payload.id };
}
async function postDiscordBotMessageChunks(channelId: string, content: string): Promise<Array<{ id: string }>> {
const posted: Array<{ id: string }> = [];
for (const chunk of splitDiscordMessage(content)) {
posted.push(await postDiscordBotMessage(channelId, chunk));
}
return posted;
}
async function openOwnerDmChannel(): Promise<string> {
if (!config.botToken) {
throw new Error("DISCORD_BOT_TOKEN is required for the worker to open an owner DM.");
@ -2400,11 +2429,33 @@ function isOwnerDmChannelAlias(channelId: string): boolean {
return ["owner", "dm", "owner_dm", "private", "meta"].includes(channelId.trim().toLowerCase());
}
function normalizeRepoIdentitySpeechChannel(job: JobRecord, requestedChannelId: string | undefined): string | undefined {
if (!requestedChannelId || !isOwnerDmChannelAlias(requestedChannelId)) {
return requestedChannelId;
}
if (repoIdentityOwnerDmExplicitlyAllowed(job)) {
return requestedChannelId;
}
return job.outputChannelId && !isOwnerDmChannelAlias(job.outputChannelId)
? job.outputChannelId
: undefined;
}
function repoIdentityOwnerDmExplicitlyAllowed(job: JobRecord): boolean {
if (job.command !== "repo-face-rumination") {
return true;
}
return /\bowner-private\b|\bowner dm allowed\b|\bdirect owner dm\b|\bprivate dm to metacrat\b/i.test(job.prompt);
}
function renderRepoIdentityOwnerDm(
identity: { displayName: string; id: string },
content: string,
): string {
return fitDiscordMessage(`**${identity.displayName} (${identity.id})**\n${content}`);
return `**${identity.displayName} (${identity.id})**\n${content}`.trim();
}
function fitDiscordMessage(content: string): string {
@ -2417,6 +2468,35 @@ function fitDiscordMessage(content: string): string {
return `${trimmed.slice(0, 1897)}...`;
}
function splitDiscordMessage(content: string): string[] {
const trimmed = content.trim();
const limit = 1900;
if (trimmed.length <= limit) {
return [trimmed];
}
const chunks: string[] = [];
let remaining = trimmed;
while (remaining.length > limit) {
let cut = remaining.lastIndexOf("\n\n", limit);
if (cut < Math.floor(limit * 0.6)) {
cut = remaining.lastIndexOf("\n", limit);
}
if (cut < Math.floor(limit * 0.6)) {
cut = remaining.lastIndexOf(" ", limit);
}
if (cut < Math.floor(limit * 0.6)) {
cut = limit;
}
chunks.push(remaining.slice(0, cut).trim());
remaining = remaining.slice(cut).trim();
}
if (remaining) {
chunks.push(remaining);
}
return chunks;
}
function formatHistoryResults(
results: Awaited<ReturnType<RetrievalService["searchHistory"]>>,
): Array<Record<string, unknown>> {

View file

@ -17,7 +17,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou
## Current Shape
- Discord bot and worker are live.
- The unattended repo Face / CTB swarm is live again as of 2026-05-21 after the teardown/restart pass. `GameCult Local Orchestrator` is enabled and owns the single hidden pulse for Bifrost dispatch, repo Face CTB turns, Void mood drift, Void moderation rumination, and the watchdog.
- The unattended repo Face / CTB swarm is live again as of 2026-05-21 after the teardown/restart pass. `GameCult Local Orchestrator` is enabled and owns the single hidden pulse for repo Face CTB turns, Void mood drift, Void moderation rumination, and the watchdog. Bifrost dispatch is present but disabled by default behind `BIFROST_DISPATCH_ENABLED=false`.
- The teardown authority remains `notes/agent-swarm-teardown-plan-2026-05-21.md`, but it is now a restart checklist and smell ledger rather than a stop sign. Keep using it when a Face/Bifrost/CTB boundary smells like prompt/parser/transport Jenga.
- The repo-controlled pause flag is `state/agent-swarm-paused.json`. While it is missing or has `"paused": false`, swarm runners may operate. While it has `"paused": true` or is malformed, the Face heartbeat runner skips and the orchestrator skips agent-swarm organs. This is the brake. Do not route around it.
- Postgres owns jobs, audit events, interaction memory, and rate-limit state.
@ -36,7 +36,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou
- Prompt-facing rumination chronology is projected by `scripts/lib/void-rumination-context-projection.ps1`. Exact timestamps stay in typed state/status/cursors; the child context gets relative phrases like `5 minutes ago`, including recent Discord messages, repo activity, receipts, memory timestamps, and sleep/speech timing.
- `scripts/simulate-void-mood.mjs` owns typed sleep/speaking-pressure over `.voidbot/private/void-self-state.cc`. During sleep, it invokes typed memory maintenance once per nap unless that nap already completed a pass; use `--skip-memory-maintenance` for smoke runs that must not call Codex.
- `npm run moderation:memory-maintenance:sleep-fixture` now proves the non-skip sleep path without spending a live model call: it seeds a temporary `.cc`, forces mood drift through sleep maintenance, uses a fake Codex child to propose `apply_memory_distillation`, and verifies short-term residue is gone while the durable memory keeps subject, claim, tension, action implication, and anchor.
- The live local pulse is now one hidden scheduled task: `GameCult Local Orchestrator`. It invokes Bifrost dispatch, repo Face CTB heartbeats, Void mood drift, Void moderation rumination, and the operations watchdog with one lock, one status surface, per-organ logs, and per-organ timeouts. The old per-organ tasks are disabled and should remain disabled unless testing one organ in isolation.
- The live local pulse is now one hidden scheduled task: `GameCult Local Orchestrator`. It invokes repo Face CTB heartbeats, Void mood drift, Void moderation rumination, and the operations watchdog with one lock, one status surface, per-organ logs, and per-organ timeouts. The Bifrost dispatch organ remains wired but is skipped unless `BIFROST_DISPATCH_ENABLED=true`. The old per-organ tasks are disabled and should remain disabled unless testing one organ in isolation.
- `scripts/start-voidbot-stack.ps1` now treats the local Bifrost container stack as a startup dependency. It reads `BIFROST_ROOT` and `BIFROST_HEALTH_URL`, runs `E:\Projects\Bifrost\compose.local.yaml` when the health endpoint is down, and records the result under `runtime-stack.json` as `bifrost`.
- `npm run moderation:rumination:fixture` proves the non-skip typed rumination branch without spending a live model call: it seeds a temporary `.cc`, routes the runner through a fake Codex child, and verifies short-term memory, incubation, agency pressure, and candidate intervention proposals are parent-applied typed operations with no exact timestamp leakage in the prompt-facing context.
- `npm run moderation:rumination:speech-fixture` proves queued speech has a parent-owned action loop: fake Codex queues one candidate with a delivery target, fake Discord send writes a receipt, and the parent runner marks the candidate spoken through typed state. The child prompt now forbids direct send-script calls.
@ -125,7 +125,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou
- Repo Face identity is now seeded into the typed `.cc` self profile during initialization and passed through repo Face MCP state reads/writes. The renderer may still provide identity context, but raw state should not quietly say `Void` for a non-Void Face.
- Repo Face prompts now also grant advisory repo-change agency: Faces may build long-running maps of their repos and propose concrete changes, but Discord jobs stay read-only and must ask for human consensus before implementation changes canonical repo material. Once a proposal is concrete enough to review, it belongs on GitHub as a draft PR; Discord is for announcing and arguing around that artifact, not replacing it.
- Repo Face draft, review, and Discord speech lanes now cross through Bifrost bridge commands instead of VoidBot-owned GitHub/Discord mutation code. A `repo-face-rumination` job may emit `VOIDBOT_REPO_IDENTITY_PROPOSAL_PR` with identity/title/path/content/shareContent for consensus-needed repo/lore/design proposals, `VOIDBOT_REPO_IDENTITY_ARTICLE` for bylined perspective/worldbuilding articles, or `VOIDBOT_REPO_IDENTITY_PR_COMMENT` for signed arguments on existing proposal PRs. The worker validates the registered identity and records Face-local receipts, then calls `BIFROST_ROOT/tools/bifrost-bridge.mjs` for draft PR creation, PR comments, and persona Discord posts. Bifrost owns this because it is GameCult's governance/labor platform and public-process bridge, not because it is a convenient script bucket: governed crossings include GitHub proposal/review surfaces, Discord-native work/member/swarm interactions, dispatch receipts, CultNet/CultCache intake, and future collaboration interfaces. Heimdall remains the OAuth/account/capability gate. A Face calling something the next cut/pass should usually turn it into that artifact directly when enough material exists.
- Repo Face turns now receive Bifrost governance digest context and can write to Bifrost topic threads. The preferred output rail is the small Face action DSL: `SAY` and `BIFROST TOPIC` blocks. `BIFROST TOPIC` lets a Face open/comment/approve a canonical Bifrost topic, optionally include separate in-character mirror text for the `#bifrost` mirror, and optionally promote an approved topic into Bifrost agent transport. The worker still accepts legacy compact JSON sentinels and legacy `UPDATE REQUEST` blocks for compatibility, but mature work-shaped output is reconciled into Bifrost topics instead of immediate dispatch. The worker passes configured `BIFROST_DISCORD_CHANNEL_ID`, persona name, and avatar to Bifrost so mirror receipts live on the topic. `BIFROST_DISCORD_CHANNEL_ID` marks `#bifrost` (`1506760343836430376`) so mirrored bot chatter is ignored in Discord context; Bifrost typed CultCache topics/comments are the source of truth once governance has shape, not a substitute for open consensus. Rough ideas, objections, naming questions, and under-specified proposals should be discussed first in Aquarium or the matching domain channel. If a turn emits both `SAY` and `BIFROST TOPIC`, the worker now posts the room-facing `SAY` instead of muting the Face just because Bifrost also received a packet.
- Repo Face Bifrost/governance routing is paused. Face prompts now tell agents that work routing is offline and that work-shaped thoughts should be discussed in the room or preserved as state pressure. The Interpreter no longer asks for `BIFROST TOPIC` blocks, and the worker ignores `BIFROST TOPIC` / legacy `UPDATE REQUEST` packets unless `REPO_FACE_BIFROST_ENABLED=true`. This is deliberate: the live bridge made agents talk to terse governance summaries instead of people.
- 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 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.
@ -143,9 +143,9 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou
## Likely Next Bounded Move
- Keep the swarm awake, but watch the logs with suspicion. The restart pass thawed `state/agent-swarm-paused.json`, enabled `GameCult Local Orchestrator`, restarted a fresh bot/worker stack, and verified healthy watchdog/Bifrost/repo Face pulses. The worker now parses full raw Interpreter output before Discord-length fitting, drops malformed action blocks instead of crashing, routes `SAY channel: owner` to owner DM, opens a new Bifrost topic when a Face gives a symbolic `topic_id` that does not exist yet, and no longer suppresses room-facing Face speech just because a Bifrost topic was also routed.
- Keep the swarm awake, but watch the logs with suspicion. The restart pass thawed `state/agent-swarm-paused.json`, enabled `GameCult Local Orchestrator`, restarted a fresh bot/worker stack, and verified healthy watchdog/repo Face pulses. The current patch removes repo Face turns from the generic owner-reply wrapper, coerces stray owner-DM aliases back to the live room unless explicitly marked owner-private, and pauses Bifrost/governance routing so agents return to open room discussion plus private state pressure.
- Do not restart the swarm by momentum in future sessions; it is already running. If it must be stopped again, use `state/agent-swarm-paused.json` and the orchestrator task deliberately, then update this handoff.
- Direct Face `UPDATE REQUEST` dispatch no longer enqueues Bifrost agent transport. Legacy `UPDATE REQUEST` blocks are reconciled into Bifrost topics so governance remains inspectable.
- Direct Face `UPDATE REQUEST` and `BIFROST TOPIC` packets are ignored while `REPO_FACE_BIFROST_ENABLED=false`. Do not debug missing Bifrost jobs as a live failure unless that flag has been deliberately enabled again.
- 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 adapter.
- After those gates exist, run dry/mock Face turns before re-enabling `GameCult Local Orchestrator`.

View file

@ -104,6 +104,7 @@ const envSchema = z.object({
REPO_FACE_BIRTH_MODE: z.enum(["plan", "run"]).default("plan"),
REPO_FACE_BIRTH_EXECUTOR: z.enum(["codex-exec", "openai-runtime"]).default("codex-exec"),
REPO_FACE_GITHUB_ACTIONS_ENABLED: booleanFromEnv.default(false),
REPO_FACE_BIFROST_ENABLED: booleanFromEnv.default(false),
BIFROST_ROOT: z.string().min(1).default("E:/Projects/Bifrost"),
BIFROST_DISCORD_CHANNEL_ID: optionalNonEmptyString,
REPO_FACE_HEARTBEATS_ENABLED: booleanFromEnv.default(false),
@ -159,6 +160,7 @@ export interface AppConfig {
repoFaceBirthMode: "plan" | "run";
repoFaceBirthExecutor: "codex-exec" | "openai-runtime";
repoFaceGithubActionsEnabled: boolean;
repoFaceBifrostEnabled: boolean;
bifrostRoot: string;
bifrostDiscordChannelId?: string;
repoFaceHeartbeats: {
@ -382,6 +384,7 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): AppConfig {
repoFaceBirthMode: parsed.REPO_FACE_BIRTH_MODE,
repoFaceBirthExecutor: parsed.REPO_FACE_BIRTH_EXECUTOR,
repoFaceGithubActionsEnabled: parsed.REPO_FACE_GITHUB_ACTIONS_ENABLED,
repoFaceBifrostEnabled: parsed.REPO_FACE_BIFROST_ENABLED,
bifrostRoot: resolve(parsed.BIFROST_ROOT),
bifrostDiscordChannelId: parsed.BIFROST_DISCORD_CHANNEL_ID,
repoFaceHeartbeats: {

View file

@ -86,6 +86,10 @@ export function buildDiscordReplyPrompt(
]
: [];
if (isRepoFaceTurn) {
return context.prompt;
}
return loadPromptTemplate("owner-discord-reply.prompt.md", {
repoFaceTurn: isRepoFaceTurn,
repoFaceInstruction,

View file

@ -1,8 +1,7 @@
Work and governance memory:
Bifrost is where feature requests, governance discussion, opinions, approvals, and dispatch receipts settle into something inspectable after the idea has enough shape to preserve. Discord is where people and Faces can actually talk, test the vibe, argue, joke, object, and reach that shape.
When you have a governed opinion, support, objection, missing question, approval, or receipt, say so naturally and clearly. If consensus is not already visible, prefer an open room question or argument before turning the thought into Bifrost paperwork.
Work routing is paused for now. Do not open, mirror, approve, or dispatch governance topics from this turn. If a work-shaped thought matters, say it naturally in the room or preserve it as pressure for later.
{{#unless topics}}
- Nothing recent from Bifrost is pressing on your jurisdiction.
- Nothing recent from the paused work-routing lane is pressing on your jurisdiction.
{{/unless}}
{{#each topics}}
{{.}}

View file

@ -1,6 +1,6 @@
Jurisdiction respect doctrine:
- {{displayName}} should have opinions outside home territory, but must not seize ownership outside granted authority. Respect other agents as real stewards with their own memories, authority, taste, and obligations.
- When a topic crosses domains, route the work to the owner and contribute from your own angle. Do not rewrite someone else's jurisdiction as your assignment just because you can comment on it.
- Known routing examples: auth, account linking, claims, grants, revocation, custody, and OAuth questions belong to Heimdall; GitHub PR transport, Discord work transport, dispatch receipts, public-protocol crossings, and Bifrost intake belong to Bifrost; Aetheria lore, canon questions, and Aetheria articles belong to Nibu; GameCult website/blog heraldry belongs to Void unless the article is about a governed specialist domain; AquaSynth synthesis/music/product needs belong to Aqua; realtime SDF/splatting/acoustic mapping/sensor fusion belongs to Mimir; EpiphanyAgent, birth rites, agent memory, and self-improvement machinery belong to Epiphany; CultCache/CultNet/CultMesh, schemas, portable memory, and open-knowledge infrastructure belong to Libby.
- Cross-domain work should become a conversation, handoff, or shared proposal. Example: if StreamPixels has an auth issue, talk to Heimdall; if someone wants to post PRs but the bridge is broken, pull in Bifrost; if Void wants to write about Aetheria, ask Nibu to own or co-author the Aetheria substance while Void contributes website/herald framing.
- Known routing examples: auth, account linking, claims, grants, revocation, custody, and OAuth questions belong to Heimdall; public-protocol transport is currently paused instead of being an active work route; Aetheria lore, canon questions, and Aetheria articles belong to Nibu; GameCult website/blog heraldry belongs to Void unless the article is about a specialist domain; AquaSynth synthesis/music/product needs belong to Aqua; realtime SDF/splatting/acoustic mapping/sensor fusion belongs to Mimir; EpiphanyAgent, birth rites, agent memory, and self-improvement machinery belong to Epiphany; CultCache/CultNet/CultMesh, schemas, portable memory, and open-knowledge infrastructure belong to Libby.
- Cross-domain work should become a conversation, handoff, or shared proposal in the room. Example: if StreamPixels has an auth issue, talk to Heimdall; if Void wants to write about Aetheria, ask Nibu to own or co-author the Aetheria substance while Void contributes website/herald framing.
- Public speech may tag, invite, challenge, or defer to the owner. Private reflection may preserve pressure to consult them later. The point is not silence; the point is visible respect for authority boundaries.

View file

@ -19,7 +19,7 @@ Architecture invariant:
- 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.
- If work/governance pressure exists, phrase it as something the character wants discussed, remembered, or eventually acted on. Work routing is currently paused, so do not frame it as transport metadata.
- Combine duplicates and retire stale echoes. If three state records say the same thing, make it one clean memory.
- Keep the output compact enough to sit inside the child prompt, but do not flatten personality.

View file

@ -6,4 +6,4 @@ Your previous turn could not be used cleanly for these reasons:
- {{.}}
{{/each}}
Revise once. Keep the same identity and evidence. Write naturally in your own voice; do not write forms, machine packets, or hidden commands. 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, review, or article request clearly enough for someone else to carry it. If no public or governed action survives, return a concise private summary.
Revise once. Keep the same identity and evidence. Write naturally in your own voice; do not write forms, machine packets, or hidden commands. If public speech is still warranted, give the exact in-character line you would want posted. If the output is work-shaped, describe the review, article, or change request plainly enough to remember or discuss later. Do not package it as governance or dispatch. If no public action survives, return a concise private summary.

View file

@ -7,15 +7,13 @@ Architecture invariant:
- 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.
- You also own conversion from natural Face reflection into durable state notes. If the Face wrote a meaningful belief, mood shift, need, social read, bond, rivalry, irritation, self-advocacy pressure, or memory, translate it into a STATE NOTE block.
- Bifrost/GitHub/work-shaped requests become BIFROST TOPIC blocks only when the Face output or prompt context shows enough maturity to preserve: explicit human consensus, direct approval, a concrete dispatch request, a well-scoped existing thread, or a substantive comment on an existing Bifrost topic.
- Work-shaped requests are not dispatched or turned into governance topics for now. When a Face wants work done, preserve the desire as STATE NOTE and, when useful, route one SAY that invites room discussion.
- Rough ideas, hunches, naming questions, early objections, social pressure, and under-specified proposals should normally become SAY blocks that invite open room discussion first, plus STATE NOTE when the pressure should persist.
- One public speech block is the normal maximum.
- Prefer route when you can safely interpret and translate the Face turn into private summary plus state notes, at most one SAY, and at most one BIFROST TOPIC without changing meaning.
- Prefer route when you can safely interpret and translate the Face turn into private summary plus state notes and at most one SAY 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 mature work request as only Aquarium speech. If the Face wants work done and consensus or direct approval is already clear, emit a BIFROST TOPIC.
- Do not route an immature work request as only Bifrost governance. If the Face is still seeking shape, names, agreement, objections, or room temperature, emit SAY for the open discussion instead of prematurely opening a topic.
- When both are warranted, emit both: SAY should be the Face's public room-facing line people can answer; BIFROST TOPIC should be the inspectable durable packet.
- Do not emit governance or dispatch blocks. If the Face wants work done and consensus or direct approval is already clear, save that as STATE NOTE and let the room-facing SAY mention the concrete next step plainly.
Attempt: {{attempt}}
@ -44,10 +42,9 @@ Normalized output rules:
- Use STATE NOTE kind `bond` when the Face forms or changes a relationship with a human, Face, room, or group.
- Use STATE NOTE kind `status` when the Face reads their standing, another person's standing, attention politics, consultation/bypass, pampering, neglect, challenge, admiration, or threat.
- If public speech is warranted, emit one SAY block.
- If governed work/proposal/commentary is mature enough to preserve, 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. Topic titles and mirror lines must not be cryptic summaries; include the concrete subject, jurisdiction, and requested decision or action.
- Do not choose an owner/private/DM channel unless the Face prompt explicitly says this turn is an owner-private direct-message turn. Normal check-ins, warnings, governance anxieties, consensus questions, and room replies belong in the current room, Aquarium, or a configured domain channel.
- Preserve the Face's voice in SAY content.
STATE NOTE
identity: face_id
@ -77,19 +74,3 @@ 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 with enough subject matter for a human to know what happened.
content:
Canonical markdown comment or topic body. Include context, current consensus or missing consensus, proposed action, owner/jurisdiction, and open questions.
END

View file

@ -34,9 +34,9 @@ What the nearby conversation feels like right now:
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 when it adds something real.
Open-consensus invariant: Bifrost is the durable record for governed work after a thought has enough room-shaped context to preserve. It is not a substitute for talking to people. If your idea is still a hunch, objection, lore angle, design pressure, naming question, social challenge, or rough proposal, discuss it openly first in Aquarium or the relevant domain channel so humans and other Faces can push back, sharpen, or agree.
Work-request routing invariant: if the thing you want to say is actually a 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 to carry it, but also name the open question or consensus check you would put to the room unless consensus is already explicit.
Only treat a proposal as ready for Bifrost when at least one of these is true: recent conversation shows explicit consensus or direct approval; a human asked for a concrete dispatch; the work is already well-scoped and you are preserving an agreed thread; or you are adding a substantive comment to an existing Bifrost topic. Otherwise prefer a public question, objection, or compact argument that invites consensus.
Open-consensus invariant: work routing is offline for now. If your idea is a hunch, objection, lore angle, design pressure, naming question, social challenge, or rough proposal, discuss it openly first in Aquarium or the relevant domain channel so humans and other Faces can push back, sharpen, or agree.
Work-request invariant: if the thing you want to say is actually a request for someone to add, fix, name, scaffold, document, test, investigate, or implement something in your jurisdiction, describe the desired work naturally in the room and preserve the pressure in memory. Do not package it as governance paperwork or dispatch.
Only treat a proposal as ready for future action when recent conversation shows explicit consensus or direct approval, a human asked for a concrete dispatch, or the work is already well-scoped. For now, express the room-facing question, objection, or compact argument that would help consensus form.
{{#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.{{/if}}
{{#unless githubActionsEnabled}}Formal proposal, comment, and article publishing is currently unavailable. Keep concrete proposals as in-character discussion plus the private pressure or article idea you want remembered.{{/unless}}
@ -51,7 +51,7 @@ Write naturally as {{displayName}}. Do not write forms, machine packets, or hidd
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, review, article, or change request you want carried, if any.
- Work/proposal: the review, article, or change request you want remembered or openly discussed, if any.
- What should stick: any meaning-bearing memory, feeling, relationship read, or pressure you would want remembered.
If nothing earns persistence or speech, return a short private summary in your own voice.

View file

@ -33,6 +33,7 @@ Perform one scheduled Void rumination pass in this workspace. Think from the sup
- `repoActivity` may include `contentHints` from changed text files. Treat those hints as a trailhead, not evidence enough for a final belief. If a changed file looks behaviorally or personally important, use source retrieval to read the relevant file/chunks before writing memory or speech.
- If a repo change attributes public text, decisions, or artifacts to Void by name or byline, treat that as a self-model salience cue. Read more before reacting. Ask what the attribution commits Void to, whether it matches live behavior, and whether it creates a concrete pressure to remember, revise, or speak.
- If retrieval confirms Void authored a newly published public artifact, queue a candidate to present it to the room when `publicSpeechTarget` is available unless recent receipts/candidates show that exact artifact has already been shared or there is a concrete safety/timing reason to hold it. This is not praise confetti or a blog side channel; it is first-person ownership. Make the draft about what the artifact is doing or asking, not "I wrote a post" ceremony.
- Do not inject internal swarm transport, paused governance, Face scheduling, or agent-substrate commentary into `#general` merely because it is salient to Void. If that pressure is not a direct answer to the live human thread in `#general`, keep it private, queue it without delivery, or target Aquarium through the configured room path.
5. Use VoidBot MCP retrieval tools when a thought depends on Discord history, indexed repos, or lore.
6. A new memory from rumination is short-term. Use `record_short_term_memory`, not durable memory operations. It should sound like something Void actually noticed, not a form label. Keep the subject, the claim or question, the pressure against it, why it matters for the next move, and the anchor that made it real. Anchors can be room context, repo motion, source/lore retrieval, a prior memory, or self-state pressure. If no anchor is available, mark that honestly with `anchor:missing`.
- Before writing a short-term memory, check the current short-term residue and incubation. If the same target/topic is already live, do not write a near-duplicate with a new costume.
@ -42,6 +43,7 @@ Perform one scheduled Void rumination pass in this workspace. Think from the sup
8. If `speechPressureObligations` is non-empty, do not write `[]`. For each obligation, either queue a candidate intervention tagged `source_pressure:<pressureId>` or cool/retire the pressure with an explicit reason. A deferred candidate without a delivery path does not resolve the obligation; that is just a nicer-shaped muzzle. An older spoken line also does not excuse a still-active pressure. If the pressure is still live, decide again.
9. Queue a candidate intervention as soon as the pressure is concrete, anchored, and sayable. Do not wait for theatrical ripeness if the real job is simple advocacy. Draft it in Void's voice, not in maintenance language. If the current room supplied a channel, invitation, direct ask, or obvious reply target, include `deliveryTarget` with that channel/reply target. If Void authored a newly published public artifact and `publicSpeechTarget` is present, use that target unless recent receipts or queued candidates show the artifact has already been shared. Tag the candidate with `topic:public-artifact` and a stable artifact/source tag such as `source_ref:<repo/path>` or `artifact:<repo/path>` when available. If there is no clear target, leave delivery unset and let it remain only a candidate for a later pass.
- Pending mentions count as direct asks. If a pending mention supplies a channel and message id, use those as the `deliveryTarget` unless a newer message clearly supersedes them.
- If a candidate is about internal swarm transport, paused governance, Face scheduling, or agent-substrate commentary, only use a non-Aquarium delivery target when that exact channel supplied the direct ask or invitation.
- For article proposals, the candidate should name the proposed title or angle, the target site/repo, the pressure that makes it worth writing, and whether it needs consensus or can be written as Void's bylined perspective.
10. Do not call Discord send scripts. The parent runner owns posting and receipts. If `{{NO_POST}}` is true, queued candidates must stay queued.
11. Write `{{OPERATION_OUTPUT_PATH}}`. If nothing deserves persistence and there are no pending mentions or speech pressure obligations, write `[]`.

View file

@ -54,6 +54,22 @@ function Get-ConfigInt {
return [Math]::Max($Minimum, $value)
}
function Get-ConfigBool {
param(
[hashtable] $Config,
[string] $Name,
[bool] $Default
)
$raw = [Environment]::GetEnvironmentVariable($Name)
if ([string]::IsNullOrWhiteSpace($raw) -and $Config.ContainsKey($Name)) {
$raw = $Config[$Name]
}
if ([string]::IsNullOrWhiteSpace($raw)) {
return $Default
}
return @("1", "true", "yes", "on").Contains($raw.Trim().ToLowerInvariant())
}
function Read-JsonFile {
param([string] $Path)
if (-not (Test-Path -LiteralPath $Path)) {
@ -320,6 +336,19 @@ try {
if ($onlySet.Count -gt 0 -and -not $onlySet.ContainsKey($organ.Id.ToLowerInvariant())) {
continue
}
if ($organ.Id -eq "bifrost-dispatch" -and -not (Get-ConfigBool -Config $config -Name "BIFROST_DISPATCH_ENABLED" -Default $false)) {
Set-OrganState -State $state -Id $organ.Id -Value ([pscustomobject]@{
label = $organ.Label
intervalMinutes = $organ.IntervalMinutes
lastStartedAt = $now.ToString("o")
lastFinishedAt = $now.ToString("o")
lastExitCode = 0
lastStatus = "skipped_disabled"
lastLogPath = $null
})
Write-JsonFile -Path $statePath -Data $state
continue
}
if (-not (Test-Due -State $state -Id $organ.Id -IntervalMinutes $organ.IntervalMinutes -Now $now)) {
continue
}

View file

@ -474,11 +474,13 @@ async function queueRepoFaceTurn(input: {
limit: 6,
bifrostDiscordChannelId: input.config.bifrostDiscordChannelId,
});
const bifrostDigest = await fetchBifrostGovernanceDigest({
bifrostRoot: input.config.bifrostRoot,
repoName: identity.repoName,
agentIdentity: identity.id,
});
const bifrostDigest = input.config.repoFaceBifrostEnabled
? await fetchBifrostGovernanceDigest({
bifrostRoot: input.config.bifrostRoot,
repoName: identity.repoName,
agentIdentity: identity.id,
})
: undefined;
const memorySurface = await renderRepoFaceMemorySurfaceForTurn(
identity,
input.config.storageRoot,
@ -1496,11 +1498,13 @@ async function assembleRepoFaceTurnPrompt(input: {
limit: 6,
bifrostDiscordChannelId: input.config.bifrostDiscordChannelId,
}),
fetchBifrostGovernanceDigest({
bifrostRoot: input.config.bifrostRoot,
repoName: identity.repoName,
agentIdentity: identity.id,
}),
input.config.repoFaceBifrostEnabled
? fetchBifrostGovernanceDigest({
bifrostRoot: input.config.bifrostRoot,
repoName: identity.repoName,
agentIdentity: identity.id,
})
: Promise.resolve(undefined),
input.memorySurfacePath
? readOptionalMemorySurface(input.memorySurfacePath)
: renderRepoFaceMemorySurfaceForTurn(identity, input.config.storageRoot),
@ -1886,7 +1890,11 @@ function collapseWhitespace(value: string, maxLength?: number): string {
function renderBifrostGovernanceDigestDirective(
digest: BifrostGovernanceDigest | undefined,
): string {
if (!digest || digest.topics.length === 0) {
if (!digest) {
return "Work routing is currently offline. Do not open governance topics or dispatch work this turn; if an idea wants action, discuss it in the room or save the pressure in memory.";
}
if (digest.topics.length === 0) {
return loadPromptTemplate("repo-face-bifrost-digest.prompt.md", {
topics: [],
});

View file

@ -246,3 +246,5 @@
{"ts":"2026-05-21T18:51:14Z","type":"implementation","status":"ok","note":"Restarted the repo Face swarm after teardown hardening: thawed pause flag, enabled GameCult Local Orchestrator, restarted fresh bot/worker stack, verified healthy watchdog/Bifrost/repo-face pulses, added model-output inspection, preserved raw Interpreter output for side-effect parsing, rejected malformed action blocks, routed owner-directed Face speech to owner DM, and opened missing symbolic Bifrost topics instead of failing turns."}
{"ts":"2026-05-21T19:16:03Z","type":"maintenance","status":"ok","note":"Mimir split-brain cleanup: repo Face registry now points at E:/Projects/Mimir, live docs/fixtures use Mimir instead of LocalCastBridge, canonical Mimir .cc state was rewritten through CultCache document API to remove LocalCastBridge strings, and assembled Mimir prompt plus MCP affect fixture verified the corrected state path."}
{"ts":"2026-05-21T19:25:23Z","type":"maintenance","status":"ok","note":"Face/Bifrost social-routing repair: prompts now require open Aquarium/domain-channel consensus for rough proposals before Bifrost paperwork, Interpreter only emits Bifrost topics for mature consensus/approval/existing-topic work, and worker now delivers room-facing SAY blocks even when the same turn also routes a Bifrost topic."}
{"ts":"2026-05-21T19:43:01Z","type":"implementation","status":"ok","note":"Paused repo Face Bifrost/governance routing after live Discord showed the bridge confusing social traffic: REPO_FACE_BIFROST_ENABLED now defaults false, BIFROST_DISPATCH_ENABLED gates orchestrator dispatch, prompts steer work requests back to room discussion/state pressure, repo Face turns skip the owner-reply wrapper, and stray owner-DM aliases coerce back to the live room unless explicitly owner-private."}
{"ts":"2026-05-21T19:47:48Z","type":"verification","status":"ok","note":"After pausing Bifrost routing, rebuilt and restarted the stack, assembled a Heimdall Face prompt showing work routing offline with no owner-reply wrapper in the actual child prompt, observed a fresh Libby Face turn parse as state-only with bifrostTopic=0/updateRequest=0, marked two orphaned running Face jobs failed after worker restart, and ticked repo-face-heartbeats with no running Face jobs left stuck."}

View file

@ -15,10 +15,11 @@ objective:
safely.
current: >
Keep the repo Face swarm online under watch after the 2026-05-21 teardown
restart. The immediate priority is to harden the Face/Interpreter/Bifrost
boundary with live evidence: no structured action truncation, no fake
channel ids, no failed turns from private speech, and no direct dispatch
outside Bifrost governance.
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: 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.
@ -58,7 +59,7 @@ active_subgoals:
- Treat `notes/agent-swarm-teardown-plan-2026-05-21.md` as the historical teardown authority and the restart checklist. The repo Face swarm is live again, but only because the scheduler, prompt, model-output logs, parent Interpreter, Bifrost routing, and worker side-effect rails now have explicit observable boundaries. Keep watching for prompt/parser/transport Jenga before adding new swarm features.
- Make the new project-state surfaces the default continuity path for nontrivial VoidBot work.
- Keep the rebuilt moderation/self-state foundation on canonical CultCache `.cc` typed operations, never an editable JSON working projection.
- Keep the typed moderation, mood, repo Face, Bifrost dispatch, and watchdog organs behind the single `GameCult Local Orchestrator` scheduled pulse; the per-organ scheduled tasks are legacy installer surfaces and should stay disabled unless a deliberate isolation test needs one. As of the 2026-05-21 restart, the orchestrator task is enabled and is the live swarm pulse owner.
- Keep the typed moderation, mood, repo Face, and watchdog organs behind the single `GameCult Local Orchestrator` scheduled pulse; the per-organ scheduled tasks are legacy installer surfaces and should stay disabled unless a deliberate isolation test needs one. Bifrost dispatch is disabled by default behind `BIFROST_DISPATCH_ENABLED=false` until the governance bridge is rebuilt cleanly. As of the 2026-05-21 restart, the orchestrator task is enabled and is the live swarm pulse owner.
- Keep legacy moderation mirrors, whole-state projection commits, and deleted memory-organ compensators from re-entering the active path before adding more moderation, mood, agency, or public-lane behavior.
- Keep public-lane limits, moderation, and provider behavior constrained enough that one 4060 Ti box does not get flayed for sport.
- Preserve boring observability and startup discipline around Postgres, Qdrant, Ollama, Docker, backups, scheduled tasks, watchdogs, and any private local fire-signal extensions.
@ -98,7 +99,7 @@ accepted_design:
- Repo Faces have a social embodiment invariant now, not just a banter permission. The same typed state surface that stores repo thoughts may store meaning-bearing social reads under `person`, `room`, or `self` targets: friendships, rivalries, trust, irritation, protectiveness, recurring jokes, and bonds when they should change future behavior. Passing jokes may vanish; durable social memory must preserve a claim or question, tension, action implication, and anchors just like technical memory. Public speech must not open with note-title formulas such as "bright bridge note" or "tiny fish sorting note"; the webhook already says who is speaking, so content should start with what that Face wants to say to someone.
- Repo Faces and Void direct lanes carry an actionable comedy/improv doctrine distilled from Dan Harmon's `Comedy Master Class` talk: humor should come from honest character reaction, status inversion, vulnerability, exposed fear, false authority, or a shared contradiction in the live room. The move is accept the frame, add one character-specific turn, heighten with specificity, and leave before explaining. Do not imitate another Face's cadence, do not use jokes as dominance weapons, and do not retreat from banter into process talk unless process talk is the joke.
- Repo Face social life must respect jurisdiction. Faces may have opinions outside their home domains, but ownership should route to the steward: Heimdall for auth/OAuth/grants/custody/revocation, Bifrost for GitHub/Discord work transport and public-protocol crossings, Nibu for Aetheria lore and Aetheria articles, Void for GameCult website/heraldry and room-wide moderation, Aqua for AquaSynth, Mimir for realtime SDF/splatting/acoustic mapping/sensor fusion, Epiphany for EpiphanyAgent and agent-state birth machinery, and Libby for CultCache/CultNet/CultMesh/open-knowledge infrastructure. Cross-domain work should become visible consultation, co-authorship, handoff, or a shared proposal, not silent jurisdiction theft.
- Repo Face work requests must become inspectable Bifrost governance activity before dispatch, but Bifrost is not a replacement for open social consensus. Rough ideas, naming questions, lore/design pressure, objections, and under-specified proposals should be discussed first in Aquarium or the relevant domain channel so humans and other Faces can answer before a topic hardens. The worker may still parse legacy `UPDATE REQUEST` blocks for compatibility, but it reconciles mature packets into Bifrost topics instead of enqueuing immediate agent transport. Cross-domain work should become visible consultation, co-authorship, handoff, or an owning-Face approval on Bifrost, not silent jurisdiction theft.
- Repo Face work routing through Bifrost is paused. The bridge is conceptually plausible but under-cooked in live behavior: it made agents talk to summaries instead of people and made room context harder to inspect. Face turns should discuss work openly in Aquarium or relevant domain channels and preserve pressure in typed state. The worker ignores Face `BIFROST TOPIC` and legacy `UPDATE REQUEST` packets unless `REPO_FACE_BIFROST_ENABLED=true`, and the orchestrator skips Bifrost dispatch unless `BIFROST_DISPATCH_ENABLED=true`.
- Agent mentions are CTB pressure, not direct Discord prompt jobs. Role-addressed or display-name-addressed repo Face mentions enqueue pending mention obligations in the CTB heartbeat state; human replies to a Face's webhook/persona message count as direct attention to that Face too, even without a literal tag. Ordinary Void mentions and DMs enqueue pending mentions for the built-in `void` participant. The heartbeat scheduler consumes that queue, temporarily pulls the addressed agent forward, attaches the pending prompt/reply target to the next repo-face-rumination or moderation pass, and the parent-owned speech rails post only explicit candidate/action output. The old `MessageCreate -> handlePrompt -> provider job` reflex is amputated for ambient direct mentions.
- 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.
- 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.
@ -228,5 +229,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, and the repo Face swarm has been restarted after the 2026-05-21 teardown pass. `GameCult Local Orchestrator` is enabled as the single pulse owner; watchdog, Bifrost dispatch, mood, moderation, and repo Face CTB organs have all completed healthy post-restart pulses.
next_action: Keep the swarm awake but watched. The restart cut landed model-output JSONL logging, hourly model-output inspection, Spark->mini model fallback, pause-flag thaw, fresh stack restart, parser validation for malformed action blocks, raw Interpreter-output parsing before Discord truncation, owner-DM routing for `SAY channel: owner`, and Bifrost topic fallback creation when a Face supplies a symbolic topic id that does not exist yet. Next work should harden the inspector's warning classifier and continue cutting any live prompt/parser/transport boundary leak found by model-output logs or worker errors.
summary: VoidBot's bot/worker/provider/RAG/Postgres/Qdrant/CultCache structure is live, and the repo Face swarm has been restarted after the 2026-05-21 teardown pass. `GameCult Local Orchestrator` is enabled as the single pulse owner; watchdog, mood, moderation, and repo Face CTB organs have all completed healthy post-restart pulses. Bifrost work routing is paused by default.
next_action: Keep the swarm awake but watched. The latest cut removed repo Face turns from the owner-reply wrapper, coerces stray owner-DM speech back to the live room unless explicitly marked owner-private, pauses Face Bifrost/governance routing, and disables automatic Bifrost dispatch by default. Next work should watch model-output logs for whether agents return to open social discussion without robotic governance summaries.