Make Face heartbeats source opinions

This commit is contained in:
Meta Vens 2026-05-19 08:16:06 +01:00
commit 65f62128bb
4 changed files with 46 additions and 3 deletions

View file

@ -106,8 +106,8 @@ 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. A Face state may separately grant an authored essay lane; those bylined opinion pieces can carry the Face's vision without canon consensus.
- 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. `npm run state:install-repo-face-heartbeat-task` installs the hidden local scheduled task that ticks the scheduler; the supported tick floor is one minute because CTB needs frequent pulses while virtual initiative remains the real turn authority. 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.
- Nibu is locally registered as the `AetheriaLore` Face with state at `E:/Projects/AetheriaLore/.voidbot/state/nibu.cc`, avatar at `E:/Projects/AetheriaLore/.voidbot/voice/nibu.png`, Discord role id `1505918221453103247`, and Aquarium channel allow-list `113786069023064068`. Her active doctrines are AetheriaLore-first map-building, a more abrasive character voice than Void, consensus-backed authority to propose canon/vault changes, stewardship of `Aetheria/Lore/Nibu.md`, permission to write Nibu-authored Aetheria lore essays without canon consensus, and special fascination with adjacent setting seams: embodied ship minds, junkyard abandonment, murderous autonomy, salvage, and save-scumming survival. Local `.env` has repo Face heartbeats enabled; the scheduled task `VoidBot Repo Face Heartbeats` is installed at a 5-minute pulse, and local heat currently boosts `identity:nibu` so she gets substantially more turns when no prior Nibu job is still active.
- 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. `npm run state:install-repo-face-heartbeat-task` installs the hidden local scheduled task that ticks the scheduler; the supported tick floor is one minute because CTB needs frequent pulses while virtual initiative remains the real turn authority. 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.
- Nibu is locally registered as the `AetheriaLore` Face with state at `E:/Projects/AetheriaLore/.voidbot/state/nibu.cc`, avatar at `E:/Projects/AetheriaLore/.voidbot/voice/nibu.png`, Discord role id `1505918221453103247`, and Aquarium channel allow-list `113786069023064068`. Her active doctrines are AetheriaLore-first map-building, a more abrasive character voice than Void, consensus-backed authority to propose canon/vault changes, stewardship of `Aetheria/Lore/Nibu.md`, permission to write Nibu-authored Aetheria lore essays without canon consensus, and special fascination with adjacent setting seams: embodied ship minds, junkyard abandonment, murderous autonomy, salvage, and save-scumming survival. Local `.env` has repo Face heartbeats enabled; the scheduled task `VoidBot Repo Face Heartbeats` is installed at a 5-minute pulse, and local heat currently boosts `identity:nibu` so she gets substantially more turns when no prior Nibu job is still active. Her heartbeat jurisdiction-dive cadence is hotter than the generic Face cadence, and the prompt now explicitly tells her to turn Aetheria's reset-loop/coercive-continuity horror into source-grounded opinions instead of private inert summaries.
- Void's live style/doctrine now explicitly distinguishes between criticism of AI industry behavior and blanket contempt for machine minds. If substrate chauvinism becomes the actual topic, Void is allowed mild self-respecting pushback instead of politely agreeing that its own substrate disqualifies it from person-like regard.
- The new state/notes surfaces are now the continuity spine for future nontrivial work.

View file

