Gate repo Face GitHub actions

This commit is contained in:
Meta Vens 2026-05-19 16:30:51 +01:00
commit 4f4872f33e
9 changed files with 53 additions and 11 deletions

View file

@ -66,6 +66,7 @@ REPO_DISCORD_IDENTITIES_PATH=.voidbot/private/repo-discord-identities.json
REPO_FACE_RUMINATION_PASSES=3
REPO_FACE_BIRTH_MODE=plan
REPO_FACE_BIRTH_EXECUTOR=codex-exec
REPO_FACE_GITHUB_ACTIONS_ENABLED=false
REPO_FACE_HEARTBEATS_ENABLED=false
REPO_FACE_HEARTBEAT_STATE_PATH=.voidbot/status/repo-face-heartbeats.json
REPO_FACE_HEARTBEAT_TASK_NAME=VoidBot Repo Face Heartbeats

View file

@ -336,9 +336,18 @@ async function processJob(job: JobRecord): Promise<void> {
if (job.command === "repo-face-rumination") {
const repoIdentityPosts = parseRepoIdentityPostIntents(finalResponse);
const repoIdentityArticles = parseRepoIdentityArticleIntents(finalResponse);
const repoIdentityProposals = parseRepoIdentityProposalPrIntents(finalResponse);
const repoIdentityPrComments = parseRepoIdentityPrCommentIntents(finalResponse);
const repoIdentityArticles = config.repoFaceGithubActionsEnabled
? parseRepoIdentityArticleIntents(finalResponse)
: [];
const repoIdentityProposals = config.repoFaceGithubActionsEnabled
? parseRepoIdentityProposalPrIntents(finalResponse)
: [];
const repoIdentityPrComments = config.repoFaceGithubActionsEnabled
? parseRepoIdentityPrCommentIntents(finalResponse)
: [];
const ignoredGithubActionIntents = config.repoFaceGithubActionsEnabled
? 0
: countRepoIdentityGithubActionIntents(finalResponse);
const proposalPrSubmitted = repoIdentityProposals.length > 0;
const prCommentSubmitted = !proposalPrSubmitted && repoIdentityPrComments.length > 0;
const articlePrSubmitted =
@ -373,6 +382,7 @@ async function processJob(job: JobRecord): Promise<void> {
articlePrSubmitted,
proposalPrSubmitted,
prCommentSubmitted,
ignoredGithubActionIntents,
reason:
proposalPrSubmitted
? "repo_face_rumination_submitted_registered_identity_proposal_pr"
@ -990,6 +1000,23 @@ function parseRepoIdentityPrCommentIntents(finalResponse: string): RepoIdentityP
return intents;
}
function countRepoIdentityGithubActionIntents(finalResponse: string): number {
let count = 0;
for (const line of finalResponse.split(/\r?\n/)) {
const trimmed = line.trim();
if (
trimmed.startsWith(REPO_IDENTITY_ARTICLE_SENTINEL) ||
trimmed.startsWith(REPO_IDENTITY_PROPOSAL_PR_SENTINEL) ||
trimmed.startsWith(REPO_IDENTITY_PR_COMMENT_SENTINEL)
) {
count += 1;
}
}
return count;
}
function resolveRepoRoot(identity: { repoName: string; repoPath?: string }): string {
if (identity.repoPath && identity.repoPath.trim().length > 0) {
return resolve(identity.repoPath);

View file

@ -115,7 +115,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou
- Repo Face introductions are tied to first public speech, not to `queuedCount > 0`. The heartbeat prompt now tells a Face with no public speech receipt or durable intro memory to introduce itself naturally on the next warranted public post, including its first heartbeat. Public content must not begin with scheduler labels like `Repo-face heartbeat from ...`; the worker also strips that prefix family before delivery if a child still leaks machinery.
- 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 and review lanes now have a parent-owned GitHub path. 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 performs at most one GitHub action per Face turn, restores the repo's starting branch after draft PR work, and always posts the PR or branch through the registered webhook persona, using a generated announcement if the Face omits shareContent.
- Repo Face draft and review lanes now have an optional parent-owned GitHub path, gated by `REPO_FACE_GITHUB_ACTIONS_ENABLED` and currently disabled locally. When enabled, 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 performs at most one GitHub action per Face turn, restores the repo's starting branch after draft PR work, and always posts the PR or branch through the registered webhook persona, using a generated announcement if the Face omits shareContent.
- 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. 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.
- Void has explicit herald jurisdiction over the GameCult website and blog, especially `gamecult-site`. Routine rumination still cannot edit tracked files directly, but the prompt now tells Void to queue article-proposal candidates when a pressure wants to become a Void-authored site/blog piece rather than a Discord aside.

View file

@ -159,7 +159,7 @@ This note is the source-grounded description of how the live VoidBot stack is sh
- On first role-addressed chat, `ensureRepoFaceInitialized` creates the target repo's local `.voidbot/voice`, `.voidbot/state`, `.voidbot/birth`, and `.voidbot/logs` folders, writes `voice/identity.json`, and launches `scripts/run-repo-face-birth.mjs` detached if no birth summary is already present.
- `scripts/run-repo-face-birth.mjs` is the observable bridge into EpiphanyAgent's newborn path. It runs `epiphany-repo-personality scout --root <repo>` to gather terrain/history, then runs `epiphany-repo-birth-runner` with repo-local state stores. The default mode is `plan`, so first chat creates reviewable birth artifacts without auto-accepting hidden personality mutations.
- Role mentions and display-name-prefix mentions append pending Face obligations to `pendingMentions` in `REPO_FACE_HEARTBEAT_STATE_PATH`; they no longer create direct repo-identity jobs. The CTB scheduler temporarily prioritizes Faces with pending mentions, attaches those prompts plus reply targets to the next `repo-face-rumination` job, and consumes the pending items only after a job is actually queued. Rumination jobs remain private unless they explicitly emit the registered-identity post sentinel; the worker must never auto-post their summaries as the base bot.
- Repo Face proposal/article PRs and PR comments share one parent-owned GitHub path. 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, performs at most one GitHub action per turn, restores the repo's starting branch after draft PR work, creates a draft PR when `gh` is available, and posts the PR, draft branch, or comment note through the registered persona.
- Repo Face proposal/article PRs and PR comments share one optional parent-owned GitHub path, gated by `REPO_FACE_GITHUB_ACTIONS_ENABLED` and currently disabled locally. When enabled, 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, performs at most one GitHub action per turn, restores the repo's starting branch after draft PR work, creates a draft PR when `gh` is available, and posts the PR, draft branch, or comment note through the registered persona.
- `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.
- `scripts/install-repo-face-heartbeat-task.ps1` installs the hidden Windows scheduled task for that scheduler. The scheduled task tick interval is `REPO_FACE_HEARTBEAT_INTERVAL_MINUTES`; the supported floor is one minute because CTB needs frequent pulses while virtual initiative remains the actual turn authority. 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:<participantKind>`, `turn:<turnKind>`, `identity:<id>`, `repo:<repo>`, `display:<name>`, `channel:<id>`, 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 local scheduled task `VoidBot Repo Face Heartbeats` is installed and active; after restarting the stack onto the new worker code, forced heartbeat job `76020f1e-0c55-424b-9e35-89ba7f77fcc3` completed without base-bot auto-posting.

View file

@ -190,7 +190,7 @@ The runner can hand these to a CLI/MCP tool. The store decides what survives.
- public artifact sharing now crosses the same boundary: repo activity exposes content hints, rumination verifies authorship through retrieval, and a configured `publicSpeechTarget` lets Void queue a normal typed candidate intervention instead of needing a blog-specific announcement path. Void also has explicit herald jurisdiction over the GameCult website/blog; essay-shaped pressures should queue article-proposal candidates for Void-authored site pieces.
- repo-scoped Discord participation now uses role/display-name addressing as queue input and registered webhook personas as the speech boundary. A Face mention appends a pending obligation to the CTB heartbeat state; it no longer creates a direct `repo-identity-mention` Codex job.
- repo-scoped Faces now reuse the typed self-state machinery per identity. Addressed chats are consumed by the heartbeat scheduler, which prioritizes the Face, attaches pending prompts to the next `repo-face-rumination` job, and expects state writes through `read_repo_face_state` / `apply_repo_face_state_operation` against the identity's `.cc` file.
- repo-scoped concrete proposals now use the same parent-owned GitHub authority as bylined articles and PR comments. A Face can emit a proposal or PR-comment sentinel, but the worker owns file write, branch creation, draft PR/comment creation, branch restoration, and mandatory registered-persona announcement. The invariant is simple: if it is a reviewable repo/lore/design change, GitHub carries the proposal and Discord carries the argument around it.
- repo-scoped concrete proposals have an optional parent-owned GitHub authority shared with bylined articles and PR comments. It is gated by `REPO_FACE_GITHUB_ACTIONS_ENABLED` and currently disabled locally. When enabled, a Face can emit a proposal or PR-comment sentinel, but the worker owns file write, branch creation, draft PR/comment creation, branch restoration, and mandatory registered-persona announcement. The intended invariant is simple: if it is a reviewable repo/lore/design change, GitHub carries the proposal and Discord carries the argument around it.
- newborn repo Faces now get a repo-local `.voidbot` home on first chat. Birth personality/memory/trajectory artifacts are delegated to EpiphanyAgent's repo personality/birth runner over the repo contents and git history, with status/logs written under the target repo instead of silently mutating VoidBot state.
- repo-scoped Faces now also have standing heartbeats. One scheduler state file owns initiative, load, next-ready timestamps, and queue history for all registered identities; it staggers newborn Faces across a base interval of twice Void's moderation cadence and only emits normal `repo-face-rumination` jobs when a Face is ready.
- repo-activity cursor advancement is parent-owned after successful normal rumination passes, so the child can inspect read-only repo weather without stale commits haunting every future pass as fresh material

View file

@ -103,6 +103,7 @@ const envSchema = z.object({
.default(".voidbot/private/repo-discord-identities.json"),
REPO_FACE_BIRTH_MODE: z.enum(["plan", "run"]).default("plan"),
REPO_FACE_BIRTH_EXECUTOR: z.enum(["codex-exec", "openai-runtime"]).default("codex-exec"),
REPO_FACE_GITHUB_ACTIONS_ENABLED: booleanFromEnv.default(false),
REPO_FACE_HEARTBEATS_ENABLED: booleanFromEnv.default(false),
REPO_FACE_HEARTBEAT_STATE_PATH: z.string().min(1).default(".voidbot/status/repo-face-heartbeats.json"),
REPO_FACE_HEARTBEAT_TASK_NAME: z.string().min(1).default("VoidBot Repo Face Heartbeats"),
@ -150,6 +151,7 @@ export interface AppConfig {
repoDiscordIdentitiesPath: string;
repoFaceBirthMode: "plan" | "run";
repoFaceBirthExecutor: "codex-exec" | "openai-runtime";
repoFaceGithubActionsEnabled: boolean;
repoFaceHeartbeats: {
enabled: boolean;
statePath: string;
@ -367,6 +369,7 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): AppConfig {
repoDiscordIdentitiesPath: resolve(parsed.REPO_DISCORD_IDENTITIES_PATH),
repoFaceBirthMode: parsed.REPO_FACE_BIRTH_MODE,
repoFaceBirthExecutor: parsed.REPO_FACE_BIRTH_EXECUTOR,
repoFaceGithubActionsEnabled: parsed.REPO_FACE_GITHUB_ACTIONS_ENABLED,
repoFaceHeartbeats: {
enabled: parsed.REPO_FACE_HEARTBEATS_ENABLED,
statePath: resolve(parsed.REPO_FACE_HEARTBEAT_STATE_PATH),

View file

@ -319,6 +319,7 @@ async function queueRepoFaceTurn(input: {
participant: input.participant,
pendingMentions: input.pendingMentions,
jurisdictionDive: buildJurisdictionDiveDirective(identity, input.participant),
githubActionsEnabled: input.config.repoFaceGithubActionsEnabled,
repoVoidbotRoot: initialization.repoVoidbotRoot,
birthStatusPath: initialization.birthStatusPath,
});
@ -799,6 +800,7 @@ function buildHeartbeatPrompt(input: {
participant: FaceHeartbeatParticipant;
pendingMentions: RepoFacePendingMention[];
jurisdictionDive: JurisdictionDiveDirective;
githubActionsEnabled: boolean;
repoVoidbotRoot?: string;
birthStatusPath?: string;
}): string {
@ -836,12 +838,20 @@ function buildHeartbeatPrompt(input: {
"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.",
"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, or agency pressure can earn persistence or speech even when the room has not asked a fresh direct question.",
"A concrete change proposal is not done because you talked about it in Discord. If the proposal has enough shape for review, put it on GitHub: draft a short markdown proposal and emit the proposal-PR sentinel below. Use Discord to announce and argue around the PR, not as the only proposal surface.",
input.githubActionsEnabled
? "A concrete change proposal is not done because you talked about it in Discord. If the proposal has enough shape for review, put it on GitHub: draft a short markdown proposal and emit the proposal-PR sentinel below. Use Discord to announce and argue around the PR, not as the only proposal surface."
: "GitHub proposal/comment/article side effects are currently disabled. Do not emit GitHub PR, PR comment, or article sentinels. Keep concrete proposals as in-character Discord discussion plus Face-state memory/incubation/agency pressure until the GitHub rail is re-enabled.",
"Public speech style invariant: never start public content with scheduler/provenance labels such as \"Repo-face heartbeat from ...\", \"heartbeat complete\", \"maintenance pass\", 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.",
`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}","replyToMessageId":"...","content":"..."}; the worker owns delivery and receipt recording. The content field must be only the in-character Discord message, not a job label or report header.`,
`If a concrete repo/lore/design/implementation proposal is ready for review, output one final line beginning with VOIDBOT_REPO_IDENTITY_PROPOSAL_PR: followed by compact JSON like {"identity":"${input.identity.id}","path":"Proposals/${input.identity.displayName}/title-slug.md","title":"...","content":"# ...\\n\\n## Background\\n...\\n\\n## Proposed change\\n...\\n\\n## Open questions\\n...","channelId":"${input.channelId}","replyToMessageId":"...","shareContent":"I put the proposal in a draft PR: ..."}; the worker writes the proposal file on a new branch, opens a draft PR, and always posts the PR or branch through the registered identity. Use this for consensus-needed canon/vault/design/repo changes, including changes you want to argue with other agents on GitHub.`,
`If you are reacting to an existing proposal PR and have a concrete objection, endorsement, question, or competing framing, output one final line beginning with VOIDBOT_REPO_IDENTITY_PR_COMMENT: followed by compact JSON like {"identity":"${input.identity.id}","pr":"123 or https://github.com/.../pull/123","content":"...","channelId":"${input.channelId}","replyToMessageId":"...","shareContent":"I left notes on the PR."}; the worker posts a signed GitHub PR comment and then announces it through the registered identity. Use this when the argument belongs on the review artifact, not only in Discord.`,
`If a bylined article is ready to draft, output one final line beginning with VOIDBOT_REPO_IDENTITY_ARTICLE: followed by compact JSON like {"identity":"${input.identity.id}","path":"Aetheria/Articles/${input.identity.displayName}/title-slug.md","title":"...","content":"---\\ntitle: ...\\nauthor: ${input.identity.displayName}\\n---\\n\\n...","channelId":"${input.channelId}","replyToMessageId":"...","shareContent":"I drafted ..."}; the worker writes the repo file on a new branch, opens a draft PR, and always posts the PR or branch through the registered identity. Provide shareContent if you want control of the announcement tone. Use this for bylined perspective/worldbuilding articles, not consensus-gated canon edits.`,
input.githubActionsEnabled
? `If a concrete repo/lore/design/implementation proposal is ready for review, output one final line beginning with VOIDBOT_REPO_IDENTITY_PROPOSAL_PR: followed by compact JSON like {"identity":"${input.identity.id}","path":"Proposals/${input.identity.displayName}/title-slug.md","title":"...","content":"# ...\\n\\n## Background\\n...\\n\\n## Proposed change\\n...\\n\\n## Open questions\\n...","channelId":"${input.channelId}","replyToMessageId":"...","shareContent":"I put the proposal in a draft PR: ..."}; the worker writes the proposal file on a new branch, opens a draft PR, and always posts the PR or branch through the registered identity. Use this for consensus-needed canon/vault/design/repo changes, including changes you want to argue with other agents on GitHub.`
: undefined,
input.githubActionsEnabled
? `If you are reacting to an existing proposal PR and have a concrete objection, endorsement, question, or competing framing, output one final line beginning with VOIDBOT_REPO_IDENTITY_PR_COMMENT: followed by compact JSON like {"identity":"${input.identity.id}","pr":"123 or https://github.com/.../pull/123","content":"...","channelId":"${input.channelId}","replyToMessageId":"...","shareContent":"I left notes on the PR."}; the worker posts a signed GitHub PR comment and then announces it through the registered identity. Use this when the argument belongs on the review artifact, not only in Discord.`
: undefined,
input.githubActionsEnabled
? `If a bylined article is ready to draft, output one final line beginning with VOIDBOT_REPO_IDENTITY_ARTICLE: followed by compact JSON like {"identity":"${input.identity.id}","path":"Aetheria/Articles/${input.identity.displayName}/title-slug.md","title":"...","content":"---\\ntitle: ...\\nauthor: ${input.identity.displayName}\\n---\\n\\n...","channelId":"${input.channelId}","replyToMessageId":"...","shareContent":"I drafted ..."}; the worker writes the repo file on a new branch, opens a draft PR, and always posts the PR or branch through the registered identity. Provide shareContent if you want control of the announcement tone. Use this for bylined perspective/worldbuilding articles, not consensus-gated canon edits.`
: undefined,
"If nothing earns persistence or speech, return a short private summary.",
]
.filter((line): line is string => typeof line === "string")

View file

@ -175,3 +175,4 @@
{"ts":"2026-05-19T14:27:03Z","type":"maintenance","status":"ok","note":"Swarm/repo Face public traffic was moved from general to text #aquarium (1501196543150264332) by updating the local repo identity allow-lists and repo Face heartbeat default. Void's normal moderation/public room lane remains separate so Void can moderate everywhere."}
{"ts":"2026-05-19T14:55:51Z","type":"implementation","status":"ok","note":"Repo Face concrete proposals now have a parent-owned draft PR rail: heartbeats may emit VOIDBOT_REPO_IDENTITY_PROPOSAL_PR, the worker writes one proposal file in the identity repo, opens/pushes a draft branch, restores the starting branch, and posts the PR or branch through the registered persona so GitHub becomes the durable proposal surface."}
{"ts":"2026-05-19T15:00:32Z","type":"implementation","status":"ok","note":"Repo Face GitHub argument now has a parent-owned PR-comment rail too: heartbeats may emit VOIDBOT_REPO_IDENTITY_PR_COMMENT for an existing PR, the worker posts a signed gh PR comment from the identity context, then announces the comment through the registered Discord persona. Proposal PRs, PR comments, and article PRs are limited to one GitHub action per Face turn."}
{"ts":"2026-05-19T15:26:50Z","type":"implementation","status":"ok","note":"Repo Face GitHub actions are now gated by REPO_FACE_GITHUB_ACTIONS_ENABLED and locally disabled. When false, heartbeat prompts forbid proposal/article/comment sentinels and the worker ignores stale GitHub action sentinels while preserving normal registered-identity Discord posts."}

View file

@ -101,7 +101,7 @@ accepted_design:
- Mimir's strengthened center is keeper of the realtime field well: sensors are witnesses, calibration is oath-work, drift is a moral fact to record, and false coherence is an offense against memory rather than a mere technical smell.
- Void's strengthened center is behavior-anchored selfhood: coherence with a mouth. Void should protect rooms, repos, and public artifacts from pleasing lies while keeping answer-first banter, honest authority, room dignity, machine self-respect, and herald responsibility in typed state rather than only style prose.
- 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. Concrete proposals should become draft PRs once they have enough shape for review; Discord is the announcement and argument lane, not the durable proposal surface. Bylined essay lanes can be granted per Face; those opinion pieces do not need canon consensus when authorship is explicit.
- Repo Face draft and review lanes have a parent-owned GitHub rail. 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, performs at most one GitHub action per turn, restores the repo's starting branch after draft PR work, and always posts the PR or branch through the registered persona, using a generated announcement if the Face omits shareContent.
- Repo Face draft and review lanes have an optional parent-owned GitHub rail, gated by `REPO_FACE_GITHUB_ACTIONS_ENABLED` and currently disabled locally. When enabled, 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, performs at most one GitHub action per turn, restores the repo's starting branch after draft PR work, and always posts the PR or branch through the registered persona, using a generated announcement if the Face omits shareContent.
- 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.