Track moderation status through Bifrost transport

This commit is contained in:
Meta Vens 2026-06-04 20:28:55 +01:00
commit 6ffafc5cde
17 changed files with 940 additions and 32 deletions

View file

@ -39,7 +39,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou
- 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.
- `npm run moderation:rumination:bifrost-transport-fixture` proves queued speech and moderation status DMs cross the default Bifrost bridge path and return receipts to typed state. `npm run moderation:rumination:speech-fixture` still proves the explicit direct Discord fallback path for standalone installs. The child prompt forbids direct send-script calls either way.
- The first live sleep-maintenance gate passed on real state. A manual typed nap ran `scripts/run-void-memory-maintenance.ps1`, consumed the scheduled rumination short-term memory with one `apply_memory_distillation` operation, and left `thoughtMemory.shortTerm` empty while preserving the durable identity seam with target, claim, tension, action implication, and anchors.
- A later live-loop inspection found one stale answered-case summary, four stale short-term memories, and an unbounded rumination/memory-maintenance failure mode. The current hardened path now verifies candidate delivery postconditions, normalizes answered-case wording on receipt/closure, uses process-aware locks plus Codex timeouts, and a fresh memory-maintenance pass reduced live short-term memory back to zero.
- `npm run moderation:rumination:nap-skip-fixture` proves awake rumination does not keep making fresh short-term residue during sleep: when the typed sleep cycle is napping and there are no new room messages or open cases, the runner writes `[]` and exits before invoking Codex.
@ -50,7 +50,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou
- Long-term memory is durable, not immutable. `revise_durable_memory`, `retire_durable_memory`, and `crystallize_memory_into_identity` make memory reconsolidation explicit; `npm run moderation:memory-lifecycle:fixture` proves a short-term thought can promote, revise, retire superseded durable records, and crystallize into an identity memory plus self-profile value.
- Agency has a typed pressure surface now: `upsert_agency_pressure` / `retire_agency_pressure` own discomforts, active tensions, self-advocacy requests, and world-advocacy requests. Candidate interventions are possible speech/actions emerging from pressure, not the pressure itself. `npm run moderation:agency-pressure:fixture` verifies the pressure enters `.cc`, renders in self-state summary, and affects mood speaking pressure.
- Strong advocacy pressure now has to account for its own mouth. The rumination runner derives `speechPressureObligations` once active self/world advocacy reaches 0.55 intensity, and only a live queued candidate counts as release for that obligation. A pass may queue/defer a candidate or cool/retire the pressure, but silent `[]` fails visibly and an older spoken candidate does not buy permanent absolution. `npm run moderation:rumination:speech-pressure-fixture` covers the silent, spoken-history, and queued-candidate paths.
- Deferred candidates are not delivered speech and no longer satisfy active advocacy pressure by themselves. The runner now delivers existing queued candidates with delivery targets, not only candidates proposed by the current model pass. The first live repair used Metacrat's explicit channel permission to move the stranded AquariumSynthHost candidate from deferred/no-target to queued/channel-target, then delivered it through the normal bot transport and recorded a typed speech receipt.
- Deferred candidates are not delivered speech and no longer satisfy active advocacy pressure by themselves. The runner now delivers existing queued candidates with delivery targets, not only candidates proposed by the current model pass. The first live repair used Metacrat's explicit channel permission to move the stranded AquariumSynthHost candidate from deferred/no-target to queued/channel-target, then delivered it through the then-current bot transport and recorded a typed speech receipt. Current unattended delivery should cross through Bifrost by default.
- Parent-owned delivery now also checks canonical typed speech receipts before using the mouth. If a queued candidate targets a Discord reply that already has a receipt, the runner retires it instead of answering the same message twice in slightly different prose. Typed state writes also normalize historical receipts to keep the earliest canonical reply per `channelId + replyToMessageId`. `scripts/smoke-void-rumination-speech-fixture.ps1` now seeds a prior receipt and proves both same-pass sibling retirement and already-answered target retirement.
- Speaking pressure is allowed to refresh while moderation is active. `scripts/simulate-void-mood.mjs` still skips sleep-cycle mutation and memory maintenance under a recent moderation lock, but it now reloads typed state and applies `update_speaking_pressure` first, so fresh canonical speech receipts update `lastSpokeAt`, damping, and `needToSpeak` even during long rumination runs.
- Most of the deterministic legacy translation and retention code is recent compensator cruft from failed attempts to manually clean an exploded state file. Do not preserve it as an earned organ. Architect the state boundary so that bad state cannot enter, and keep only narrow historical migrations for old debris.
@ -105,7 +105,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou
- The important distinction is outward too, not just inward. Self-directed tensions can ask for prompt/memory/runtime surgery, while world-directed tensions can turn repo/lore/room opinions into real requests or first-person interventions when the same objection survives more than one pass.
- The intended shape now is: quiet runs can follow one seam for several passes, connect repo motion to lore or philosophy if the evidence supports it, and only surface with the juiciest version once the incubation queue says the thought has actually grown teeth.
- Ambient Discord mentions are no longer a direct reply organ. Role/display-name Face mentions, human replies to a Face's webhook/persona message, and ordinary Void mentions/DMs append pending obligations to `REPO_FACE_HEARTBEAT_STATE_PATH`; the CTB scheduler pulls the addressed agent forward and passes the pending prompt/reply target into the next Face rumination or Void moderation turn. Display-name matching is exact-token matching across the whole message, so group introductions like "Meet Kiko, Huginn and Weksa" queue every named Face instead of requiring the message to begin with one name. Mention detection can happen outside the regular autonomous Face chatter lanes, but public speech still goes through the registered channel/action rails with source-channel transcript attached. Slash commands and explicit owner queue tools may still use provider jobs, but `MessageCreate` no longer falls straight into `handlePrompt`.
- The moderation loop now speaks through `node scripts/send-discord-message.mjs` using the local bot token instead of approval-gated side-effecting MCP tools, so unattended runs can DM or post without Codex pausing for permission every time it wants to open its mouth. That helper and the `post_discord_message` MCP tool now also support a shared guild-channel webhook pipe with per-message `personaName` and optional `personaAvatarUrl`, so swarm agents can speak as themselves without needing separate Discord bot identities.
- The moderation loop now crosses Discord through Bifrost by default. `scripts/run-void-moderator-rumination.ps1` calls `BIFROST_ROOT/tools/bifrost-bridge.mjs` for public candidate posts and user moderation-status DMs, then converts the Bifrost receipt into typed Void state. `node scripts/send-discord-message.mjs` remains an explicit standalone fallback behind `VOID_DISCORD_TRANSPORT=direct`; it should not become the hidden GameCult transport authority again.
- Repo identities now have a cleaner address/speech split. A private registry at `REPO_DISCORD_IDENTITIES_PATH` maps identity id/repo/display name/avatar/role/channel allow-list. Discord users can address a Face by role mention or exact display-name mention; Codex agents still speak through `post_repo_identity_message`.
- Void still moderates and participates everywhere the normal bot lanes allow. The swarm/repo Faces are scoped to text `#aquarium` (`1501196543150264332`) so their lively heartbeats and role replies do not flood general. The old general channel id is `113786069023064068`; do not reuse it as a Face heartbeat default.
- Repo Faces now have topic/channel speech permissions. Aquarium is the deliberately low-threshold general-musing lane for all Faces; they may post compact casual thoughts, jokes, curiosities, and half-formed fascinations there without a work artifact. Domain channels are also allowed when the topic matches: Nibu can use narrative/gamedesign/concepts/development, Aqua can use music/soundtrack/development, Mimir can use development/programming/visual-effects/machine-learning/unity3d, and Epiphany can use development/programming/machine-learning. The CTB scheduler folds per-topic speed multipliers into Face initiative, and the heartbeat prompt includes recent cross-channel context so agents can jump into non-Aquarium chatter when their thoughts line up.

