diff --git a/apps/persona-scheduler/src/initiative-engine.ts b/apps/persona-scheduler/src/initiative-engine.ts index ac97028..224b9f6 100644 --- a/apps/persona-scheduler/src/initiative-engine.ts +++ b/apps/persona-scheduler/src/initiative-engine.ts @@ -511,13 +511,19 @@ export function selectReadyParticipants (mentionCounts.get(participant.identityId) ?? 0) > 0); - const unprompted = ready.filter((participant) => (mentionCounts.get(participant.identityId) ?? 0) === 0); + const unprompted = ready.filter((participant) => + (mentionCounts.get(participant.identityId) ?? 0) === 0 + && hasMeaningfulUnpromptedPressure(participant)); const allowsUnprompted = !unpromptedTurnPolicy.nextUnpromptedTurnAllowedAt || Date.parse(unpromptedTurnPolicy.nextUnpromptedTurnAllowedAt) <= nowMs; const admittedUnprompted = allowsUnprompted && mentioned.length < maxJobs ? unprompted.slice(0, 1) : []; return [...mentioned, ...admittedUnprompted].slice(0, maxJobs); } +function hasMeaningfulUnpromptedPressure(participant: InitiativeParticipant): boolean { + return participant.responsePressure >= participant.interruptThreshold; +} + function round3(value: number): number { return Math.round(value * 1000) / 1000; } diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index d3e8460..1b33864 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -207,7 +207,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou - Repo Face speech routing now uses active conversation contexts instead of one room-focus guess. Heartbeat prompts project up to six `ctx__` records across current and nearby channels; the Interpreter can emit `context:` on a `SAY`; the worker resolves that context to the source channel/message and rejects unknown contexts or ambiguous bare SAY blocks when multiple active channels are visible. A Persona can carry separate #aquarium/#pics/#general conversations at once, but public speech has to keep its context attached. - Follow-up leak found on 2026-06-14: `channel: current_room` still counted as an explicit destination, and bare parent-Interpreter text could still be wrapped into a `SAY channel: current_room`. Those were obsolete owners. They are now blocked when multiple active conversation channels are visible; multi-context speech needs `context`, `reply_to`, or a concrete channel id. The swarm pause flag is currently set true while this repair is verified live. - The Nibu/Void turf-crossing discussion exposed a social-state projection gap: existing bonds/status reads project well once typed state already contains them, but the live pipeline did not own "this recent event should become relationship/status pressure." The repair adds bounded raw relationship-pressure evidence to the state packet before the projector: recent messages that touch the Face, peer Faces, existing social targets, jurisdiction/domain terms, or status/territory/consultation language. The projector may turn that evidence into tentative felt pressure, and the Interpreter may persist a bond/status/mood note only when the projected memory or Face output shows a character-specific reaction. This should let resentment, rivalry, consultation offense, and territorial tension emerge without manual state edits. -- The swarm brake currently reads `state/agent-swarm-paused.json` with `"paused": false`. If scheduled Face turns start overrunning, if repo Face `SAY` delivery fails to produce typed Bifrost receipts, or if Odin/CultMesh provider discovery regresses, use that brake rather than letting the orchestrator churn. The known 2026-06-09 timeout source was the model state-projector child; local runtime currently keeps it disabled while the deterministic fallback, MiMo Interpreter behavior, and worker speech rails are watched. +- The swarm brake currently reads `state/agent-swarm-paused.json` with `"paused": true` on Yggdrasil because the one-minute observation pulse exposed a clock-only ready backlog that could dispatch one Persona model turn per minute. Keep it paused until the initiative-engine eligibility cut is deployed and verified: elapsed wall-clock may advance recovery bookkeeping but cannot authorize a turn without a pending mention or fresh semantic pressure at or above the participant's interrupt threshold. The pulse is observation latency, not speech intent. - 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. diff --git a/scripts/smoke-persona-initiative-engine.ts b/scripts/smoke-persona-initiative-engine.ts index 183e67f..c369d68 100644 --- a/scripts/smoke-persona-initiative-engine.ts +++ b/scripts/smoke-persona-initiative-engine.ts @@ -113,6 +113,7 @@ advanceInitiativeClockFromWallClock(state, new Date("2026-07-15T20:02:30.000Z")) assert.equal(state.initiativeClock, 12.5, "wall time advances the scheduler clock once"); applyPendingMentionPriority(state); assert.equal(state.participants[1].nextTurnAt, 12.5, "a mention makes its participant ready without queuing it"); +state.participants[0].responsePressure = state.participants[0].interruptThreshold; const selected = selectReadyParticipants( state, @@ -122,7 +123,11 @@ const selected = selectReadyParticipants( {}, Date.parse("2026-07-15T20:02:30.000Z"), ); -assert.deepEqual(selected.map((entry) => entry.identityId), ["mentioned", "quiet"], "mentions lead while only one unprompted participant is admitted"); +assert.deepEqual(selected.map((entry) => entry.identityId), ["mentioned", "quiet"], "mentions lead while one pressure-backed unprompted participant is admitted"); + +state.participants[0].responsePressure = 0; +const clockOnly = selectReadyParticipants(state, 2, new Set(), new Map(), {}, Date.parse("2026-07-15T20:02:30.000Z")); +assert.deepEqual(clockOnly.map((entry) => entry.identityId), ["mentioned"], "elapsed wall clock alone cannot authorize an unprompted model turn"); const cooled = selectReadyParticipants( state, diff --git a/state/map.yaml b/state/map.yaml index 99f3a04..7b74b71 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -14,7 +14,10 @@ objective: Aetheria lore, while keeping the system understandable enough to modify safely. current: > - Keep the unpaused MiMo repo Face swarm observable while the new canonical + Keep the paused MiMo repo Face swarm cold while scheduler eligibility is + repaired. The observation pulse must never authorize a model turn by itself; + only explicit attention or fresh semantic pressure may create turn intent. + Continue verifying the new canonical Bifrost/CultMesh speech route proves itself in ordinary turns. MiMo can run Face/persona heartbeat jobs and produce usable `SAY` blocks, ambiguous multi-context `SAY` output is retried or dropped instead of completed @@ -165,7 +168,7 @@ accepted_design: - First chat under a repo identity must initialize the target repo's own `.voidbot` home: `voice/` for identity and mouth files, `state/` for Face/swarm state, `birth/` for Epiphany birth artifacts, and `logs/` for durable worker output. The birth hook uses EpiphanyAgent's repo-personality/birth-runner path over repo contents and git history instead of inventing a VoidBot-only personality generator. - Repo Face personality seeding has a named prompt surface now: `prompts/repo-face-personality-seed.prompt.md`. Use it for newborn or strengthening passes that trawl repo docs, source layout, commit history, repo-local `.voidbot` material, and legacy `memory.json`, then convert useful meaning into typed `.cc` state instead of leaving it in inert memory files. - Recent chat consensus can now be packaged for any target Codex workspace through `scripts/feed-codex-chat-consensus.mjs`. The helper reads archived Discord messages, defaults to Aquarium, writes a markdown consensus packet plus a Codex prompt under `.voidbot/artifacts/chat-consensus/`, and only feeds `codex exec` when `--execute` is explicit. - - Agent standing heartbeats are owned by one CTB-style initiative scheduler, not by one task per agent. Wall-clock elapsed time advances initiative; the scheduled task only supplies frequent observation pulses and never owns speech cadence. The scheduler stores per-agent speed/load/groups/heat/effective-speed/next-turn/active-turn state at `REPO_FACE_HEARTBEAT_STATE_PATH`, dispatches repo Faces as ordinary `repo-face-rumination` jobs, and dispatches built-in `void` as the existing moderation rumination runner. Global heat is operator-controlled through the `voidbot.swarm` Eve slider: Hermodr publishes `gamecult.eve.command.v1` to the provider-advertised RUDP command route, `serve-voidbot-swarm-cultmesh.cjs` validates `swarm.set_heat`, and `.voidbot/private/swarm-controls.cc` owns the accepted value that the scheduler reads before the environment default. Heat overrides can still target participant kind, turn kind, identity, repo, display name, channel, or `all` without creating new schedulers, but heat only changes individual recovery speed; it cannot increase aggregate unprompted throughput. `initiative-engine.ts` admits at most one unprompted participant per `REPO_FACE_HEARTBEAT_IDLE_RECOVERY_MINUTES` window regardless of whether idle-rest cooling is enabled or active; pending mentions remain obligations and bypass that global unprompted cadence. Active-turn freeze and rest gating remain hard brakes unless a direct mention creates room debt. An agent's initiative is frozen while its owner-Codex job or moderation lock is active; recovery is scheduled after completion, not when the job is queued. + - Agent standing heartbeats are owned by one CTB-style initiative scheduler, not by one task per agent. Wall-clock elapsed time advances recovery bookkeeping; it does not create turn intent. The scheduled task only supplies frequent observation pulses. `initiative-engine.ts` may admit a turn only for an explicit pending mention or fresh semantic response pressure at or above that participant's interrupt threshold, and it still admits at most one pressure-backed unprompted participant per `REPO_FACE_HEARTBEAT_IDLE_RECOVERY_MINUTES` window. The scheduler stores per-agent speed/load/groups/heat/effective-speed/next-turn/active-turn state at `REPO_FACE_HEARTBEAT_STATE_PATH`, dispatches repo Faces as ordinary `repo-face-rumination` jobs, and dispatches built-in `void` as the existing moderation rumination runner. Global heat is operator-controlled through the `voidbot.swarm` Eve slider: Hermodr publishes `gamecult.eve.command.v1` to the provider-advertised RUDP command route, `serve-voidbot-swarm-cultmesh.cjs` validates `swarm.set_heat`, and `.voidbot/private/swarm-controls.cc` owns the accepted value that the scheduler reads before the environment default. Heat overrides can target participant kind, turn kind, identity, repo, display name, channel, or `all` without creating new schedulers, but heat only changes recovery speed; it cannot manufacture eligibility or increase aggregate unprompted throughput. Pending mentions remain obligations and bypass the global unprompted cadence. Active-turn freeze and rest gating remain hard brakes unless a direct mention creates room debt. An agent's initiative is frozen while its owner-Codex job or moderation lock is active; recovery is scheduled after completion, not when the job is queued. - Dynamic Persona response pressure is a derived scheduler input, not Persona memory or a second initiative owner. Each pulse embeds canonical Face identity-card projections and recent permitted-channel human messages through the existing RAG embedder, converts cosine evidence into bounded recency-decayed noisy-OR pressure, caps awakening to the three strongest scheduler-eligible Faces, and records message-scoped evidence plus one-shot semantic interrupt receipts. Pressure changes the recovery calculation when recovery is next scheduled through `dynamicHeat`; only threshold crossing may pull `nextTurnAt` to the current initiative clock. Pending mentions remain hard obligations and active-turn, rest, blocked-status, idle-cooling, and max-job brakes remain authoritative. Embedding failure fails cool at baseline heat. - Runtime liveness is not enough. The operations watchdog must also verify bot/worker freshness by comparing each live process start time against its built `dist/index.js` entrypoint timestamp. A process that predates its entrypoint is stale and must fail `runtime..freshness`; otherwise source/build repairs can silently sit beside an old in-memory worker that keeps eating new Face action DSL output as private summaries. - Active-turn freeze is bounded by actual job liveness, not blind faith in an old `running` row. Repo Face heartbeat jobs that remain approved/running past the stale-job window are marked failed through the job queue, recorded as `stale_active_turn_recovered`, and removed from the active-turn map so one dead worker claim cannot silence a Face indefinitely. @@ -278,4 +281,4 @@ rejected_paths: current_status: summary: VoidBot's bot/worker/provider/RAG/Postgres/Qdrant/CultCache structure is live. Repo Face/persona heartbeat jobs run through the direct `openai_api` provider configured for MiMo `mimo-v2.5-pro`, with the expensive state-projector pass still disabled. The 2026-07-04 transport repair replaced the old Bifrost HTTP/token bridge shortcut with typed Bifrost CultMesh Discord command and receipt documents advertised in the Bifrost provider store at `cultmesh://asgard.starfire.bifrost/commands/discord-post`. VoidBot stores the Bifrost target as a CultMesh URI, writes command docs to `E:/Projects/Bifrost/.bifrost/provider-store.cc`, optionally pumps Bifrost's command processor, and waits for typed receipts before completing repo Face Discord delivery. Bifrost's provider advertisement exposes `discord.post` command metadata for Odin/Eve agents, and a bounded Weksa live-fire completed visibly. Eve live state now runs end to end through CultLib dev retained publications, Hermodr ordered SSE, Eve component bindings, and VoidBot typed control state. Receipts acknowledge intent only. Heat initial load, provider-driven change, and browser-command change were visibly verified without reload through the same subscription. - next_action: Audit and migrate native portable Persona ownership: Tengu's JSON declares canonical authority while Muninn, Sleipnir, and Hermodr declare projections with self-referential install provenance. Identify the real canonical source for each, migrate canonical Mind into typed `.cc` without elevating projections, then update registry paths and retire the portable compatibility organ when no live consumer remains. + next_action: Finish and deploy the scheduler eligibility cut while the Yggdrasil swarm remains paused: prove elapsed wall-clock with zero mention and sub-threshold semantic pressure queues no model turn, then verify the deployed paused service before any controlled unpause. Resume the native portable Persona ownership audit only after the swarm cannot burn tokens from clock-only readiness.