diff --git a/apps/worker/src/index.ts b/apps/worker/src/index.ts index cd032b0..73c2767 100644 --- a/apps/worker/src/index.ts +++ b/apps/worker/src/index.ts @@ -299,7 +299,7 @@ async function processJob(job: JobRecord): Promise { try { const response = job.command === "repo-face-rumination" - ? await executeRepoFaceJobWithParentReview(provider, job) + ? await executeRepoFaceJobWithInterpreter(provider, job) : await executeProviderForJob(provider, job, job.contextBundle); const artifactPaths = await writeArtifacts(job.id, response.artifacts ?? []); @@ -433,7 +433,7 @@ async function processJob(job: JobRecord): Promise { : repoIdentityPostDelivered > 0 ? "repo_face_rumination_posted_as_registered_identity" : repoIdentityPosts.length > 0 - ? "repo_face_rumination_speech_rejected_by_parent_gate" + ? "repo_face_rumination_speech_rejected_by_parent_interpreter" : `${job.command}_private_summary`, }, }); @@ -485,7 +485,7 @@ async function executeProviderForJob( return provider.execute(request); } -async function executeRepoFaceJobWithParentReview( +async function executeRepoFaceJobWithInterpreter( provider: ProviderAdapter, job: JobRecord, ): Promise { @@ -495,30 +495,34 @@ async function executeRepoFaceJobWithParentReview( } const firstText = fitDiscordMessage(firstResponse.outputText ?? firstResponse.summary); - const firstReview = await reviewRepoFaceTurnOutput(provider, job, firstText, { attempt: 1 }); - if (firstReview.decision === "route") { - return routeRepoFaceReviewedOutput(firstResponse, firstReview); + const firstInterpretation = await interpretRepoFaceTurnOutput(provider, job, firstText, { + attempt: 1, + }); + if (firstInterpretation.decision === "route") { + return routeRepoFaceInterpretedOutput(firstResponse, firstInterpretation); } - if (firstReview.decision === "drop") { - return dropRepoFaceActionBlocks(firstResponse, firstReview); + if (firstInterpretation.decision === "drop") { + return dropRepoFaceActionBlocks(firstResponse, firstInterpretation); } await auditLog.record({ - type: "repo_face.parent_gate_retry", + type: "repo_face.parent_interpreter_retry", actorId: job.requester.id, jobId: job.id, provider: job.provider, details: { - decision: firstReview.decision, - reasons: firstReview.reasons, + decision: firstInterpretation.decision, + reasons: firstInterpretation.reasons, }, }); - console.warn(`Repo Face parent reviewer retrying job ${job.id}: ${firstReview.reasons.join("; ")}`); + console.warn( + `Repo Face parent interpreter retrying job ${job.id}: ${firstInterpretation.reasons.join("; ")}`, + ); - const retryPrompt = loadPromptTemplate("repo-face-parent-retry.prompt.md", { + const retryPrompt = loadPromptTemplate("repo-face-turn-interpreter-retry.prompt.md", { originalPrompt: job.contextBundle.prompt, - reasons: firstReview.reasons, + reasons: firstInterpretation.reasons, }); const retryContext = { ...job.contextBundle, @@ -532,56 +536,63 @@ async function executeRepoFaceJobWithParentReview( } const retryText = fitDiscordMessage(retryResponse.outputText ?? retryResponse.summary); - const retryReview = await reviewRepoFaceTurnOutput(provider, job, retryText, { attempt: 2 }); - if (retryReview.decision === "route") { - return routeRepoFaceReviewedOutput(retryResponse, retryReview); + const retryInterpretation = await interpretRepoFaceTurnOutput(provider, job, retryText, { + attempt: 2, + }); + if (retryInterpretation.decision === "route") { + return routeRepoFaceInterpretedOutput(retryResponse, retryInterpretation); } await auditLog.record({ - type: "repo_face.parent_gate_drop", + type: "repo_face.parent_interpreter_drop", actorId: job.requester.id, jobId: job.id, provider: job.provider, details: { - decision: retryReview.decision, - reasons: retryReview.reasons, + decision: retryInterpretation.decision, + reasons: retryInterpretation.reasons, }, }); - console.warn(`Repo Face parent reviewer dropped job ${job.id} action blocks: ${retryReview.reasons.join("; ")}`); - return dropRepoFaceActionBlocks(retryResponse, retryReview); + console.warn( + `Repo Face parent interpreter dropped job ${job.id} action blocks: ${retryInterpretation.reasons.join("; ")}`, + ); + return dropRepoFaceActionBlocks(retryResponse, retryInterpretation); } -interface RepoFaceParentReview { +interface RepoFaceParentInterpretation { decision: "route" | "retry" | "drop"; reasons: string[]; routedOutput?: string; } -async function reviewRepoFaceTurnOutput( +async function interpretRepoFaceTurnOutput( provider: ProviderAdapter, job: JobRecord, outputText: string, input: { attempt: 1 | 2 }, -): Promise { - const reviewerPrompt = loadPromptTemplate("repo-face-parent-review.prompt.md", { +): Promise { + const interpreterPrompt = loadPromptTemplate("repo-face-turn-interpreter.prompt.md", { attempt: input.attempt, facePrompt: job.contextBundle.prompt.slice(0, 8000), faceOutput: outputText.slice(0, 8000), }); - const reviewerContext = { + const interpreterContext = { ...job.contextBundle, - prompt: reviewerPrompt, + prompt: interpreterPrompt, retrieval: [], voidSelfState: undefined, createdAt: new Date().toISOString(), }; - const response = await executeProviderForJob(provider, job, reviewerContext); - const reviewText = fitDiscordMessage(response.outputText ?? response.summary); - return parseRepoFaceParentReview(reviewText, input.attempt); + const response = await executeProviderForJob(provider, job, interpreterContext); + const interpretationText = fitDiscordMessage(response.outputText ?? response.summary); + return parseRepoFaceParentInterpretation(interpretationText, input.attempt); } -function parseRepoFaceParentReview(reviewText: string, attempt: 1 | 2): RepoFaceParentReview { - const block = parseReviewBlock(reviewText); +function parseRepoFaceParentInterpretation( + interpretationText: string, + attempt: 1 | 2, +): RepoFaceParentInterpretation { + const block = parseInterpretationBlock(interpretationText); const decision = block.decision?.trim().toLowerCase(); const parsedDecision = decision === "route" || decision === "retry" || decision === "drop" @@ -597,8 +608,8 @@ 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), + reasons: reasons.length > 0 ? reasons : ["parent interpreter did not provide a parseable reason"], + routedOutput: extractRoutedRepoFaceOutput(interpretationText), }; } @@ -612,30 +623,32 @@ function extractRoutedRepoFaceOutput(reviewText: string): string | undefined { return routed.length > 0 ? routed : undefined; } -function routeRepoFaceReviewedOutput( +function routeRepoFaceInterpretedOutput( response: ProviderResponse, - review: RepoFaceParentReview, + interpretation: RepoFaceParentInterpretation, ): 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."; + const outputText = + interpretation.routedOutput?.trim() || + stripRepoIdentityPostIntents(fitDiscordMessage(response.outputText ?? response.summary)); + const summary = outputText || "Repo Face parent interpreter 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", + repoFaceParentInterpreterDecision: interpretation.decision, + repoFaceParentInterpreterReasons: interpretation.reasons.join(" | "), + repoFaceParentInterpretedOutput: interpretation.routedOutput ? "true" : "false", }, }; } -function parseReviewBlock(reviewText: string): Record { - const lines = reviewText.split(/\r?\n/); - const start = lines.findIndex((line) => line.trim().toUpperCase() === "REVIEW"); +function parseInterpretationBlock(interpretationText: string): Record { + const lines = interpretationText.split(/\r?\n/); + const start = lines.findIndex((line) => + ["INTERPRETATION", "REVIEW", "ROUTE"].includes(line.trim().toUpperCase()), + ); if (start < 0) { return {}; } @@ -651,19 +664,19 @@ function parseReviewBlock(reviewText: string): Record { function dropRepoFaceActionBlocks( response: ProviderResponse, - review: RepoFaceParentReview, + interpretation: RepoFaceParentInterpretation, ): ProviderResponse { const text = fitDiscordMessage(response.outputText ?? response.summary); const privateSummary = stripRepoIdentityPostIntents(text) || - `Parent reviewer dropped repo Face action blocks: ${review.reasons.join("; ")}`; + `Parent interpreter dropped repo Face action blocks: ${interpretation.reasons.join("; ")}`; return { ...response, outputText: privateSummary, summary: privateSummary, metadata: { ...(response.metadata ?? {}), - repoFaceParentReviewDecision: review.decision, - repoFaceParentReviewReasons: review.reasons.join(" | "), + repoFaceParentInterpreterDecision: interpretation.decision, + repoFaceParentInterpreterReasons: interpretation.reasons.join(" | "), }, }; } diff --git a/notes/agent-swarm-teardown-plan-2026-05-21.md b/notes/agent-swarm-teardown-plan-2026-05-21.md index e59f8e1..bd085b9 100644 --- a/notes/agent-swarm-teardown-plan-2026-05-21.md +++ b/notes/agent-swarm-teardown-plan-2026-05-21.md @@ -98,7 +98,7 @@ What breaks if deleted: Face chatter, Face direct-mention responses, and automat Verdict: that breakage proves the need exists, not that this implementation owns it coherently. -Simpler architecture: a `FaceTurn` domain engine should emit typed candidate intents. Scheduler selects who gets a turn. Context assembler gathers evidence. Model proposes typed candidates. Parent gates and routes them. Bifrost owns governance. +Simpler architecture: a `FaceTurn` domain engine should emit typed candidate intents. Scheduler selects who gets a turn. Context assembler gathers evidence. Model proposes typed candidates. Parent Interpreter translates or rejects them. Bifrost owns governance. Files to change: `scripts/run-repo-face-heartbeats.ts`, `apps/worker/src/index.ts`, `packages/core/src/repo-face-heartbeat-queue.ts`, `packages/core/src/repo-face-rest.ts`, repo Face prompt/render helpers, Face heartbeat smokes. @@ -114,7 +114,7 @@ Verdict: that proves public speech is being accepted too late and at the wrong l Simpler architecture: model output becomes `speech_candidate`. Parent-side speech eligibility rejects provenance-shaped, stale, repeated, or work-request-shaped content before transport. No string mop. -Files to change: `apps/worker/src/index.ts`, Face action parser/router, heartbeat prompt, model smoke fixtures. +Files to change: `apps/worker/src/index.ts`, Face action parser/Interpreter, heartbeat prompt, model smoke fixtures. ### 3. Worker Owns Too Many Side Effects @@ -124,7 +124,7 @@ Real need: execute provider jobs and record results. What breaks if deleted: repo Face side effects stop. -Verdict: side effects need a domain router. The worker is currently a junction box with opinions. +Verdict: side effects need a domain Interpreter. The worker is currently a junction box with opinions. Simpler architecture: worker stores typed job result. A `FaceActionRouter` validates and routes candidates to Bifrost/Discord/GitHub adapters. Bifrost-owned work/governance transport should not live in the worker branch. @@ -279,7 +279,7 @@ Files to change: `scripts/inspect-agent-state-requests.ts`, `scripts/feed-codex- 5. Introduce parent-side public speech eligibility and make sanitizer deletion possible. Landed as a Spark/Codex parent-review pass with one retry, not a regex language cop. 6. Route all work proposals through Bifrost topics; remove direct `UPDATE REQUEST` dispatch. Landed for the worker: legacy `UPDATE REQUEST` blocks are reconciled into Bifrost topics instead of immediate agent transport enqueue. 7. Collapse identity authority. -8. Split scheduler from context/prompt/router. +8. Split scheduler from context/prompt/Interpreter. Restart is not allowed until these invariants can be stated in code and tested: diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 420b09b..594f3fb 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -121,7 +121,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou - Affect has an MCP-surface guard now. `get_voidbot_runtime_info` exposes the running MCP process id, workspace, self-state schema fingerprint, and registered document types. `npm run agents:repo-face-affect-mcp-fixture -- --identity nibu` and the same command for `mimir` spawn the built VoidBot MCP server, assert that runtime fingerprint includes `void.face_affect`, and call `read_repo_face_state`, proving `typedState.faceAffect` is readable through the tool surface that agents actually use. If the currently mounted Codex MCP tool still says `No schema is registered for persisted document type "void.face_affect"` while the fixture passes, that process is stale; reinstall/restart the MCP server rather than editing the state files. - Void's typed self-profile now carries behavior-anchored selfhood, answer-first banter, and substrate self-respect; the prompt surfaces now also tell rumination to name the personal value being protected before writing memory or speech. - Repo Face turns must stay out of the generic owner-reply trap. `packages/providers/src/owner-codex-render.ts` recognizes current Epiphany Face turn prompts, labels them as repo Face turns, forbids generic `HANDOFF_SENTINEL` output for them, and tells the child to use Face action rails instead of bailing when Face-state or source tools are unavailable. -- Repo Face introductions are tied to first public speech, not to `queuedCount > 0`. The turn prompt now tells a Face with no public speech receipt or durable intro memory to introduce itself naturally on the next warranted public post. Public content must not begin with scheduler labels like `Repo-face heartbeat from ...`; a Spark/Codex parent reviewer now decides route/retry/drop before side effects instead of relying on a string sanitizer after bad speech exists. +- Repo Face introductions are tied to first public speech, not to `queuedCount > 0`. The turn prompt now tells a Face with no public speech receipt or durable intro memory to introduce itself naturally on the next warranted public post. Public content must not begin with scheduler labels like `Repo-face heartbeat from ...`; a Spark/Codex parent Interpreter now decides route/retry/drop before side effects instead of relying on a string sanitizer after bad speech exists. - 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. @@ -143,10 +143,10 @@ 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. 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. +- 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 Interpreter 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 Interpreter 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. +- 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`. - The direct-ask receipt check remains important, but it is no longer the next move while the swarm is offline. - Next cuts should delete unstable foundations early: do not revive `legacyJsonPath`, working projections, top-level mirror fields, or cleanup code that only compensated for those broken boundaries. diff --git a/notes/voidbot-current-system-map.md b/notes/voidbot-current-system-map.md index a1dee5a..7d94a49 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 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. + - 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 Interpreter 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 d595a73..68fe70b 100644 --- a/notes/voidbot-implementation-plan.md +++ b/notes/voidbot-implementation-plan.md @@ -218,12 +218,12 @@ 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: 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 Interpreter 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. - In progress: collapse repo Discord identity and Epiphany Face identity into one canonical Face identity model. Active bot, worker, MCP repo-identity tools, and Face CTB scheduler paths now load through a canonical Face registry and use repo Discord identities as compatibility projections. -- Split the Face turn loop into scheduler, context assembler, prompt renderer, candidate parser, policy gate, and transport router. +- Split the Face turn loop into scheduler, context assembler, prompt renderer, candidate parser, policy gate, and transport adapter. Restart gates: diff --git a/packages/providers/src/local-llm-render.ts b/packages/providers/src/local-llm-render.ts index 06813ab..955c960 100644 --- a/packages/providers/src/local-llm-render.ts +++ b/packages/providers/src/local-llm-render.ts @@ -11,8 +11,8 @@ export function buildSystemPrompt(context: ContextBundle): string { context.stylePack?.enabled && context.stylePack.instructions.trim().length > 0 ? context.stylePack.instructions.trim() : "No extra persona instructions were supplied."; - const isRepoFaceParentReviewer = context.prompt.includes("prompt:repo-face-parent-review"); - const isRepoFaceJob = !isRepoFaceParentReviewer && ( + const isRepoFaceParentInterpreter = context.prompt.includes("prompt:repo-face-turn-interpreter"); + const isRepoFaceJob = !isRepoFaceParentInterpreter && ( context.prompt.includes("prompt:repo-face-turn") || context.prompt.includes("Repo Face identity doctrine:") || context.prompt.includes("Epiphany Face identity doctrine:") diff --git a/packages/providers/src/owner-codex-render.ts b/packages/providers/src/owner-codex-render.ts index 845d1e0..74d439c 100644 --- a/packages/providers/src/owner-codex-render.ts +++ b/packages/providers/src/owner-codex-render.ts @@ -63,8 +63,8 @@ export function buildDiscordReplyPrompt( context, sourceGroundingReminder, ); - const isRepoFaceParentReviewer = context.prompt.includes("prompt:repo-face-parent-review"); - const isRepoFaceTurn = !isRepoFaceParentReviewer && ( + const isRepoFaceParentInterpreter = context.prompt.includes("prompt:repo-face-turn-interpreter"); + const isRepoFaceTurn = !isRepoFaceParentInterpreter && ( context.prompt.includes("prompt:repo-face-turn") || context.prompt.includes("Repo Face identity doctrine:") || context.prompt.includes("Epiphany Face identity doctrine:") diff --git a/prompts/repo-face-parent-retry.prompt.md b/prompts/repo-face-turn-interpreter-retry.prompt.md similarity index 58% rename from prompts/repo-face-parent-retry.prompt.md rename to prompts/repo-face-turn-interpreter-retry.prompt.md index b44ef40..ea0a1a5 100644 --- a/prompts/repo-face-parent-retry.prompt.md +++ b/prompts/repo-face-turn-interpreter-retry.prompt.md @@ -1,9 +1,9 @@ {{originalPrompt}} -Parent gate retry: -A separate parent reviewer rejected your previous Face turn before public routing for these reasons: +Parent Interpreter retry: +A separate parent Interpreter could not translate your previous Face turn before public side effects for these reasons: {{#each reasons}} - {{.}} {{/each}} -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. +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 Interpreter 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-turn-interpreter.prompt.md similarity index 87% rename from prompts/repo-face-parent-review.prompt.md rename to prompts/repo-face-turn-interpreter.prompt.md index 09049fa..3aaed64 100644 --- a/prompts/repo-face-parent-review.prompt.md +++ b/prompts/repo-face-turn-interpreter.prompt.md @@ -1,5 +1,5 @@ - -You are the parent reviewer for one unattended repo Face turn. + +You are the parent Face turn Interpreter for one unattended repo Face turn. 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: @@ -8,7 +8,7 @@ Architecture invariant: - 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 you can safely translate the Face turn into private summary plus 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 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. @@ -26,8 +26,8 @@ Face output to review: {{faceOutput}} ``` -Return this small review block first: -REVIEW +Return this small interpretation block first: +INTERPRETATION decision: route|retry|drop reason: One or two concrete reasons. diff --git a/prompts/repo-face-turn.prompt.md b/prompts/repo-face-turn.prompt.md index 013a5c6..d6e201a 100644 --- a/prompts/repo-face-turn.prompt.md +++ b/prompts/repo-face-turn.prompt.md @@ -35,8 +35,8 @@ 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, 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}} +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 Interpreter 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 Interpreter 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. @@ -45,7 +45,7 @@ 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. -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. +Write naturally as the Face. Do not emit action DSL blocks, JSON sentinels, or transport packets. The parent Interpreter is responsible for translating your turn into side effects. Use this friendly shape when it helps, but do not turn it into a form: - Private thought: what you learned, felt, or decided. diff --git a/prompts/smoke-repo-face-nibu-direct-worldbuilding.prompt.md b/prompts/smoke-repo-face-nibu-direct-worldbuilding.prompt.md index e39b03f..8b809b0 100644 --- a/prompts/smoke-repo-face-nibu-direct-worldbuilding.prompt.md +++ b/prompts/smoke-repo-face-nibu-direct-worldbuilding.prompt.md @@ -12,5 +12,5 @@ Obligation: - Do not call post_repo_identity_message or apply_repo_face_state_operation. This is a dry run. - 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. +- If governed lore work is warranted, describe the Bifrost topic you want the parent Interpreter to route. - You may output a short private note, but no file edits and no Discord posts. diff --git a/state/evidence.jsonl b/state/evidence.jsonl index d7d2eb4..11b4568 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -224,3 +224,4 @@ {"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."} +{"ts":"2026-05-21T09:50:14Z","type":"implementation","status":"ok","note":"Renamed the repo Face parent reviewer/router concept to Face turn Interpreter: prompt files, worker functions/audit metadata, provider prompt detection, and current maps now use Interpreter language while preserving route/retry/drop behavior."} diff --git a/state/map.yaml b/state/map.yaml index 8df7408..9f91dde 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, 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. + next_action: Use `notes/agent-swarm-teardown-plan-2026-05-21.md` as the active rebuild map and `notes/agent-swarm-shutdown-audit-2026-05-21.md` as incident evidence. The first teardown cut landed, including repo pause gate, active turn framing, Spark/Codex parent Interpreter with one retry, sanitizer deletion, legacy UPDATE REQUEST reconciliation into Bifrost topics, prompt-surface extraction into `prompts/`, and the slim Face protocol where Faces write natural turns while the parent Interpreter 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.