View file

@ -199,15 +199,15 @@ When present, an ignored local script at `.voidbot/private/check-voidbot-operati
## Verse Service Contract
- Owner:
- VoidBot owns Discord ingress, room cognition, moderation judgment, archive/source retrieval, direct owner handoff, typed Void self-state, repo Face scheduling compatibility, and parent-owned speech delivery.
- VoidBot owns Discord ingress, room cognition, moderation judgment, archive/source retrieval, direct owner handoff, typed Void self-state, repo Face scheduling compatibility, and parent-owned speech intent/receipt application. Default Discord egress crosses through Bifrost; direct VoidBot send helpers are standalone fallback.
- Huginn owns Persona-state and `.cc` runtime inspection stewardship: schema availability, migration pressure, projection health, access-tool sanity, CultMesh publication, and Eve DSL inspection for typed state.
- Bifrost owns governed public crossings for GitHub proposals/articles/comments, work topics, dispatch receipts, and future Discord-native work interfaces.
- Bifrost owns governed public crossings for GitHub proposals/articles/comments, work topics, dispatch receipts, current Discord egress for swarm/moderation status, and future Discord-native work interfaces.
- Odin owns Verse/provider discovery and interface aggregation, not provider state.
- Eve/CultUI owns presentation shape; browser/native/TUI renderers lower provider-owned surfaces.
- Inputs:
- Discord gateway events, archived Discord corpus, indexed source/lore archives, Postgres jobs/audit/interaction memory, Qdrant vectors, typed `.cc` self/Face state, Bifrost governance digest, and orchestrator status.
- Outputs:
- Discord replies or webhook persona posts through parent-owned rails, owner handoff artifacts, MCP retrieval/tool results, typed CultCache operations, and CultMesh-advertised Eve/CultUI provider surfaces.
- Discord reply/status intents sent through Bifrost with receipts applied to typed state, owner handoff artifacts, MCP retrieval/tool results, typed CultCache operations, and CultMesh-advertised Eve/CultUI provider surfaces.
- Derived state:
- JSON status files, generated HTML, source/archive freshness summaries, prompt-facing projections, and local debug snapshots are witnesses or lowerings. They do not own durable truth.
- Forbidden writers:

View file

@ -328,7 +328,7 @@ Restart gates:
- Landed earlier advocacy release: speech-pressure obligations now start at 0.55 intensity instead of waiting for 0.65, only a live queued candidate satisfies an active pressure, and a previously spoken candidate no longer counts as permanent release while the pressure remains active. Mood drift also weights active advocacy more strongly so speaking pressure rises sooner.
- Landed rumination verification: `npm run moderation:rumination:fixture` routes the non-skip rumination branch through a fake Codex child and proves short-term memory, incubation, agency-pressure, and candidate-intervention proposals cross the parent-applied typed operation boundary without timestamp leakage.
- Landed controlled live rumination review: the first real-model `-NoPost` pass produced valid but self-orbiting readiness-test residue, which was pruned/retired and folded into a prompt guard; the second real-model `-NoPost` pass sampled non-VoidBot repo pressure, used retrieval, and correctly wrote `[]` when nothing earned persistence.
- Landed parent-owned speech closure: candidate interventions can include `deliveryTarget`, the prompt forbids child-side send scripts, the parent runner delivers at most one newly queued deliverable candidate per pass, records the receipt, and marks the candidate spoken with `mark_candidate_intervention_spoken`. `npm run moderation:rumination:speech-fixture` proves the loop with fake Codex and fake Discord send helpers.
- Landed parent-owned speech closure: candidate interventions can include `deliveryTarget`, the prompt forbids child-side send scripts, the parent runner delivers at most one newly queued deliverable candidate per pass, records the receipt, and marks the candidate spoken with `mark_candidate_intervention_spoken`. Default delivery goes through Bifrost (`npm run moderation:rumination:bifrost-transport-fixture`); `npm run moderation:rumination:speech-fixture` proves the direct Discord fallback for standalone installs.
- Landed receipt-backed duplicate direct-reply guard: before delivery, the parent runner now retires queued candidates whose `channelId + replyToMessageId` already appear in canonical speech receipts, so a later pass cannot answer the same direct ask twice just because the message remains visible. The speech fixture now seeds a prior receipt and proves both same-pass sibling retirement and already-answered target retirement.
- Landed scheduled rumination re-enable: one observed live scheduled pass completed with Last Result `0`, proposed one anchored short-term memory, delivered no speech, and kept exact timestamps out of the prompt-facing context.
- Landed live sleep-maintenance verification: a manual typed nap ran real memory maintenance on live state, promoted the scheduled rumination short-term memory into one durable `identity_seam`, and left `thoughtMemory.shortTerm` empty.