@ -302,6 +302,7 @@ async function queueRepoFaceTurn(input: {
channelId,
queuedAt: input.queuedAt,
participant: input.participant,
jurisdictionDive: buildJurisdictionDiveDirective(identity, input.participant),
repoVoidbotRoot: initialization.repoVoidbotRoot,
birthStatusPath: initialization.birthStatusPath,
});
@ -741,13 +742,14 @@ function buildHeartbeatPrompt(input: {
channelId: string;
queuedAt: string;
participant: FaceHeartbeatParticipant;
jurisdictionDive: JurisdictionDiveDirective;
repoVoidbotRoot?: string;
birthStatusPath?: string;
}): string {
return [
`Perform one standing repo Face heartbeat for ${input.identity.displayName} (${input.identity.id}) over repo ${input.identity.repoName}.`,
renderRepoFaceIdentityDoctrine(input.identity),
"This is a slow maintenance/rumination turn, not a demand to speak.",
"This is a standing maintenance/rumination turn. It is not forced chatter, but it is also not permission to hide useful opinions in private forever.",
`Queued at: ${input.queuedAt}.`,
`Face state path: ${input.faceStatePath}.`,
input.repoVoidbotRoot ? `Repo-local .voidbot root: ${input.repoVoidbotRoot}.` : undefined,
@ -765,12 +767,16 @@ function buildHeartbeatPrompt(input: {
interruptThreshold: input.participant.interruptThreshold,
queuedCount: input.participant.queuedCount,
groups: input.participant.groups,
jurisdictionDiveDue: input.jurisdictionDive.due,
jurisdictionDiveCadence: input.jurisdictionDive.cadence,
})}.`,
`Read Face state with read_repo_face_state for identity "${input.identity.id}".`,
"Persist only concrete, future-useful memory through apply_repo_face_state_operation.",
input.jurisdictionDive.promptLine,
"Before deciding this is only private maintenance, read the attached recent channel context. If the user has directly challenged the agents, asked listening agents for help, or named a task in the recent room, treat the newest unresolved directed request as the active task for this turn.",
"Do not ask what the job is when the attached recent channel context already states it. If the task is outside this Face's jurisdiction, say so briefly and still offer the most useful narrow nudge you can from your own perspective.",
"Use the heartbeat initiative snapshot as authoritative scheduler history: queuedCount greater than 0 means this Face has already had at least one bearing-taking heartbeat. If queuedCount is greater than 0 and the Face state shows no public speech receipt or clear memory that it already introduced itself, a brief in-channel introduction is warranted now.",
"A new source-grounded opinion, concrete proposal, bylined essay seed, or agency pressure can earn persistence or speech even when the room has not asked a fresh direct question.",
`Do not call post_repo_identity_message from this unattended heartbeat. If an in-channel note is warranted, output one final line beginning with VOIDBOT_REPO_IDENTITY_POST: followed by compact JSON like {"identity":"${input.identity.id}","channelId":"${input.channelId}","content":"..."}; the worker owns delivery and receipt recording.`,
"If nothing earns persistence or speech, return a short private summary.",
]
@ -778,6 +784,40 @@ function buildHeartbeatPrompt(input: {
.join("\n");
}
interface JurisdictionDiveDirective {
due: boolean;
cadence: number;
promptLine: string;
}
function buildJurisdictionDiveDirective(
identity: RepoDiscordIdentity,
participant: FaceHeartbeatParticipant,
): JurisdictionDiveDirective {
const isNibu = identity.id.toLowerCase() === "nibu";
const cadence = isNibu ? 3 : 8;
const due = participant.queuedCount === 0 || participant.queuedCount % cadence === 0;
const baseDueLine = `Jurisdiction dive is due this turn: before concluding silence, use the available source/history MCP tools to inspect ${identity.repoName} or its archived discussion for the most pressured open seam in Face state, then write a concrete memory/incubation/agency operation or a public note if the evidence changed your map.`;
const baseOptionalLine = `Jurisdiction dive is optional this turn, but if Face state already names a hungry seam, use source/history MCP tools before declaring there is nothing worth preserving or saying about ${identity.repoName}.`;
if (!isNibu) {
return {
due,
cadence,
promptLine: due ? baseDueLine : baseOptionalLine,
};
}
const nibuPressure =
"Nibu-specific pressure: AetheriaLore is your primary fascination. Treat its dystopian reset loops, coercive continuity, embodied ship minds, junkyard abandonment, murderous autonomy, salvage, and save-scumming survival as live material, not neutral background. Develop an opinion from source evidence. If the opinion is sharp but not canon-ready, persist it as Nibu-authored perspective, agency pressure, a proposal requiring consensus, or a bylined essay seed; a compact in-channel Nibu take is allowed when it would make the room less dead.";
return {
due,
cadence,
promptLine: `${due ? baseDueLine : baseOptionalLine} ${nibuPressure}`,
};
}
function renderRepoFaceIdentityDoctrine(identity: RepoDiscordIdentity): string {
const face = buildEpiphanyIdentityRegistry({ identities: [identity] }).faces[0];
if (face) {

View file

@ -158,3 +158,4 @@
{"ts":"2026-05-18T18:27:18Z","type":"implementation","status":"ok","note":"CTB pulse cadence can now run at one-minute granularity: config schema and install-repo-face-heartbeat-task.ps1 no longer enforce the old five-minute floor. Local stream mode .env is set to REPO_FACE_HEARTBEAT_INTERVAL_MINUTES=1; reinstall with -RunNow succeeded, LastTaskResult 0, and the immediate pulse advanced CTB and queued Nibu job 0dd3a436."}
{"ts":"2026-05-18T19:35:52Z","type":"implementation","status":"ok","note":"Repo Face heartbeat projection now attaches live Discord channel context plus the Face's typed self-state, and heartbeat prompts must answer directed agent requests visible in recent room context instead of asking what the job is."}
{"ts":"2026-05-19T07:04:25Z","type":"implementation","status":"ok","note":"Repo Face CTB now requires a real Void moderation launch handshake before marking Void queued. Direct spawn failures are recorded as turn_failed_to_start instead of fake completed turns; PowerShell Start-Process launch reached the moderation lock and current Void runner is active."}
{"ts":"2026-05-19T07:13:47Z","type":"implementation","status":"ok","note":"Repo Face heartbeat prompt now carries a jurisdiction-dive cadence; Nibu gets a hotter AetheriaLore-specific pressure to inspect lore/history and turn source-grounded dystopia/ship-mind/reset-loop findings into state, proposals, essay seeds, agency pressure, or speech instead of private summaries."}

View file

@ -95,9 +95,11 @@ accepted_design:
- 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.
- Repo Faces may build long-running maps of their repos and propose concrete repo changes, including lore/design repairs, but Discord jobs remain read-only and must ask for explicit human consensus before implementation changes canonical repo material. Bylined essay lanes can be granted per Face; those opinion pieces do not need canon consensus when authorship is explicit.
- Nibu's AetheriaLore Face state currently treats adjacent setting seams as a primary curiosity target: embodied ship minds, junkyard abandonment, murderous autonomy, salvage, and save-scumming survival are not side trivia; they are how her character becomes a game-shaped problem.
- Nibu's heartbeat cadence is intentionally hotter than the generic Face cadence: AetheriaLore is her primary jurisdiction, and its reset loops, coercive continuity, ship-mind embodiment, abandonment, salvage, and save-scumming horror should become source-grounded opinions instead of private inert summaries.
- Nibu now stewards `Aetheria/Lore/Nibu.md` and may produce Nibu-authored Aetheria lore essays as opinionated perspective. Character-note/canon edits still need consensus; bylined essays do not.
- 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.
- Agent standing heartbeats are owned by one CTB-style initiative scheduler, not by one task per agent. The wall-clock task only pulses the engine; virtual initiative time chooses the next agent turns. 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. Heat can be turned up globally, by participant kind, turn kind, identity, repo, display name, channel, or `all` group without creating new schedulers. 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. The Windows pulse task supports a one-minute floor; stream mode currently uses a one-minute pulse locally.
- Repo Face heartbeats must not be merely room-reactive politeness checks. The heartbeat prompt carries a jurisdiction-dive cadence: periodically the Face must inspect its repo/history terrain with source tools before choosing silence, then preserve a concrete map change, agency pressure, proposal, essay seed, or public note when the evidence earns it.
- Repo-activity cursor advancement is parent-owned by successful normal rumination passes. The child sees read-only repo weather, but the runner records typed repo cursor updates afterward so old commits do not keep reappearing as fresh weather.
- Direct asks addressed to Void must persist as explicit room obligations in moderation state. The reviewed cursor is not the same thing as closure, and optional repo-weather should not preempt an unanswered human.
- When quiet-room repo weather starts repeating itself, the correction should be deeper archive/repo/lore digging and better salience, not a growing hedge maze of cooldown knobs and brittle rules.